Commit 652934fb793743ab1bf962b90c7a7d6f6681c199 in #274 sets FD_CLOEXEC in order "to ensure sockets are not leaked if processes are spawned". However it's cleared too early, before forking. This PR clears it after forking, but still before exec.
The first commit adds a helper for signal-safely emitting an error message, since the second commit also needs this.
The second commit and a code comment explain why it's unsafe to clear FD_CLOEXEC before fork. ryanofsky also described it:
As I understand it, this race has always existed, and was not introduced in 652934f. What 652934f did was start using
FD_CLOEXECwhich narrowed the race window, without completely closing it. This followup PR fixes the race more completely, but there is still a small race between creating the socketpair and applying the cause the CLOEXEC flags.The race happens when a
SpawnProcesscall happens at the same time as a separateforkcall in unrelated thread not usingSpawnProcess. BecauseSpawnProcesscreates a socket pair, if a separateforkhappens in another thread, it could inherit the socket pair file descriptors and keep them open too long if it is not looping over them and closing them likeSpawnProcessis. As I understand it this could result insocketpairconnections staying open even after theSpawnProcessparent or child have closed them, soonDisconnectevents might not be triggered, and resources might not be freed.
A test in https://github.com/Sjors/libmultiprocess/commit/1e1ff0397bf26b69c0c4b0dfffac4106490f482c demonstrates the issue, but is not included in the PR to keep things simple.
This should not be an actual problem in the way Bitcoin Core uses libmultiprocess today, but it may be with Windows and/or multiple connection support.