@@ -41,8 +41,9 @@ public final class Signal<Value, Error: Swift.Error> {
4141 state = Atomic ( SignalState ( ) )
4242
4343 /// Holds the final signal state captured by an `interrupted` event. If it
44- /// is set, the Signal should interrupt as soon as possible.
45- let interruptedState = Atomic < SignalState < Value , Error > ? > ( nil )
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
4647
4748 /// Used to track if the signal has terminated. Protected by `sendLock`.
4849 var terminated = false
@@ -56,6 +57,15 @@ public final class Signal<Value, Error: Swift.Error> {
5657 return
5758 }
5859
60+ @inline ( __always)
61+ func interrupt( _ observers: Bag < Observer > ) {
62+ for observer in observers {
63+ observer. sendInterrupted ( )
64+ }
65+ terminated = true
66+ interruptedState = nil
67+ }
68+
5969 if case . interrupted = event {
6070 // Recursive events are generally disallowed. But `interrupted` is kind
6171 // of a special snowflake, since it can inadvertently be sent by
@@ -66,14 +76,24 @@ public final class Signal<Value, Error: Swift.Error> {
6676 // the disposal would be delegated to the current sender, or
6777 // occasionally one of the senders waiting on `sendLock`.
6878 if let state = signal. state. swap ( nil ) {
69- interruptedState. value = state
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
7093
7194 if sendLock. try ( ) {
72- if !terminated, let state = interruptedState. swap ( nil ) {
73- for observer in state. observers {
74- observer. sendInterrupted ( )
75- }
76- terminated = true
95+ if !terminated, let state = interruptedState {
96+ interrupt ( state. observers)
7797 }
7898 sendLock. unlock ( )
7999 signal. generatorDisposable? . dispose ( )
@@ -94,11 +114,8 @@ public final class Signal<Value, Error: Swift.Error> {
94114
95115 // Check if a downstream consumer or a concurrent sender has
96116 // interrupted the signal.
97- if !isTerminating, let state = interruptedState. swap ( nil ) {
98- for observer in state. observers {
99- observer. sendInterrupted ( )
100- }
101- terminated = true
117+ if !isTerminating, let state = interruptedState {
118+ interrupt ( state. observers)
102119 shouldDispose = true
103120 }
104121
@@ -114,15 +131,12 @@ public final class Signal<Value, Error: Swift.Error> {
114131 // `interruptedState` should always be visible after `sendLock` is
115132 // released. So we check it again and handle the interruption if
116133 // it has not been taken over.
117- if !shouldDispose && !terminated && !isTerminating, let state = interruptedState. swap ( nil ) {
134+ if !shouldDispose && !terminated && !isTerminating, let state = interruptedState {
118135 sendLock. lock ( )
119136
120137 if !terminated {
121- for observer in state. observers {
122- observer. sendInterrupted ( )
123- }
138+ interrupt ( state. observers)
124139 shouldDispose = true
125- terminated = true
126140 }
127141
128142 sendLock. unlock ( )
0 commit comments