@@ -46,13 +46,11 @@ public final class Signal<Value, Error: Swift.Error> {
4646 private let core : Core
4747
4848 private final class Core {
49- private enum OperationResult {
50- case shouldDispose
51- case shouldTryCommitTermination
52- case none
53- }
54-
5549 /// The disposable associated with the signal.
50+ ///
51+ /// Disposing of `disposable` is assumed to remove the generator
52+ /// observer from its attached `Signal`, so that the generator observer
53+ /// as the last +1 retain of the `Signal` core may deinitialize.
5654 private let disposable : SerialDisposable
5755
5856 /// The state of the signal.
@@ -104,6 +102,8 @@ public final class Signal<Value, Error: Swift.Error> {
104102 } else {
105103 self . stateLock. unlock ( )
106104 }
105+
106+ tryToCommitTermination ( )
107107 } else {
108108 self . sendLock. lock ( )
109109 self . stateLock. lock ( )
@@ -119,20 +119,26 @@ public final class Signal<Value, Error: Swift.Error> {
119119 }
120120
121121 self . sendLock. unlock ( )
122- }
123122
124- // Check if the status has been bumped to `terminating` due to a
125- // terminal event being sent concurrently or recursively.
126- //
127- // The check is deliberately made outside of the `sendLock` so that it
128- // covers also any potential concurrent terminal event in one shot.
129- //
130- // Related PR:
131- // https://github.com/ReactiveCocoa/ReactiveSwift/pull/112
132- if . shouldDispose == self . tryToCommitTermination ( ) {
133- // Dispose only after notifying observers, so disposal
134- // logic is consistently the last thing to run.
135- self . disposable. dispose ( )
123+ // Check if the status has been bumped to `terminating` due to a
124+ // terminal event being sent concurrently or recursively.
125+ //
126+ // The check is deliberately made outside of the `sendLock` so that it
127+ // covers also any potential concurrent terminal event in one shot.
128+ //
129+ // Related PR:
130+ // https://github.com/ReactiveCocoa/ReactiveSwift/pull/112
131+ //
132+ // While calling `tryToCommitTermination` is sufficient, this is a fast
133+ // path for the recurring value delivery.
134+ if stateLock. try ( ) {
135+ if case . terminating = state {
136+ stateLock. unlock ( )
137+ tryToCommitTermination ( )
138+ } else {
139+ stateLock. unlock ( )
140+ }
141+ }
136142 }
137143 }
138144
@@ -178,27 +184,12 @@ public final class Signal<Value, Error: Swift.Error> {
178184 let observer = newObservers. remove ( using: token)
179185 self . state = . alive( newObservers, hasDeinitialized: hasDeinitialized)
180186
181- var result = OperationResult . none
182-
183187 // Ensure `observer` is deallocated after `stateLock` is
184188 // released to avoid deadlocks.
185189 withExtendedLifetime ( observer) {
186190 // Start the disposal of the `Signal` core if the `Signal` has
187191 // deinitialized and there is no active observer.
188- result = tryToDisposeSilentlyIfQualified ( )
189-
190- stateLock. unlock ( )
191- }
192-
193- if result == . shouldTryCommitTermination {
194- result = tryToCommitTermination ( )
195- }
196-
197- if result == . shouldDispose {
198- // Disposing of `disposable` is assumed to remove the generator
199- // observer from its attached `Signal`, so that the generator observer
200- // as the last +1 retain of the `Signal` core may deinitialize.
201- disposable. dispose ( )
192+ tryToDisposeSilentlyIfQualified ( unlocking: stateLock)
202193 }
203194 } else {
204195 stateLock. unlock ( )
@@ -212,9 +203,7 @@ public final class Signal<Value, Error: Swift.Error> {
212203 /// method as a result of a false positive `terminating` check is permitted.
213204 ///
214205 /// - precondition: `stateLock` must not be acquired by the caller.
215- ///
216- /// - returns: `.shouldDispose` if the attempt succeeds. `.none` otherwise.
217- private func tryToCommitTermination( ) -> OperationResult {
206+ private func tryToCommitTermination( ) {
218207 // Acquire `stateLock`. If the termination has still not yet been
219208 // handled, take it over and bump the status to `terminated`.
220209 stateLock. lock ( )
@@ -226,20 +215,19 @@ public final class Signal<Value, Error: Swift.Error> {
226215 state = . terminated
227216 stateLock. unlock ( )
228217
229- defer { sendLock. unlock ( ) }
230-
231218 if let event = terminationKind. materialize ( ) {
232219 for observer in observers {
233220 observer. action ( event)
234221 }
235222 }
236223
237- return . shouldDispose
224+ sendLock. unlock ( )
225+ disposable. dispose ( )
226+ return
238227 }
239228 }
240229
241230 stateLock. unlock ( )
242- return . none
243231 }
244232
245233 /// Try to dispose of the signal silently if the `Signal` has deinitialized and
@@ -250,27 +238,31 @@ public final class Signal<Value, Error: Swift.Error> {
250238 ///
251239 /// - precondition: `stateLock` must have been acquired by the caller.
252240 ///
253- /// - returns: `.shouldDispose` if the signal has terminated immediately.
254- /// `.shouldTryCommitTermination` if the signal has transitioned to
255- /// `terminating` state, and the caller should try to commit the
256- /// termination with `tryToCommitTermination`. `.none` otherwise.
257- private func tryToDisposeSilentlyIfQualified( ) -> OperationResult {
241+ /// - parameters:
242+ /// - stateLock: The `stateLock` acquired by the caller.
243+ private func tryToDisposeSilentlyIfQualified( unlocking stateLock: Lock ) {
258244 assert ( !stateLock. try ( ) , " Calling `unconditionallyTerminate` without acquiring `stateLock`. " )
259245
260246 if case let . alive( observers, true ) = state, observers. isEmpty {
261247 // Transition to `terminated` directly only if there is no event delivery
262248 // on going.
263249 if sendLock. try ( ) {
264- defer { sendLock. unlock ( ) }
265250 self . state = . terminated
266- return . shouldDispose
251+ stateLock. unlock ( )
252+ sendLock. unlock ( )
253+
254+ disposable. dispose ( )
255+ return
267256 }
268257
269258 self . state = . terminating( Bag ( ) , . silent)
270- return . shouldTryCommitTermination
259+ stateLock. unlock ( )
260+
261+ tryToCommitTermination ( )
262+ return
271263 }
272264
273- return . none
265+ stateLock . unlock ( )
274266 }
275267
276268 /// Acknowledge the deinitialization of the `Signal`.
@@ -283,17 +275,7 @@ public final class Signal<Value, Error: Swift.Error> {
283275 }
284276
285277 // Attempt to start the disposal of the signal if it has no active observer.
286- var result = tryToDisposeSilentlyIfQualified ( )
287-
288- stateLock. unlock ( )
289-
290- if result == . shouldTryCommitTermination {
291- result = tryToCommitTermination ( )
292- }
293-
294- if result == . shouldDispose {
295- disposable. dispose ( )
296- }
278+ tryToDisposeSilentlyIfQualified ( unlocking: stateLock)
297279 }
298280
299281 deinit {
0 commit comments