Skip to content

Commit 6e47851

Browse files
committed
Bound a pipe as well as a socket: a channel here is a pipe
⚠️ `WSAPoll' takes sockets and nothing else, so `kal_timeout_read' upon a pipe reported `kal_err_not_supported' --- and `openkal.process' makes a channel out of a pipe on this system. A C library above this implementation therefore reaches `poll' and `select' upon one, and openkal-musl's network probe reported it on the row that builds for this system: FAIL: select reports the read end ready (errno=38) A `select' that refuses a pipe makes every program waiting on a subprocess's output stop. ⭐ THREE ENQUIRIES, ONE PER KIND OF OBJECT, chosen by asking what the handle is: `WSAPoll' for a socket, `PeekNamedPipe' for a pipe, and always ready for a file, because a read from one does not wait. ⚠️ A socket also reports `FILE_TYPE_PIPE', so the socket enquiry is made FIRST and the file type only decides what a non-socket is. ⭐ `PeekNamedPipe' is the one NON-DESTRUCTIVE readiness enquiry in this whole ecosystem: it reports how many bytes are there and takes none, which is why this implementation needs no read-ahead where the port above it does. ⚠️ A closed writing end is READY and not an error --- the call then fails with `ERROR_BROKEN_PIPE' and the read that follows reports the end of input without waiting, which is what readiness asserts. Reporting the failure would make a program that reads until end-of-input wait for ever instead. What remains unbounded is a character device, and `kal_err_not_supported' is what timeout.h states for a resource an implementation does not cover. Measured here: 16 objects, exported surface complete at 88 names, no undefined symbol outside the permitted set, 50 declared names and none missing from a `.def'.
1 parent 3370889 commit 6e47851

3 files changed

Lines changed: 88 additions & 24 deletions

File tree

port/kernel32.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ HeapFree
3333
LoadLibraryW
3434
LocalFree
3535
MultiByteToWideChar
36+
PeekNamedPipe
3637
QueryPerformanceCounter
3738
QueryPerformanceFrequency
3839
ReadFile

src/timeout.cpp

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,30 @@
99
// socket would transfer without blocking, so a bounded read is a bounded wait
1010
// for readiness followed by the ordinary read.
1111
//
12-
// ⚠️⚠️ AND `WSAPoll' ANSWERS FOR SOCKETS AND FOR NOTHING ELSE, WHICH IS THE ONE
12+
// ⚠️⚠️ AND NO SINGLE CALL ANSWERS FOR EVERY RESOURCE HERE, WHICH IS THE ONE
1313
// PLACE THIS SYSTEM DIFFERS FROM THE OTHER TWO IN KIND RATHER THAN IN SPELLING.
1414
//
15-
// There, one call answers for every descriptor. Here a socket and a file are
16-
// different kinds of object and the readiness call takes only the first; a pipe
17-
// is asked with `PeekNamedPipe', a file is always ready, and a console has its
18-
// own enquiry. openkal.timeout's header anticipates exactly this: "AN
19-
// IMPLEMENTATION MAY PROVIDE THIS FOR SOME OF ITS RESOURCES AND NOT OTHERS, and
20-
// reports kal_err_not_supported for the rest. That is not the defect clause 6.4
21-
// describes."
15+
// There, one call answers for every descriptor. Here a socket, a pipe and a
16+
// file are different kinds of object: `WSAPoll' takes only the first, a pipe is
17+
// asked with `PeekNamedPipe', and a file is always ready because a read from
18+
// one does not wait. Three enquiries, one per kind, chosen by asking what the
19+
// handle is.
2220
//
23-
// ⇒ `kal_timeout_read' and `kal_timeout_write' upon a stream that is not a
24-
// socket report `kal_err_not_supported'. That is a stated answer a caller can
25-
// act upon --- and it is the honest one, because the alternative is to wait a
26-
// while and then attempt the transfer anyway, which would report `kal_err_again'
27-
// for a pipe that had data and block for one that did not.
21+
// ⭐ AND THE PIPE IS NOT OPTIONAL. `openkal.process' makes a channel out of a
22+
// pipe here, so a C library above this implementation reaches `poll' and
23+
// `select' upon one --- and a `select' that reported `kal_err_not_supported'
24+
// for a pipe would make every program that waits on a subprocess's output stop.
25+
// Measured: openkal-musl's own network probe, on the row that builds for this
26+
// system, reported `select reports the read end ready (errno=38)'.
27+
//
28+
// ⚠️ A SOCKET ALSO REPORTS `FILE_TYPE_PIPE', so the socket enquiry is made
29+
// FIRST and the file type only decides what a non-socket is.
30+
//
31+
// ⇒ What remains unbounded is a character device --- a console --- and
32+
// `kal_err_not_supported' is what this interface states for a resource an
33+
// implementation does not cover: "AN IMPLEMENTATION MAY PROVIDE THIS FOR SOME
34+
// OF ITS RESOURCES AND NOT OTHERS, and reports kal_err_not_supported for the
35+
// rest. That is not the defect clause 6.4 describes."
2836

