@@ -57,9 +57,8 @@ import Combine
5757/// ```
5858///
5959/// ## Thread Safety:
60- /// MonoTask uses two semaphores for fine-grained thread safety:
61- /// - `resultSemaphore`: Protects cached result and expiration state
62- /// - `callbackSemaphore`: Protects callback array and execution state
60+ /// MonoTask uses a single internal semaphore for fine-grained thread safety:
61+ /// - Protects cached result, expiration timestamp, callback registration, and execution state
6362///
6463/// ## Performance:
6564/// - **Execution Merging**: Prevents duplicate work when multiple callers request same task
@@ -150,10 +149,10 @@ public class MonoTask<TaskResult> {
150149 /// 2. Determines if a new execution should be started (execution merging)
151150 /// 3. Starts execution only if no other execution is currently running
152151 ///
153- /// **Thread Safety**: Protected by `callbackSemaphore`
152+ /// **Thread Safety**: Protected by a single internal semaphore
154153 ///
155154 /// **Execution Merging**: Multiple concurrent calls to this method will:
156- /// - All register their callbacks in the `_callbacks ` array
155+ /// - All register their callbacks in the `waitingCallbacks ` array
157156 /// - Only the first call will trigger actual execution
158157 /// - All callbacks receive the same result when execution completes
159158 ///
@@ -171,7 +170,7 @@ public class MonoTask<TaskResult> {
171170
172171 // 2) Register callback for result notification
173172 if let completionCallback {
174- waitingCallbacks! . append ( completionCallback)
173+ waitingCallbacks? . append ( completionCallback)
175174 }
176175
177176 // 3) Schedule a brand-new execution and mark force scheduled
@@ -204,7 +203,7 @@ public class MonoTask<TaskResult> {
204203
205204 // Register callback for result notification
206205 if let completionCallback {
207- waitingCallbacks! . append ( completionCallback)
206+ waitingCallbacks? . append ( completionCallback)
208207 }
209208
210209 // Start execution only if not already running (execution merging)
@@ -228,8 +227,8 @@ public class MonoTask<TaskResult> {
228227 /// - If `clearResult()` is called during execution, execution ID changes
229228 /// - Stale retry attempts check their ID and abort if execution was cancelled
230229 ///
231- /// **Thread Safety**: This method runs on `taskQueue` and uses `resultSemaphore`
232- /// for cache state protection
230+ /// **Thread Safety**: This method runs on `taskQueue` and uses the internal semaphore
231+ /// for cache and execution state protection
233232 ///
234233 /// - Parameter retryConfiguration: Current retry configuration (decrements on each retry)
235234 private func _unsafe_execute( retry retryConfiguration: RetryCount ) {
@@ -283,8 +282,8 @@ public class MonoTask<TaskResult> {
283282 /// 3. Invoke all callbacks with the same result
284283 /// 4. Transition task from "executing" to "idle" state
285284 ///
286- /// **Thread Safety**: Uses `callbackQueue` and `callbackSemaphore` for safe
287- /// callback array manipulation
285+ /// **Thread Safety**: Uses `callbackQueue` for delivery. Callback extraction and state
286+ /// transitions are protected by the internal semaphore before hopping to `callbackQueue`.
288287 ///
289288 /// **State Transition**: After this method completes, the task transitions from
290289 /// "executing" to "idle" state (`waitingCallbacks` becomes nil)
@@ -304,7 +303,18 @@ public class MonoTask<TaskResult> {
304303 }
305304 }
306305
306+ /// Latest successfully cached result if available and not expired.
307+ ///
308+ /// - Note: This property is `@Published` for Combine subscribers and updated on
309+ /// the internal synchronization boundary. Delivery occurs on `callbackQueue` when
310+ /// produced by an execution.
307311 @Published public private( set) var result : TaskResult ? = nil
312+
313+ /// Indicates whether the task is currently executing.
314+ ///
315+ /// - Note: This property is `@Published` and flips to `true` when the first caller
316+ /// begins an execution (i.e., `waitingCallbacks` becomes non-nil) and back to `false`
317+ /// when callbacks have been notified and `waitingCallbacks` is cleared.
308318 @Published public private( set) var isExecuting : Bool = false
309319}
310320
0 commit comments