@@ -157,6 +157,7 @@ func (o *operation) InitMsg(p []byte, oob []byte) {
157
157
// It supports both synchronous and asynchronous IO.
158
158
func execIO (o * operation , submit func (o * operation ) error ) (int , error ) {
159
159
fd := o .fd
160
+ fd .initIO ()
160
161
// Notify runtime netpoll about starting IO.
161
162
err := fd .pd .prepare (int (o .mode ), fd .isFile )
162
163
if err != nil {
@@ -297,8 +298,13 @@ type FD struct {
297
298
// The kind of this file.
298
299
kind fileKind
299
300
300
- // Whether FILE_FLAG_OVERLAPPED was not set when opening the file
301
+ // Whether FILE_FLAG_OVERLAPPED was not set when opening the file.
301
302
isBlocking bool
303
+
304
+ // Initialization parameters.
305
+ initIOOnce sync.Once
306
+ initIOErr error // only used in the net package
307
+ initPollable bool // value passed to [FD.Init]
302
308
}
303
309
304
310
// setOffset sets the offset fields of the overlapped object
@@ -336,7 +342,51 @@ const (
336
342
)
337
343
338
344
// logInitFD is set by tests to enable file descriptor initialization logging.
339
- var logInitFD func (net string , fd * FD , err error )
345
+ var logInitFD func (net int , fd * FD , err error )
346
+
347
+ func (fd * FD ) initIO () error {
348
+ fd .initIOOnce .Do (func () {
349
+ if fd .initPollable {
350
+ // The runtime poller will ignore I/O completion
351
+ // notifications not initiated by this package,
352
+ // so it is safe to add handles owned by the caller.
353
+ //
354
+ // If we could not add the handle to the runtime poller,
355
+ // assume the handle hasn't been opened for overlapped I/O.
356
+ fd .initIOErr = fd .pd .init (fd )
357
+ if fd .initIOErr != nil {
358
+ fd .initPollable = false
359
+ }
360
+ }
361
+ if logInitFD != nil {
362
+ logInitFD (int (fd .kind ), fd , fd .initIOErr )
363
+ }
364
+ if ! fd .initPollable {
365
+ // Handle opened for overlapped I/O (aka non-blocking) that are not added
366
+ // to the runtime poller need special handling when reading and writing.
367
+ var info windows.FILE_MODE_INFORMATION
368
+ if err := windows .NtQueryInformationFile (fd .Sysfd , & windows.IO_STATUS_BLOCK {}, uintptr (unsafe .Pointer (& info )), uint32 (unsafe .Sizeof (info )), windows .FileModeInformation ); err == nil {
369
+ fd .isBlocking = info .Mode & (windows .FILE_SYNCHRONOUS_IO_ALERT | windows .FILE_SYNCHRONOUS_IO_NONALERT ) != 0
370
+ } else {
371
+ // If we fail to get the file mode information, assume the file is blocking.
372
+ fd .isBlocking = true
373
+ }
374
+ } else {
375
+ fd .rop .runtimeCtx = fd .pd .runtimeCtx
376
+ fd .wop .runtimeCtx = fd .pd .runtimeCtx
377
+ if fd .kind != kindNet || socketCanUseSetFileCompletionNotificationModes {
378
+ // Non-socket handles can use SetFileCompletionNotificationModes without problems.
379
+ err := syscall .SetFileCompletionNotificationModes (fd .Sysfd ,
380
+ syscall .FILE_SKIP_SET_EVENT_ON_HANDLE | syscall .FILE_SKIP_COMPLETION_PORT_ON_SUCCESS ,
381
+ )
382
+ if err == nil {
383
+ fd .skipSyncNotif = true
384
+ }
385
+ }
386
+ }
387
+ })
388
+ return fd .initIOErr
389
+ }
340
390
341
391
// Init initializes the FD. The Sysfd field should already be set.
342
392
// This can be called multiple times on a single FD.
@@ -349,63 +399,42 @@ func (fd *FD) Init(net string, pollable bool) error {
349
399
}
350
400
351
401
switch net {
352
- case "file" , "dir" :
402
+ case "file" :
353
403
fd .kind = kindFile
354
404
case "console" :
355
405
fd .kind = kindConsole
356
406
case "pipe" :
357
407
fd .kind = kindPipe
358
- case "tcp" , "tcp4" , "tcp6" ,
359
- "udp" , "udp4" , "udp6" ,
360
- "ip" , "ip4" , "ip6" ,
361
- "unix" , "unixgram" , "unixpacket" :
362
- fd .kind = kindNet
363
408
default :
364
- return errors .New ("internal error: unknown network type " + net )
409
+ // We don't actually care about the various network types.
410
+ fd .kind = kindNet
365
411
}
366
412
fd .isFile = fd .kind != kindNet
413
+ fd .initPollable = pollable
367
414
fd .rop .mode = 'r'
368
415
fd .wop .mode = 'w'
369
416
fd .rop .fd = fd
370
417
fd .wop .fd = fd
371
418
372
- var err error
373
- if pollable {
374
- // Note that the runtime poller will ignore I/O completion
375
- // notifications not initiated by this package,
376
- // so it is safe to add handles owned by the caller.
377
- //
378
- // If we could not add the handle to the runtime poller,
379
- // assume the handle hasn't been opened for overlapped I/O.
380
- err = fd .pd .init (fd )
381
- pollable = err == nil
382
- }
383
- if logInitFD != nil {
384
- logInitFD (net , fd , err )
385
- }
386
- if ! pollable {
387
- // Handle opened for overlapped I/O (aka non-blocking) that are not added
388
- // to the runtime poller need special handling when reading and writing.
389
- var info windows.FILE_MODE_INFORMATION
390
- if err := windows .NtQueryInformationFile (fd .Sysfd , & windows.IO_STATUS_BLOCK {}, uintptr (unsafe .Pointer (& info )), uint32 (unsafe .Sizeof (info )), windows .FileModeInformation ); err == nil {
391
- fd .isBlocking = info .Mode & (windows .FILE_SYNCHRONOUS_IO_ALERT | windows .FILE_SYNCHRONOUS_IO_NONALERT ) != 0
392
- } else {
393
- // If we fail to get the file mode information, assume the file is blocking.
394
- fd .isBlocking = true
395
- }
396
- return err
397
- }
398
- if fd .kind != kindNet || socketCanUseSetFileCompletionNotificationModes {
399
- // Non-socket handles can use SetFileCompletionNotificationModes without problems.
400
- err := syscall .SetFileCompletionNotificationModes (fd .Sysfd ,
401
- syscall .FILE_SKIP_SET_EVENT_ON_HANDLE | syscall .FILE_SKIP_COMPLETION_PORT_ON_SUCCESS ,
402
- )
403
- if err == nil {
404
- fd .skipSyncNotif = true
419
+ // A file handle (and its duplicated handles) can only be associated
420
+ // with one IOCP. A new association will fail if the handle is already
421
+ // associated. Defer the association until the first I/O operation so that
422
+ // overlapped handles passed in os.NewFile have a chance to be used
423
+ // with an external IOCP. This is common case, for example, when calling
424
+ // os.NewFile on a handle just to pass it to a exec.Command standard
425
+ // input/output/error. If the association fails, the I/O operations
426
+ // will be performed synchronously.
427
+ if fd .kind == kindNet {
428
+ // The net package is the only consumer that requires overlapped
429
+ // handles and that cares about handle IOCP association errors.
430
+ // We can should do the IOCP association here.
431
+ return fd .initIO ()
432
+ } else {
433
+ if logInitFD != nil {
434
+ // For testing.
435
+ logInitFD (int (fd .kind ), fd , nil )
405
436
}
406
437
}
407
- fd .rop .runtimeCtx = fd .pd .runtimeCtx
408
- fd .wop .runtimeCtx = fd .pd .runtimeCtx
409
438
return nil
410
439
}
411
440
0 commit comments