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