[BUG] Scope the session lock to the snapshot in resetMultiHandle - #4394
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4394 +/- ##
==========================================
+ Coverage 82.18% 82.26% +0.09%
==========================================
Files 501 501
Lines 19847 19847
==========================================
+ Hits 16310 16326 +16
+ Misses 3537 3521 -16
🚀 New features to boost your workflow:
|
marcalff
left a comment
There was a problem hiding this comment.
LGTM.
Thanks for the fix, and, more importantly, for all the analysis to identify this.
|
label fix-merge-conflicts: The conflict resolution HTML interface in github is good enough to resolve minor conflicts in CHANGELOG, so maintainers sometime resolve conflicts directly. In this case I don't want to take risks and cause damages to your tests, so please take a look and rebase. Thanks for the fix. |
thank you for consider! I fix this now!!! thx for hard work reviewing!!! |
resetMultiHandle held sessions_m_ for the whole function, then called CancelSession and doRemoveSessions, both of which take it again on the same thread. It is a plain std::mutex, so the IO thread stopped there and never came back, on the path that recovers from a curl_multi_perform error. The lock only ever guarded the snapshot, so it is scoped to that. Making the mutex recursive would have worked and been wrong: FinishOperation runs the caller's handler through Cleanup, and sessions_m_ was never meant to cover user code. A test peer reaches resetMultiHandle directly, since curl_multi_perform cannot be made to fail from a test and a production failpoint is not worth carrying. One registered session is enough to reach both re-lock sites. Fixes open-telemetry#4389. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
Add the CHANGELOG entry now that the pull request has a number. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
Include opentelemetry/version.h directly in the curl test. The test peer this change adds is wrapped in OPENTELEMETRY_BEGIN_NAMESPACE, and the file had been picking that macro up transitively, which include-what-you-use rejects. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
ef14c9d to
b32c8cf
Compare
|
Rebased, and thank you for leaving it to me rather than guessing at the test file. The #4399 merging is what caused it. Both conflicts were against it: its Same conflict on #4392 and #4395 for the same reason, so I rebased those two as well. All three are Checked after the rebase rather than assuming it came through clean: the lock is still scoped to the snapshot, the test peer and its One thing worth flagging while you are here. All four of my open curl changes add their |
[BUG] Scope the session lock to the snapshot in resetMultiHandle (open-telemetry#4394)
Fixes #4389.
resetMultiHandle()tooksessions_m_for the whole function and then called two things that take it again on the same thread:CancelSession(), which reachesCleanupSession(), anddoRemoveSessions().sessions_m_is a plainstd::mutex, so the IO thread stopped there and never came back. That is the path that recovers from acurl_multi_performerror, so the failure mode is a background thread that quietly stops existing while everything queued behind it waits.The lock only ever guarded the snapshot. It is scoped to that now, which is the whole production change:
std::list<std::shared_ptr<Session>> sessions; { std::lock_guard<std::mutex> session_lock_guard{sessions_m_}; std::lock_guard<std::recursive_mutex> session_id_lock_guard{session_ids_m_}; ... } for (auto &session : sessions) { session->CancelSession(); session->FinishOperation(); } doRemoveSessions();Why not make the mutex recursive
It would work, and it would be the wrong shape.
FinishOperation()reachesCleanup(), which runs the caller's event handler and their completion callback.sessions_m_was never meant to cover user code, and a handler that touches the client from inside it would deadlock against a lock it cannot see. Scoping the snapshot keeps the lock covering the container it was written for.Testing it
curl_multi_performcannot be made to fail from a test, so the case reachesresetMultiHandle()directly through a peer, the same way the OTLP exporters reach their private members. One registered session is enough: the snapshot picks it up,CancelSession()reaches the first re-lock anddoRemoveSessions()the second.I did carry a production failpoint while investigating, forcing the branch once with an injected counter, and that is how the report on #4389 got its stack. It is not in this change and I do not think it should be.
Against
mainwith only the test added, the case does not fail, it hangs:timeout 45gives exit 124 and zero cases finished. With the fix it passes in 501 ms. Worth knowing for a future bisect, since a timeout rather than a red assertion is what a regression here looks like.What this does not fix
curl_multi_init()can returnnullptr, and the current code stores that, after which everycurl_multi_performreturnsCURLM_BAD_HANDLEand lands straight back inresetMultiHandle(). Recovering from a failure to rebuild the multi handle is a policy question rather than a bug fix, so it is not here. #4391 covers the teardown ordering on the same path.Checks
23 of 23 in
curl_http_test, clean underOTELCPP_MAINTAINER_MODE=ON, clean underclang-format18.1.8.For significant contributions please make sure you have completed the following items:
CHANGELOG.mdupdated for non-trivial changes