|
12 | 12 | // sigsend is called by the signal handler to queue a new signal.
|
13 | 13 | // signal_recv is called by the Go program to receive a newly queued signal.
|
14 | 14 | // Synchronization between sigsend and signal_recv is based on the sig.state
|
15 |
| -// variable. It can be in 3 states: sigIdle, sigReceiving and sigSending. |
| 15 | +// variable. It can be in 4 states: sigIdle, sigReceiving, sigSending and sigFixup. |
16 | 16 | // sigReceiving means that signal_recv is blocked on sig.Note and there are no
|
17 | 17 | // new pending signals.
|
18 | 18 | // sigSending means that sig.mask *may* contain new pending signals,
|
19 | 19 | // signal_recv can't be blocked in this state.
|
20 | 20 | // sigIdle means that there are no new pending signals and signal_recv is not blocked.
|
| 21 | +// sigFixup is a transient state that can only exist as a short |
| 22 | +// transition from sigReceiving and then on to sigIdle: it is |
| 23 | +// used to ensure the AllThreadsSyscall()'s mDoFixup() operation |
| 24 | +// occurs on the sleeping m, waiting to receive a signal. |
21 | 25 | // Transitions between states are done atomically with CAS.
|
22 | 26 | // When signal_recv is unblocked, it resets sig.Note and rechecks sig.mask.
|
23 | 27 | // If several sigsends and signal_recv execute concurrently, it can lead to
|
@@ -59,6 +63,7 @@ const (
|
59 | 63 | sigIdle = iota
|
60 | 64 | sigReceiving
|
61 | 65 | sigSending
|
| 66 | + sigFixup |
62 | 67 | )
|
63 | 68 |
|
64 | 69 | // sigsend delivers a signal from sighandler to the internal signal delivery queue.
|
@@ -112,13 +117,29 @@ Send:
|
112 | 117 | notewakeup(&sig.note)
|
113 | 118 | break Send
|
114 | 119 | }
|
| 120 | + case sigFixup: |
| 121 | + // nothing to do - we need to wait for sigIdle. |
| 122 | + osyield() |
115 | 123 | }
|
116 | 124 | }
|
117 | 125 |
|
118 | 126 | atomic.Xadd(&sig.delivering, -1)
|
119 | 127 | return true
|
120 | 128 | }
|
121 | 129 |
|
| 130 | +// sigRecvPrepareForFixup is used to temporarily wake up the |
| 131 | +// signal_recv() running thread while it is blocked waiting for the |
| 132 | +// arrival of a signal. If it causes the thread to wake up, the |
| 133 | +// sig.state travels through this sequence: sigReceiving -> sigFixup |
| 134 | +// -> sigIdle -> sigReceiving and resumes. (This is only called while |
| 135 | +// GC is disabled.) |
| 136 | +//go:nosplit |
| 137 | +func sigRecvPrepareForFixup() { |
| 138 | + if atomic.Cas(&sig.state, sigReceiving, sigFixup) { |
| 139 | + notewakeup(&sig.note) |
| 140 | + } |
| 141 | +} |
| 142 | + |
122 | 143 | // Called to receive the next queued signal.
|
123 | 144 | // Must only be called from a single goroutine at a time.
|
124 | 145 | //go:linkname signal_recv os/signal.signal_recv
|
@@ -146,7 +167,16 @@ func signal_recv() uint32 {
|
146 | 167 | }
|
147 | 168 | notetsleepg(&sig.note, -1)
|
148 | 169 | noteclear(&sig.note)
|
149 |
| - break Receive |
| 170 | + if !atomic.Cas(&sig.state, sigFixup, sigIdle) { |
| 171 | + break Receive |
| 172 | + } |
| 173 | + // Getting here, the code will |
| 174 | + // loop around again to sleep |
| 175 | + // in state sigReceiving. This |
| 176 | + // path is taken when |
| 177 | + // sigRecvPrepareForFixup() |
| 178 | + // has been called by another |
| 179 | + // thread. |
150 | 180 | }
|
151 | 181 | case sigSending:
|
152 | 182 | if atomic.Cas(&sig.state, sigSending, sigIdle) {
|
|
0 commit comments