Skip to content

Commit 1a94e80

Browse files
authored
Merge pull request #891 from z2oh/revert-866-6.2-pipes
Revert "IO: switch Win32 pipes to `PIPE_WAIT` and use a sentinel byte"
2 parents d1d14d9 + 9e1b3bb commit 1a94e80

File tree

3 files changed

+17
-52
lines changed

3 files changed

+17
-52
lines changed

src/event/event_windows.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,6 @@ _dispatch_pipe_monitor_thread(void *context)
277277
char cBuffer[1];
278278
DWORD dwNumberOfBytesTransferred;
279279
OVERLAPPED ov = {0};
280-
// Block on a 0-byte read; this will only resume when data is
281-
// available in the pipe. The pipe must be PIPE_WAIT or this thread
282-
// will spin.
283280
BOOL bSuccess = ReadFile(hPipe, cBuffer, /* nNumberOfBytesToRead */ 0,
284281
&dwNumberOfBytesTransferred, &ov);
285282
DWORD dwBytesAvailable;

src/io.c

Lines changed: 16 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
// 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.
1466+
// Try to make writing nonblocking, although pipes not coming
1467+
// from Foundation.Pipe may not have FILE_WRITE_ATTRIBUTES.
14691468
DWORD dwPipeMode = 0;
14701469
if (GetNamedPipeHandleState((HANDLE)fd, &dwPipeMode, NULL,
1471-
NULL, NULL, NULL, 0) && !(dwPipeMode & PIPE_WAIT)) {
1472-
dwPipeMode |= PIPE_WAIT;
1470+
NULL, NULL, NULL, 0) && !(dwPipeMode & PIPE_NOWAIT)) {
1471+
dwPipeMode |= PIPE_NOWAIT;
14731472
if (!SetNamedPipeHandleState((HANDLE)fd, &dwPipeMode,
14741473
NULL, NULL)) {
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",
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",
14801480
fd_entry);
14811481
}
14821482
}
@@ -2548,40 +2548,13 @@ _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 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);
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);
25842556
}
2557+
len = MIN(len, fpli.OutboundQuota);
25852558
}
25862559

25872560
OVERLAPPED ovlOverlapped = {};

tests/dispatch_io_pipe.c

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -404,12 +404,7 @@ test_dispatch_write(int kind, int delay)
404404
dispatch_group_t g = dispatch_group_create();
405405
dispatch_group_enter(g);
406406

407-
// The libdispatch implementation writes at most bufsize-1 bytes
408-
// before requiring a reader to start making progress. Because
409-
// these tests operate serially, the reader will not make progress
410-
// until the write finishes, and a write of >= bufsize will not
411-
// finish until the reader starts draining the pipe.
412-
const size_t bufsize = test_get_pipe_buffer_size(kind) - 1;
407+
const size_t bufsize = test_get_pipe_buffer_size(kind);
413408

414409
char *buf = calloc(bufsize, 1);
415410
assert(buf);

0 commit comments

Comments
 (0)