Fixes #11777. Reverts #11006. Replaces #13501.
With this change the HTTP server will exit gracefully, meaning that all requests will finish processing and sending the response, even if this means to wait more than 2 seconds (current time allowed to exit the event loop).
Another small change is that connections are accepted even when the server is stopping, but HTTP requests are rejected. This can be improved later, especially if chunked replies are implemented.
Briefly, before this PR, this is the order or events when a request arrives (RPC stop
):
bufferevent_disable(..., EV_READ)
StartShutdown()
evhttp_del_accept_socket(...)
ThreadHTTP
terminates (event loop exits) because there are no active or pending events thanks to 1. and 3.- client doesn’t get the response thanks to 4.
This can be verified by applying
0 // Event loop will exit after current HTTP requests have been handled, so
1 // this reply will get back to the client.
2 StartShutdown();
3+ MilliSleep(2000);
4 return "Bitcoin server stopping";
5 }
and checking the log output:
0 Received a POST request for / from 127.0.0.1:62443
1 ThreadRPCServer method=stop user=__cookie__
2 Interrupting HTTP server
3** Exited http event loop
4 Interrupting HTTP RPC server
5 Interrupting RPC
6 tor: Thread interrupt
7 Shutdown: In progress...
8 torcontrol thread exit
9 Stopping HTTP RPC server
10 addcon thread exit
11 opencon thread exit
12 Unregistering HTTP handler for / (exactmatch 1)
13 Unregistering HTTP handler for /wallet/ (exactmatch 0)
14 Stopping RPC
15 RPC stopped.
16 Stopping HTTP server
17 Waiting for HTTP worker threads to exit
18 msghand thread exit
19 net thread exit
20
21 ... sleep 2 seconds ...
22
23 Waiting for HTTP event thread to exit
24 Stopped HTTP server
For this reason point 3. is moved right after all HTTP workers quit. In that moment HTTP replies are queued in the event loop which keeps spinning util all connections are closed. In order to trigger the server side close with keep alive connections (implicit in HTTP/1.1) the header Connection: close
is sent if shutdown was requested. This can be tested by
0bitcoind -regtest
1nc localhost 18443
2POST / HTTP/1.1
3Authorization: Basic ...
4Content-Type: application/json
5Content-Length: 44
6
7{"jsonrpc": "2.0","method":"stop","id":123}
Summing up, this PR:
- removes explicit event loop exit — event loop exits once there are no active or pending events
- changes the moment the listening sockets are removed — explained above
- sends header
Connection: close
on active requests when shutdown was requested which is relevant when it’s a persistent connection (default in HTTP 1.1) — libevent is aware of this header and closes the connection gracefully - removes event loop explicit break after 2 seconds timeout