Do not reuse DB handles in child processes - #1256
Conversation
Both the test agent and RPC API fork child processes from a common parent. The parent process may connect to the database and if that happens, the child process may reuse the connection that was initiated by the parent. However, this causes mayhem in the test agent. While the parent process uses the DB connection to check for new tests to carry out, the child processes use that same DB connection to update the test progress and store the results. DB connection handles aren’t meant to be used concurrently, so subtle problems can happen in this situation. It also causes mayhem in the RPCAPI because the common parent makes a connection before forking the worker children. Each child therefore shares the same connection handle concurrently, which can also cause the same subtle problems in production. The solution is to ensure that database connection handles are always tied to a specific PID. In Zonemaster::Backend::DB, this check is done whenever the dbh() method is called: if the current PID is not the same as the PID that created the database connection, we return a new connection.
|
Testing prior to this PR on FreeBSD with Mysql I sometimes get the following errors: Could that be related? I also see |
|
When testing prior to this PR on Rocky 8, I get similar errors in the logs. I did have to have 15 concurrent tests in flight instead of 5 to get such an error. The documentation in DBD::mysql says:
Although we don’t have threads, we did share handles among processes, which is certainly just as bad. Basically this seems to me like another symptom of the problem this PR addresses, yes. |
Yes those errors are just symptoms of the issue that this PR solves. Many other errors/warnings are possible, as the Backend code can crash in other places. |
matsduf
left a comment
There was a problem hiding this comment.
With is PR installed there are no errors under FreeBSD. I tested with SQLite, MySQL and Postgresql. Before the PR both MySQL and Postgresql gave errors.
Purpose
This PR fixes the accidental reuse of the same database connection handle between a parent process and its children, which itself could cause random failures in both the test agent and the RPC API components of Zonemaster-Backend.
The symptom: after some use, a command like
zmtest SOME-DOMAINwould have its progress stay at 0% forever until the zm-testagent service is restarted. And lines such as the following ones appear in the logs of zm-testagent:Commit d603804 introduced a pre-flight check in both the test agent and the RPC API, and this commit is where this specific problem started happening. That pre-flight check involves connecting to the database to check the schema version before starting the daemon proper. For some mysterious reason, if this is done on a Red Hat-based system like RHEL or Rocky Linux (I was unable to reproduce this on Ubuntu and haven’t tested on FreeBSD), it caused database connection handles to be used concurrently by both the parent process and all of its children. The documentation of DBD::Pg specifically warns against this by saying:
The solution is to ensure that database connection handles are always tied to a specific PID. In Zonemaster::Backend::DB, this check is done whenever the dbh() method is called: if the current PID is not the same as the PID that created the database connection, we return a new connection.
Context
Troubleshooting production instances of Zonemaster.
Changes
How to test this PR
To reproduce the problem: install Zonemaster-Backend on a Rocky Linux 8 machine following the instructions. Set up a PostgreSQL database.
Then, in a terminal, run
journalctl -xfu zm-testagent.Meanwhile, in another terminal, run
for d in a.fr b.fr c.fr d.fr e.fr; do zmb start_domain_test --domain $d; done.Watch the logs. When each test starts, there should be a log indicating a new connection to the database. And all tests should complete without ever generating any warnings involving
DBD::Pg::do(see examples above).