diff options
author | Junegunn Choi <junegunn.c@gmail.com> | 2024-01-21 22:58:18 +0900 |
---|---|---|
committer | Junegunn Choi <junegunn.c@gmail.com> | 2024-01-21 23:04:37 +0900 |
commit | 7484292e63f0dabca4cccdca136f6a6d105b3b79 (patch) | |
tree | 90deb960a810a9a1125c3374f921097779d248cb | |
parent | 687c2741b8d5c0cfcd0b318596cd04914fecf4e9 (diff) |
Avoid deadlocks by adding a 2 second timeout to GET / endpoint
Because fzf processes HTTP GET requests in the main event loop,
accessing the endpoint from within execute/transform actions would
result in a deadlock and hang fzf indefinitely. This commit sets
a 2 second timeout to avoid the deadlock.
-rw-r--r-- | man/man1/fzf.1 | 8 | ||||
-rw-r--r-- | src/server.go | 16 |
2 files changed, 18 insertions, 6 deletions
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 2a528e22..d5fc96ff 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -831,12 +831,14 @@ e.g. \fB# Start HTTP server on port 6266 fzf --listen 6266 - # Get program state in JSON format (experimental) - curl localhost:6266 - # Send action to the server curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )' + # Get program state in JSON format (experimental) + # * Make sure NOT to access this endpoint from execute/transform actions + # as it will result in a timeout + curl localhost:6266 + # Start HTTP server on port 6266 with remote connections allowed # * Listening on non-localhost address requires using an API key export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)" diff --git a/src/server.go b/src/server.go index e655896f..d5a148df 100644 --- a/src/server.go +++ b/src/server.go @@ -30,7 +30,9 @@ const ( httpOk = "HTTP/1.1 200 OK" + crlf httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf + httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf httpReadTimeout = 10 * time.Second + jsonContentType = "Content-Type: application/json" + crlf maxContentLength = 1024 * 1024 ) @@ -141,7 +143,7 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string { return answer(httpBadRequest, message) } good := func(message string) string { - return answer(httpOk+"Content-Type: application/json"+crlf, message) + return answer(httpOk+jsonContentType, message) } conn.SetReadDeadline(time.Now().Add(httpReadTimeout)) scanner := bufio.NewScanner(conn) @@ -165,8 +167,16 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string { getMatch := getRegex.FindStringSubmatch(text) if len(getMatch) > 0 { server.actionChannel <- []*action{{t: actResponse, a: getMatch[1]}} - response := <-server.responseChannel - return good(response) + select { + case response := <-server.responseChannel: + return good(response) + case <-time.After(2 * time.Second): + go func() { + // Drain the channel + <-server.responseChannel + }() + return answer(httpUnavailable+jsonContentType, `{"error":"timeout"}`) + } } else if !strings.HasPrefix(text, "POST / HTTP") { return bad("invalid request method") } |