@@ -2548,13 +2548,40 @@ _dispatch_operation_perform(dispatch_operation_t op)
2548
2548
NTSTATUS status = _dispatch_NtQueryInformationFile (hFile ,
2549
2549
& iosb , & fpli , sizeof (fpli ), FilePipeLocalInformation );
2550
2550
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 );
2556
2584
}
2557
- len = MIN (len , fpli .OutboundQuota );
2558
2585
}
2559
2586
2560
2587
OVERLAPPED ovlOverlapped = {};
0 commit comments