@@ -21,6 +21,7 @@ const _INVALID_HANDLE_VALUE = ^uintptr(0)
21
21
const (
22
22
netpollSourceReady = iota + 1
23
23
netpollSourceBreak
24
+ netpollSourceTimer
24
25
)
25
26
26
27
const (
@@ -148,31 +149,45 @@ func netpollBreak() {
148
149
// delay == 0: does not block, just polls
149
150
// delay > 0: block for up to that many nanoseconds
150
151
func netpoll (delay int64 ) (gList , int32 ) {
152
+ if iocphandle == _INVALID_HANDLE_VALUE {
153
+ return gList {}, 0
154
+ }
155
+
151
156
var entries [64 ]overlappedEntry
152
- var wait , n , i uint32
153
- var errno int32
157
+ var wait uint32
154
158
var toRun gList
155
-
156
159
mp := getg ().m
157
160
158
- if iocphandle == _INVALID_HANDLE_VALUE {
159
- return gList {}, 0
161
+ if delay >= 1e15 {
162
+ // An arbitrary cap on how long to wait for a timer.
163
+ // 1e15 ns == ~11.5 days.
164
+ delay = 1e15
165
+ }
166
+
167
+ if delay > 0 && mp .waitIocpHandle != 0 {
168
+ // GetQueuedCompletionStatusEx doesn't use a high resolution timer internally,
169
+ // so we use a separate higher resolution timer associated with a wait completion
170
+ // packet to wake up the poller. Note that the completion packet can be delivered
171
+ // to another thread, and the Go scheduler expects netpoll to only block up to delay,
172
+ // so we still need to use a timeout with GetQueuedCompletionStatusEx.
173
+ // TODO: Improve the Go scheduler to support non-blocking timers.
174
+ signaled := netpollQueueTimer (delay )
175
+ if signaled {
176
+ // There is a small window between the SetWaitableTimer and the NtAssociateWaitCompletionPacket
177
+ // where the timer can expire. We can return immediately in this case.
178
+ return gList {}, 0
179
+ }
160
180
}
161
181
if delay < 0 {
162
182
wait = _INFINITE
163
183
} else if delay == 0 {
164
184
wait = 0
165
185
} else if delay < 1e6 {
166
186
wait = 1
167
- } else if delay < 1e15 {
168
- wait = uint32 (delay / 1e6 )
169
187
} else {
170
- // An arbitrary cap on how long to wait for a timer.
171
- // 1e9 ms == ~11.5 days.
172
- wait = 1e9
188
+ wait = uint32 (delay / 1e6 )
173
189
}
174
-
175
- n = uint32 (len (entries ) / int (gomaxprocs ))
190
+ n := len (entries ) / int (gomaxprocs )
176
191
if n < 8 {
177
192
n = 8
178
193
}
@@ -181,7 +196,7 @@ func netpoll(delay int64) (gList, int32) {
181
196
}
182
197
if stdcall6 (_GetQueuedCompletionStatusEx , iocphandle , uintptr (unsafe .Pointer (& entries [0 ])), uintptr (n ), uintptr (unsafe .Pointer (& n )), uintptr (wait ), 0 ) == 0 {
183
198
mp .blocked = false
184
- errno = int32 ( getlasterror () )
199
+ errno := getlasterror ()
185
200
if errno == _WAIT_TIMEOUT {
186
201
return gList {}, 0
187
202
}
@@ -190,7 +205,7 @@ func netpoll(delay int64) (gList, int32) {
190
205
}
191
206
mp .blocked = false
192
207
delta := int32 (0 )
193
- for i = 0 ; i < n ; i ++ {
208
+ for i : = 0 ; i < n ; i ++ {
194
209
e := & entries [i ]
195
210
switch unpackNetpollSource (e .key ) {
196
211
case netpollSourceReady :
@@ -212,10 +227,58 @@ func netpoll(delay int64) (gList, int32) {
212
227
// Forward the notification to the blocked poller.
213
228
netpollBreak ()
214
229
}
230
+ case netpollSourceTimer :
231
+ // TODO: We could avoid calling NtCancelWaitCompletionPacket for expired wait completion packets.
215
232
default :
216
233
println ("runtime: GetQueuedCompletionStatusEx returned net_op with invalid key=" , e .key )
217
234
throw ("runtime: netpoll failed" )
218
235
}
219
236
}
220
237
return toRun , delta
221
238
}
239
+
240
+ // netpollQueueTimer queues a timer to wake up the poller after the given delay.
241
+ // It returns true if the timer expired during this call.
242
+ func netpollQueueTimer (delay int64 ) (signaled bool ) {
243
+ const (
244
+ STATUS_SUCCESS = 0x00000000
245
+ STATUS_PENDING = 0x00000103
246
+ STATUS_CANCELLED = 0xC0000120
247
+ )
248
+ mp := getg ().m
249
+ // A wait completion packet can only be associated with one timer at a time,
250
+ // so we need to cancel the previous one if it exists. This wouldn't be necessary
251
+ // if the poller would only be woken up by the timer, in which case the association
252
+ // would be automatically cancelled, but it can also be woken up by other events,
253
+ // such as a netpollBreak, so we can get to this point with a timer that hasn't
254
+ // expired yet. In this case, the completion packet can still be picked up by
255
+ // another thread, so defer the cancellation until it is really necessary.
256
+ errno := stdcall2 (_NtCancelWaitCompletionPacket , mp .waitIocpHandle , 1 )
257
+ switch errno {
258
+ case STATUS_CANCELLED :
259
+ // STATUS_CANCELLED is returned when the associated timer has already expired,
260
+ // in which automatically cancels the wait completion packet.
261
+ fallthrough
262
+ case STATUS_SUCCESS :
263
+ dt := - delay / 100 // relative sleep (negative), 100ns units
264
+ if stdcall6 (_SetWaitableTimer , mp .waitIocpTimer , uintptr (unsafe .Pointer (& dt )), 0 , 0 , 0 , 0 ) == 0 {
265
+ println ("runtime: SetWaitableTimer failed; errno=" , getlasterror ())
266
+ throw ("runtime: netpoll failed" )
267
+ }
268
+ key := packNetpollKey (netpollSourceTimer , nil )
269
+ if errno := stdcall8 (_NtAssociateWaitCompletionPacket , mp .waitIocpHandle , iocphandle , mp .waitIocpTimer , key , 0 , 0 , 0 , uintptr (unsafe .Pointer (& signaled ))); errno != 0 {
270
+ println ("runtime: NtAssociateWaitCompletionPacket failed; errno=" , errno )
271
+ throw ("runtime: netpoll failed" )
272
+ }
273
+ case STATUS_PENDING :
274
+ // STATUS_PENDING is returned if the wait operation can't be cancelled yet.
275
+ // This can happen if this thread was woken up by another event, such as a netpollBreak,
276
+ // and the timer expired just while calling NtCancelWaitCompletionPacket, in which case
277
+ // this call fails to cancel the association to avoid a race condition.
278
+ // This is a rare case, so we can just avoid using the high resolution timer this time.
279
+ default :
280
+ println ("runtime: NtCancelWaitCompletionPacket failed; errno=" , errno )
281
+ throw ("runtime: netpoll failed" )
282
+ }
283
+ return signaled
284
+ }
0 commit comments