@@ -40,58 +40,113 @@ public final class Signal<Value, Error: Swift.Error> {
4040 public init ( _ generator: ( Observer ) -> Disposable ? ) {
4141 state = Atomic ( SignalState ( ) )
4242
43+ /// Holds the final signal state captured by an `interrupted` event. If it
44+ /// is set, the Signal should interrupt as soon as possible. Implicitly
45+ /// protected by `state` and `sendLock`.
46+ var interruptedState : SignalState < Value , Error > ? = nil
47+
48+ /// Used to track if the signal has terminated. Protected by `sendLock`.
49+ var terminated = false
50+
4351 /// Used to ensure that events are serialized during delivery to observers.
4452 let sendLock = NSLock ( )
4553 sendLock. name = " org.reactivecocoa.ReactiveSwift.Signal "
4654
47- /// When set to `true`, the Signal should interrupt as soon as possible.
48- let interrupted = Atomic ( false )
49-
5055 let observer = Observer { [ weak self] event in
5156 guard let signal = self else {
5257 return
5358 }
5459
55- func interrupt( ) {
56- if let state = signal. state. swap ( nil ) {
57- for observer in state. observers {
58- observer. sendInterrupted ( )
59- }
60+ @inline ( __always)
61+ func interrupt( _ observers: Bag < Observer > ) {
62+ for observer in observers {
63+ observer. sendInterrupted ( )
6064 }
65+ terminated = true
66+ interruptedState = nil
6167 }
6268
6369 if case . interrupted = event {
64- // Normally we disallow recursive events, but `interrupted` is
65- // kind of a special snowflake, since it can inadvertently be
66- // sent by downstream consumers.
70+ // Recursive events are generally disallowed. But `interrupted` is kind
71+ // of a special snowflake, since it can inadvertently be sent by
72+ // downstream consumers.
6773 //
68- // So we'll flag Interrupted events specially, and if it
69- // happened to occur while we're sending something else, we'll
70- // wait to deliver it.
71- interrupted. value = true
72-
73- if sendLock. try ( ) {
74- interrupt ( )
75- sendLock. unlock ( )
76-
77- signal. generatorDisposable? . dispose ( )
74+ // So we would treat `interrupted` events specially. If it happens
75+ // to occur while the `sendLock` is acquired, the observer call-out and
76+ // the disposal would be delegated to the current sender, or
77+ // occasionally one of the senders waiting on `sendLock`.
78+ if let state = signal. state. swap ( nil ) {
79+ // Writes to `interruptedState` are implicitly synchronized. So we do
80+ // not need to guard it with locks.
81+ //
82+ // Specifically, senders serialized by `sendLock` can react to and
83+ // clear `interruptedState` only if they see the write made below.
84+ // The write can happen only once, since `state` being swapped with
85+ // `nil` is a point of no return.
86+ //
87+ // Even in the case that both a previous sender and its successor see
88+ // the write (the `interruptedState` check before & after the unlock
89+ // of `sendLock`), the senders are still bound to the `sendLock`.
90+ // So whichever sender loses the battle of acquring `sendLock` is
91+ // guaranteed to be blocked.
92+ interruptedState = state
93+
94+ if sendLock. try ( ) {
95+ if !terminated, let state = interruptedState {
96+ interrupt ( state. observers)
97+ }
98+ sendLock. unlock ( )
99+ signal. generatorDisposable? . dispose ( )
100+ }
78101 }
79102 } else {
80- if let state = ( event. isTerminating ? signal. state. swap ( nil ) : signal. state. value) {
103+ let isTerminating = event. isTerminating
104+
105+ if let observers = ( isTerminating ? signal. state. swap ( nil ) ? . observers : signal. state. value? . observers) {
106+ var shouldDispose = false
107+
81108 sendLock. lock ( )
82109
83- for observer in state. observers {
84- observer. action ( event)
85- }
110+ if !terminated {
111+ for observer in observers {
112+ observer. action ( event)
113+ }
114+
115+ // Check if a downstream consumer or a concurrent sender has
116+ // interrupted the signal.
117+ if !isTerminating, let state = interruptedState {
118+ interrupt ( state. observers)
119+ shouldDispose = true
120+ }
86121
87- let shouldInterrupt = !event. isTerminating && interrupted. value
88- if shouldInterrupt {
89- interrupt ( )
122+ if isTerminating {
123+ terminated = true
124+ shouldDispose = true
125+ }
90126 }
91127
92128 sendLock. unlock ( )
93129
94- if event. isTerminating || shouldInterrupt {
130+ // Based on the implicit memory order, any updates to the
131+ // `interruptedState` should always be visible after `sendLock` is
132+ // released. So we check it again and handle the interruption if
133+ // it has not been taken over.
134+ if !shouldDispose && !terminated && !isTerminating, let state = interruptedState {
135+ sendLock. lock ( )
136+
137+ // `terminated` before acquring the lock could be a false negative,
138+ // since it might race against other concurrent senders until the
139+ // lock acquisition above succeeds. So we have to check again if the
140+ // signal is really still alive.
141+ if !terminated {
142+ interrupt ( state. observers)
143+ shouldDispose = true
144+ }
145+
146+ sendLock. unlock ( )
147+ }
148+
149+ if shouldDispose {
95150 // Dispose only after notifying observers, so disposal
96151 // logic is consistently the last thing to run.
97152 signal. generatorDisposable? . dispose ( )
0 commit comments