summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2024-01-21 22:58:18 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2024-01-21 23:04:37 +0900
commit7484292e63f0dabca4cccdca136f6a6d105b3b79 (patch)
tree90deb960a810a9a1125c3374f921097779d248cb
parent687c2741b8d5c0cfcd0b318596cd04914fecf4e9 (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.18
-rw-r--r--src/server.go16
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")
}