Skip to content

Commit df9066b

Browse files
committed
Revert the terminating check fast path. Remove OperationResult.
1 parent a407b6e commit df9066b

File tree

1 file changed

+44
-62
lines changed

1 file changed

+44
-62
lines changed

Sources/Signal.swift

Lines changed: 44 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)