@@ -1463,20 +1463,20 @@ _dispatch_fd_entry_create_with_fd(dispatch_fd_t fd, uintptr_t hash)
14631463 int result = ioctlsocket ((SOCKET )fd , (long )FIONBIO , & value );
14641464 (void )dispatch_assume_zero (result );
14651465 } else {
1466- // Try to make writing nonblocking, although pipes not coming
1467- // from Foundation.Pipe may not have FILE_WRITE_ATTRIBUTES.
1466+ // The _dispatch_pipe_monitor_thread expects pipes to be
1467+ // PIPE_WAIT and exploits this assumption by using a blocking
1468+ // 0-byte read as a synchronization mechanism.
14681469 DWORD dwPipeMode = 0 ;
14691470 if (GetNamedPipeHandleState ((HANDLE )fd , & dwPipeMode , NULL ,
1470- NULL , NULL , NULL , 0 ) && !(dwPipeMode & PIPE_NOWAIT )) {
1471- dwPipeMode |= PIPE_NOWAIT ;
1471+ NULL , NULL , NULL , 0 ) && !(dwPipeMode & PIPE_WAIT )) {
1472+ dwPipeMode |= PIPE_WAIT ;
14721473 if (!SetNamedPipeHandleState ((HANDLE )fd , & dwPipeMode ,
14731474 NULL , NULL )) {
1474- // We may end up blocking on subsequent writes, but we
1475- // don't have a good alternative.
1476- // The WriteQuotaAvailable from NtQueryInformationFile
1477- // erroneously returns 0 when there is a blocking read
1478- // on the other end of the pipe.
1479- _dispatch_fd_entry_debug ("failed to set PIPE_NOWAIT" ,
1475+ // If setting the pipe to PIPE_WAIT fails, the
1476+ // monitoring thread will spin constantly, saturating
1477+ // a core, which is undesirable but non-fatal.
1478+ // The semantics will still be correct in this case.
1479+ _dispatch_fd_entry_debug ("failed to set PIPE_WAIT" ,
14801480 fd_entry );
14811481 }
14821482 }
@@ -2548,13 +2548,40 @@ _dispatch_operation_perform(dispatch_operation_t op)
25482548 NTSTATUS status = _dispatch_NtQueryInformationFile (hFile ,
25492549 & iosb , & fpli , sizeof (fpli ), FilePipeLocalInformation );
25502550 if (NT_SUCCESS (status )) {
2551- // WriteQuotaAvailable is unreliable in the presence
2552- // of a blocking reader, when it can return zero, so only
2553- // account for it otherwise
2554- if (fpli .WriteQuotaAvailable > 0 ) {
2555- len = MIN (len , fpli .WriteQuotaAvailable );
2551+ // WriteQuotaAvailable is the free space in the output buffer
2552+ // that has not already been reserved for reading. In other words,
2553+ // WriteQuotaAvailable =
2554+ // OutboundQuota - WriteQuotaUsed - QueuedReadSize.
2555+ // It is not documented that QueuedReadSize is part of this
2556+ // calculation, but this behavior has been observed experimentally.
2557+ // Unfortunately, this means that it is not possible to distinguish
2558+ // between a full output buffer and a reader blocked waiting for a
2559+ // full buffer's worth of data. This is a problem because if the
2560+ // output buffer is full and no reader is waiting for data, then
2561+ // attempting to write to the buffer of a PIPE_WAIT, non-
2562+ // overlapped I/O pipe will block the dispatch queue thread.
2563+ //
2564+ // In order to work around this idiosyncrasy, we bound the size of
2565+ // the write to be OutboundQuota - 1. This affords us a sentinel value
2566+ // in WriteQuotaAvailable that can be used to detect if a reader is
2567+ // making progress or not.
2568+ // WriteQuotaAvailable = 0 => a reader is blocked waiting for data.
2569+ // WriteQuotaAvailable = 1 => the pipe has been written to, but no
2570+ // reader is making progress.
2571+ // When we detect that WriteQuotaAvailable == 1, we write 0 bytes to
2572+ // avoid blocking the dispatch queue thread.
2573+ if (fpli .WriteQuotaAvailable == 0 ) {
2574+ // This condition can only occur when we have a reader blocked
2575+ // waiting for data on the pipe. In this case, write a full
2576+ // buffer's worth of data (less one byte to preserve this
2577+ // sentinel value of WriteQuotaAvailable == 0).
2578+ len = MIN (len , fpli .OutboundQuota - 1 );
2579+ } else {
2580+ // Subtract 1 from WriteQuotaAvailable to ensure we do not fill
2581+ // the pipe and preserve the sentinel value of
2582+ // WriteQuotaAvailable == 1.
2583+ len = MIN (len , fpli .WriteQuotaAvailable - 1 );
25562584 }
2557- len = MIN (len , fpli .OutboundQuota );
25582585 }
25592586
25602587 OVERLAPPED ovlOverlapped = {};
0 commit comments