Detect client disconnection while running query and immediately interrupt its execution#198
Detect client disconnection while running query and immediately interrupt its execution#198Stolb27 merged 6 commits into6.16.3_arenadata22from
Conversation
Fixes the case when the server is executing a lengthy query and the client breaks the connection. The operating system will be aware that the connection is no more, but postgres node doesn't notice this, because it doesn't try to read from or write to the socket while running query. So we'll get a zombie connection. In theory, the query could be one that runs for a million years, continues to chew up CPU and I/O and occupies a connection slot that's sad. Worse still, a sent query might be modifiable and not return any data, then it might be surprising for disconnected client that his previously sent modification will be accepted at some point later - at completion of execution. For these reasons, the query have to be interrupted as much earlier as possible. The patch provides a new GUC check_client_connection_interval that can be used to periodically check via CLIENT_CHECK_CONNECTION_TIMEOUT interrupts whether the client connection has gone away, while running very long queries. It is disabled by default. For non-locking check of socket state the patch uses a non-standard Linux extension (also adopted by at least one other OS) - POLLRDHUP option that is not defined by POSIX. Backport from PostgreSQL commits: - https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c30f54ad732ca5c8762bb68bbe0f51de9137dd72 - https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=22f6f2c1ccb56e0d6a159d4562418587e4b10e01
darthunix
left a comment
There was a problem hiding this comment.
May be we should also import postgres/postgres@f8e5f15 and postgres/postgres@be42015 commits as well to make the code base fully equivalent to the upstream postgres? It doesn't seem to be a big back-port from my point of view (but it is up to you, @maksm90 )
|
|
||
| /* Start timeout for checking if the client has gone away if necessary. */ | ||
| if (client_connection_check_interval > 0 && | ||
| Gp_role != GP_ROLE_EXECUTE && |
There was a problem hiding this comment.
What is the reason to avoid executor to check its TCP connection status with CLIENT_CONNECTION_CHECK_TIMEOUT (may we you have confused it with STATEMENT_TIMEOUT)? At first glance it doesn't seems to be bad if executor stops its slice processing on connection teardown with coordinator.
There was a problem hiding this comment.
It makes sense. I'll try to exhaustively test the cases of possible executor interrupts to check the correctness of our logic for executor role.
There was a problem hiding this comment.
I have tested different scenarios of disconnection executor with coordinator and our logic comes up big. Pushed fix
There was a problem hiding this comment.
Restored this guard condition, as handle of QD<->QEs failure will be implemented in future - not within this PR
All successive improvements around |
| #if !(defined(POLLRDHUP) || defined(__darwin__)) | ||
| /* Linux and OSX only, for now. See pq_check_connection(). */ | ||
| if (*newval != 0) | ||
| { | ||
| GUC_check_errdetail("client_connection_check_interval must be set to 0 on platforms that lack POLLRDHUP."); | ||
| GUC_check_errdetail("client_connection_check_interval must be set to 0 on platforms that lack POLLRDHUP and not OSX."; |
There was a problem hiding this comment.
Note this check is necessary for other parts of this commit to be safe on systems other than having POLLRDHUP and OSX.
darthunix
left a comment
There was a problem hiding this comment.
I can see that current PR doesn't help when we isolate QE port with firewall. In this case query hangs as master backend doesn't detect network problem, though keepalive messages caused RST. I believe we should fix GPDB cluster dispatching code as well in the current PR.
src/backend/libpq/pqcomm.c
Outdated
| int rc; | ||
|
|
||
| pollfd.fd = MyProcPort->sock; | ||
| #ifdef POLLRDHUP |
There was a problem hiding this comment.
I don't like the nested preprocessing as such code is not easy to read. May be we should unroll it to a single level #if defined(POLLRDHUP) ... #elif defined(__darwin__) ... #end?
There was a problem hiding this comment.
I agree, but don't like fixed version much, because of copypaste.
Will it be better to move polling logic to separate function with two parameters?
static bool poll_fd(short init_events, short tgt_revents)
Or add additional logic which can costs additional processing time, but still acceptable.
bool
pq_check_connection(void){
short rdhup_ev;
#if defined(POLLRDHUP)
rdhup_ev = POLLRDHUP;
#elif defined(__darwin__)
rdhup_ev = 0;
#else
return true;
#endif
/*...*/
pollfd.events = POLLOUT | POLLIN | rdhup_ev;
/*...*/
else if (rc == 1 && (pollfd.revents & (POLLHUP | rdhup_ev)))
/*...*/
}
Or any variation of this.
I have decided to implement coordinator disconnection check logic for QEs in separate PR |
Stolb27
left a comment
There was a problem hiding this comment.
I've tested changes on provided cases. On the first look, all work as expected.
This patch is a result of using futurize -w -n --stage1 {python file} on the
python code base.
This patch is a result of using futurize -w -n --stage2 {python file} on the
python code base.
1. Add install of future python package to Dockerfile 2. Add gpMgmt/test/behave/mgmt_utils/steps directory to PYTHONPATH in behave test to fix import 3. Partly ported changes to sql_isolation_testcase.py from 4. Use string type variables from `six` in a number of places to correctly determine string type. 5. Use a lambda function instead of str.strip in map to not depend on actual string types in the array. 6. Cast argument of io.StringIO to unicode 7. Specify encoding when converting str to bytes in gpconfig_helper.py 8. Fix expected test output for parallel_retrieve_cursor/corner 9. Fix output in sql_isolation_testcase.py 10. Add installation of future package in ABi tests. 11. Do not use str and char types from the future package since it brings problems with type comparisons. 12. Do not use standard library aliases since it brings problems with unit tests. 13. In unit tests compare json strings as dict because order of elements in json is not stable. 14. Use future version 0.16 for distributions where we use Python 2, as this version is used in these distributions.
Motivation
This PR fixes the case when the server is executing a lengthy query and the client breaks the connection. The operating system will be aware that the connection is no more, but postgres node doesn't notice this, because it doesn't try to read from or write to the socket while running query. So we'll get a zombie connection. In theory, the query could be one that runs for a million years, continues to chew up CPU and I/O and occupies a connection slot that's sad. Worse still, a sent query might be modifiable and not return any data, then it might be surprising for disconnected client that his previously sent modification will be accepted at some point later - at completion of execution. For these reasons, the query have to be interrupted as much earlier as possible.
Implementation details
The patch provides a new GUC
check_client_connection_intervalthat can be used to periodically check viaCLIENT_CHECK_CONNECTION_TIMEOUTinterrupts whether the client connection has gone away, while running very long queries. It is disabled by default.For non-locking check of socket state the patch uses a non-standard Linux extension (also adopted by at least one other OS) -
POLLRDHUPoption that is not defined by POSIX.Backport from PostgreSQL commits:
Scenarios to test functionality
There are at least three test cases that have to be passed to make sure of patch correction:
In this case client's OS sends FIN message and server have to handle changed socket state on the next iteration of
CLIENT_CONNECTION_CHECK_TIMEOUTinterrupt triggering.This case requires to set settings related with keepalive behavior (
tcp_keepalives_idle,tcp_keepalives_intervalandtcp_keepalives_count). And server terminates "zombie" backend after connection reset ensured by keepalive procedure.After asynchronous sending long query client have to close connection by sending 'X' message (calling
PQfinishlibpq function). Hereon, connection is gracefully closed by client and backend process have to cancel query execution on the next iteration ofCLIENT_CONNECTION_CHECK_TIMEOUTinterrupt.