@@ -2518,13 +2518,38 @@ _dispatch_operation_perform(dispatch_operation_t op)
2518
2518
NTSTATUS status = _dispatch_NtQueryInformationFile (hFile ,
2519
2519
& iosb , & fpli , sizeof (fpli ), FilePipeLocalInformation );
2520
2520
if (NT_SUCCESS (status )) {
2521
- // WriteQuotaAvailable is unreliable in the presence
2522
- // of a blocking reader, when it can return zero, so only
2523
- // account for it otherwise
2524
- if (fpli .WriteQuotaAvailable > 0 ) {
2525
- len = MIN (len , fpli .WriteQuotaAvailable );
2521
+ // WriteQuotaAvailable is the free space in the output buffer
2522
+ // that has not already been reserved for reading. In other words,
2523
+ // WriteQuotaAvailable =
2524
+ // OutboundQuota - WriteQuotaUsed - QueuedReadSize.
2525
+ // Unfortunately, this means that it is not possible to distinguish
2526
+ // between a full output buffer and a reader blocked waiting for a
2527
+ // full buffer's worth of data. This is a problem because if the
2528
+ // output buffer is full and no reader is waiting for data, then
2529
+ // attempting to write to the buffer of a PIPE_WAIT, non-
2530
+ // overlapped I/O pipe will block the dispatch queue thread.
2531
+ //
2532
+ // In order to work around this idiosynchrasy, we bound the size of
2533
+ // the write to be OutboundQuota - 1. This affords us a sentinel value
2534
+ // in WriteQuotaAvailable that can be used to detect if a reader is
2535
+ // making progress or not.
2536
+ // WriteQuotaAvailable = 0 => a reader is blocked waiting for data.
2537
+ // WriteQuotaAvailable = 1 => the pipe has been written to, but no
2538
+ // reader is making progress.
2539
+ // When we detect that WriteQuotaAvailable == 1, we write 0 bytes to
2540
+ // avoid blocking the dispatch queue thread.
2541
+ if (fpli .WriteQuotaAvailable == 1 ) {
2542
+ len = 0 ;
2543
+ } else if (fpli .WriteQuotaAvailable > 1 ) {
2544
+ // Subtract 1 from WriteQuotaAvailable to ensure we do not fill
2545
+ // the pipe and preserve the sentinel value of
2546
+ // WriteQuotaAvailable == 1.
2547
+ len = MIN (len , fpli .WriteQuotaAvailable - 1 );
2526
2548
}
2527
- len = MIN (len , fpli .OutboundQuota );
2549
+
2550
+ // Always bound the write size by the OutboundQuota - 1 to preserve
2551
+ // the sentinel value of WriteQuotaAvailable == 1.
2552
+ len = MIN (len , fpli .OutboundQuota - 1 );
2528
2553
}
2529
2554
2530
2555
OVERLAPPED ovlOverlapped = {};
0 commit comments