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.
This commit is contained in:
Junegunn Choi 2024-01-21 22:58:18 +09:00
parent 687c2741b8
commit 7484292e63
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
2 changed files with 18 additions and 6 deletions

View File

@ -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)"

View File

@ -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")
}