2937
namespace {
3038

@@ -42,10 +50,10 @@ int bound_ms(kal_u64 ns) {
4250
return static_cast<int>(ms);
4351
}
4452

45-
// Whether this word names a socket, which is the question the note above makes
46-
// unavoidable. `getsockname' is the enquiry that answers it: upon a socket it
47-
// succeeds or fails for a reason of its own, and upon anything else this system
48-
// reports `WSAENOTSOCK'.
53+
// Whether this word names a socket, which is the first question because a
54+
// socket also reports `FILE_TYPE_PIPE'. `getsockname' is the enquiry that
55+
// answers it: upon a socket it succeeds or fails for a reason of its own, and
56+
// upon anything else this system reports `WSAENOTSOCK'.
4957
bool is_socket(SOCKET s) {
5058
auto* n = okw::net_or_null();
5159
if (n == nullptr) return false;
@@ -55,6 +63,17 @@ bool is_socket(SOCKET s) {
5563
return n->last_error() != okw::WSAENOTSOCK;
5664
}
5765

66+
enum class shape { socket, pipe, ready, none };
67+
68+
shape shape_of(SOCKET raw) {
69+
if (is_socket(raw)) return shape::socket;
70+
switch (GetFileType(reinterpret_cast<HANDLE>(raw))) {
71+
case FILE_TYPE_PIPE: return shape::pipe;
72+
case FILE_TYPE_DISK: return shape::ready; // a read from a file does not wait
73+
default: return shape::none; // a console, or nothing at all
74+
}
75+
}
76+
5877
// Waits for one socket. Reports kal_ok when it is ready, kal_err_again when the
5978
// bound expired, and a translated error otherwise.
6079
int await(SOCKET s, short events, kal_u64 ns) {
@@ -69,9 +88,39 @@ int await(SOCKET s, short events, kal_u64 ns) {
6988
return kal_ok;
7089
}
7190

91+
// Waits for a pipe to have bytes, without taking them.
92+
//
93+
// ⭐ `PeekNamedPipe' IS THE ONE NON-DESTRUCTIVE READINESS ENQUIRY IN THIS WHOLE
94+
// ECOSYSTEM, and it is why this implementation needs no read-ahead where the
95+
// port above it does. It reports how many bytes are there and takes none.
96+
//
97+
// ⚠️ A CLOSED WRITING END IS READY AND NOT AN ERROR. The call then fails with
98+
// `ERROR_BROKEN_PIPE', and a read that follows reports the end of input without
99+
// waiting --- which is what readiness asserts. Reporting the failure here would
100+
// make a program that reads until end-of-input wait for ever instead.
101+
int await_pipe(HANDLE h, kal_u64 ns) {
102+
const int ms = bound_ms(ns);
103+
kal_u64 waited = 0;
104+
for (;;) {
105+
DWORD available = 0;
106+
if (!PeekNamedPipe(h, nullptr, 0, nullptr, &available, nullptr)) {
107+
const DWORD e = GetLastError();
108+
if (e == ERROR_BROKEN_PIPE || e == ERROR_PIPE_NOT_CONNECTED) return kal_ok;
109+
return okw::translate_win32(e);
110+
}
111+
if (available > 0) return kal_ok;
112+
if (ms == 0) return kal_err_again;
113+
if (ms > 0 && waited >= static_cast<kal_u64>(ms)) return kal_err_again;
114+
// The interval this interface reports as its granularity, so the cost of
115+
// the loop is the number already stated rather than a second one.
116+
Sleep(1);
117+
waited += 1;
118+
}
119+
}
120+
72121
int await_stream(kal_stream s, short events, kal_u64 ns) {
73122
// ⚠️ ONE REASON TO REFUSE, AND NOT TWO. An earlier form answered a null or
74-
// invalid handle with `kal_err_invalid' and a valid non-socket with
123+
// invalid handle with `kal_err_invalid' and everything else with
75124
// `kal_err_not_supported', and the conformance suite reported both bounded
76125
// reads of the standard input as not holding: a run whose standard input is
77126
// not attached has a handle of zero, and the suite's list of admissible
@@ -80,13 +129,21 @@ int await_stream(kal_stream s, short events, kal_u64 ns) {
80129
// ⭐ The early return was answering a DIFFERENT QUESTION. "Is this handle
81130
// valid" is what the unbounded operation answers; what this interface
82131
// answers is whether this implementation can bound an operation upon this
83-
// resource, and the header sanctions exactly one refusal for that: "AN
84-
// IMPLEMENTATION MAY PROVIDE THIS FOR SOME OF ITS RESOURCES AND NOT OTHERS,
85-
// and reports kal_err_not_supported for the rest." A handle that is not a
86-
// socket is one of the rest, and zero is not a socket.
132+
// resource.
87133
const SOCKET raw = static_cast<SOCKET>(s.h);
88-
if (!is_socket(raw)) return kal_err_not_supported;
89-
return await(raw, events, ns);
134+
switch (shape_of(raw)) {
135+
case shape::socket: return await(raw, events, ns);
136+
case shape::pipe:
137+
// Writability is not enquired of: this system has no call that
138+
// reports whether a pipe would accept bytes without blocking, and a
139+
// write to one completes or reports. openkal-musl's okm_poll.c
140+
// records the same answer for the same reason.
141+
if (events == POLLWRNORM_) return kal_ok;
142+
return await_pipe(reinterpret_cast<HANDLE>(raw), ns);
143+
case shape::ready: return kal_ok;
144+
case shape::none: return kal_err_not_supported;
145+
}
146+
return kal_err_not_supported;
90147
}
91148

92149
} // namespace

src/win32.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ enum : DWORD {
190190
FILE_NAME_NORMALIZED = 0x0,
191191
VOLUME_NAME_DOS = 0x0,
192192
FILE_TYPE_DISK = 0x0001,
193+
FILE_TYPE_CHAR = 0x0002,
194+
FILE_TYPE_PIPE = 0x0003,
193195

194196
HANDLE_FLAG_INHERIT = 0x1,
195197
STARTF_USESTDHANDLES = 0x00000100u,
@@ -255,6 +257,10 @@ OKW_IMPORT BOOL OKW_API SetHandleInformation(HANDLE, DWORD, DWORD);
255257
// For kal_process_channel. The security attributes decide whether the ends are
256258
// inheritable, which is what makes one of them able to cross a spawn.
257259
OKW_IMPORT BOOL OKW_API CreatePipe(HANDLE*, HANDLE*, SECURITY_ATTRIBUTES*, DWORD);
260+
// How many bytes a pipe has without taking them, which is the one readiness
261+
// enquiry on this system that is not `WSAPoll'. src/timeout.cpp says why both
262+
// are needed.
263+
OKW_IMPORT BOOL OKW_API PeekNamedPipe(HANDLE, LPVOID, DWORD, DWORD*, DWORD*, DWORD*);
258264
OKW_IMPORT BOOL OKW_API GetConsoleMode(HANDLE, DWORD*);
259265
OKW_IMPORT BOOL OKW_API SetConsoleMode(HANDLE, DWORD);
260266

0 commit comments

Comments
 (0)