Skip to content

Fix client self cancel#194

Merged
mdavidsaver merged 6 commits into
epics-base:masterfrom
mdavidsaver:client-self-cancel
Jul 17, 2026
Merged

Fix client self cancel#194
mdavidsaver merged 6 commits into
epics-base:masterfrom
mdavidsaver:client-self-cancel

Conversation

@mdavidsaver

Copy link
Copy Markdown
Member

Attempt to resolve #192 . Alternative to slac-epics#18 , including a modified version of that added test case.

@george-mcintyre Please have a look.

The basic goal is to prevent Operation/Subscription callback functors from being free'd while executing. There are two situations where this may happen. With API calls which allow a new functor to be set, and as part of cancellation. This PR changes such that:

  • Attempting to assign a new functor during callback execution becomes a logic_error.
  • Attempting to cancel() during callback execution will silent skip freeing the functor. This being deferred to a later repeated cancel(), or destruction.
  • Log an ERR if Operation/Subscription destruction happens on the client worker thread and syncCancel==true (the default).

With this new ERR condition. Such situations with syncCancel==true are assumed to be accidental, where the caller is not aware of the possibility of a self-reference loop. eg. The Operation owns the callback which owns the Operation which ... This is not always true though. As was the case in QSRV changed by this PR. However, I think that all such situations should anyway set syncCancel==false.

Mechanically, "busy" flags are added for all callback functors. This includes in the Discovery class, which is also moved to be local to discovery.cpp.

Functor lifetime during the handle_*() methods is tied to a shared_ptr, which remains in-scope until the end of the method. So, with the added "busy" flags repected, the functor can not be destructed during execution.

mdavidsaver and others added 6 commits July 9, 2026 11:46
It is not safe to allow the event callback function to
be freed while it is executing.  This is UB.
Currently possible by cancellation, or the onEvent expert
method.

1. onEvent() from a callback becomes logic_error
2. Explicit Subscription::cancel() during callback is made safe.
   Defer free to a later repeat cancel() or dtor.
3. Apply the same logic to onInit
Also privatize struct Discovery with added notify_busy flag.
Log an error when an operation created with syncCancel=true
is allowed to be free'd asynchronously on the client worker.

This may indicate that the Operation/Subscription is directly
owned by its own callback.  Creating a self-reference loop.
Or it might indicate a more complicated, but safe, situation
like pvalink where a new operation is initiated as the previous
one completes.

Use the syncCancel=false flag, opting of blocking cancellation,
as an indication that this risk is accepted by user code.
@mdavidsaver mdavidsaver self-assigned this Jul 9, 2026
@mdavidsaver mdavidsaver added the bug Something isn't working label Jul 9, 2026
@mdavidsaver
mdavidsaver merged commit cc7bc72 into epics-base:master Jul 17, 2026
45 checks passed
@mdavidsaver
mdavidsaver deployed to github-pages July 17, 2026 21:35 — with GitHub Actions Active
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle self-cancellation / replacement of callbacks during execution

2 participants