From 4dbc066aac3fbff35b34428a1be1bc4aadbeafd5 Mon Sep 17 00:00:00 2001 From: Hayodea Hekol Date: Fri, 29 May 2026 06:22:02 -0400 Subject: [PATCH] New class: SyncCancelerForAsyncWork This class abstracts the pattern of running an async callee which needs to be able to be canceled from a synchronous code path. It really just lifts the logic that's regularly used in the StimulusProducer path into a reusable abstraction. --- include/spinscale/syncCancelerForAsyncWork.h | 85 ++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 include/spinscale/syncCancelerForAsyncWork.h diff --git a/include/spinscale/syncCancelerForAsyncWork.h b/include/spinscale/syncCancelerForAsyncWork.h new file mode 100644 index 0000000..fd8a89e --- /dev/null +++ b/include/spinscale/syncCancelerForAsyncWork.h @@ -0,0 +1,85 @@ +#ifndef SYNC_CANCELER_FOR_ASYNC_WORK_H +#define SYNC_CANCELER_FOR_ASYNC_WORK_H + +#include +#include + +#include +#include + +namespace sscl { + +/** + * SyncCancelerForAsyncWork + * + * A small helper to coordinate synchronous cancellation requests with + * asynchronous work that must only observe cancellation at explicit + * uncancelable segment boundaries. + * + * The async callee should structure its logic as: + * - enter an uncancelable segment (execUncancelableSegmentOrAbort) + * - perform synchronous work that must not be interrupted + * - exit the segment + * - perform cancelable async work (outside the lock) + * - repeat + * + * requestStop() blocks until any currently-executing segment releases s.lock, + * then flips shouldContinue to false. This guarantees shouldContinue is stable + * throughout each uncancelable segment. + * + * startAcceptingWork() is intentionally unlocked. Precondition: callers must + * only call startAcceptingWork() when no async callee is running yet (e.g. at + * the end of setup(), before posting/arming the first async work). If this + * method races a running callee, that is a caller bug. + */ +class SyncCancelerForAsyncWork +{ +public: + SyncCancelerForAsyncWork() = default; + + void startAcceptingWork() + { + // Intentionally unlocked — see class-level EXPLANATION above. + s.rsrc.shouldContinue = true; + } + + /** @return shouldContinue before this call (was work being accepted?) */ + bool requestStop() + { + sscl::SpinLock::Guard guard(s.lock); + const bool wasContinuing = s.rsrc.shouldContinue; + s.rsrc.shouldContinue = false; + return wasContinuing; + } + + /** @return true if requestStop() has set shouldContinue to false. */ + bool isCancellationRequested() + { + sscl::SpinLock::Guard guard(s.lock); + return !s.rsrc.shouldContinue; + } + + template + requires std::invocable + bool execUncancelableSegmentOrAbort(Body&& body) + { + sscl::SpinLock::Guard guard(s.lock); + if (!s.rsrc.shouldContinue) { + return false; + } + std::forward(body)(); + return true; + } + +private: + struct Resources + { + bool shouldContinue = false; + }; + + sscl::SharedResourceGroup s; +}; + +} // namespace sscl + +#endif // SYNC_CANCELER_FOR_ASYNC_WORK_H