-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Console streams are blocking on Windows #14576
Comments
IIRC Win32 console handles do not support overlapped I/O, so any kind of asynchronous capability requires threads; the standard input and output streams are not opened with |
This snippet echoes the standard input while printing an asterisk every second in the background, using a dedicated thread for reading from require "colorize"
lib LibC
fun PostQueuedCompletionStatus(completionPort : HANDLE, dwNumberOfBytesTransferred : DWORD, dwCompletionKey : ULONG_PTR, lpOverlapped : OVERLAPPED*) : BOOL
end
module AsyncStdin
@@read_cv = Thread::ConditionVariable.new
@@read_mtx = Thread::Mutex.new
@@iocp : LibC::HANDLE = LibC::INVALID_HANDLE_VALUE
@@read_key = Crystal::IOCP::CompletionKey.new
@@done_cv = Thread::ConditionVariable.new
@@done_mtx = Thread::Mutex.new
@@buf : String?
@@stdin_read = Thread.new do
while true
@@read_mtx.synchronize do
until @@read_key.fiber
@@read_cv.wait(@@read_mtx)
end
end
# this shall be baked into `ConsoleUtils.read_console` instead, which also implies
# this entire thread shouldn't need to interact with any Crystal scheduler directly
@@buf = STDIN.gets.not_nil!
# `JOB_OBJECT_MSG_EXIT_PROCESS` is to reuse the resumption mechanism for `Process.run`
LibC.PostQueuedCompletionStatus(@@iocp, LibC::JOB_OBJECT_MSG_EXIT_PROCESS, @@read_key.object_id, nil)
@@done_mtx.synchronize do
while @@buf
@@done_cv.wait(@@done_mtx)
end
end
end
end
def self.read_stdin
@@read_mtx.synchronize do
# TODO: what happens if there are concurrent reads? replace these with a queue?
@@iocp = Crystal::EventLoop.current.iocp
@@read_key.fiber = Fiber.current
@@read_cv.signal
end
Fiber.suspend
@@done_mtx.synchronize do
buf, @@buf = @@buf, nil
@@done_cv.signal
buf
end
end
spawn do
while true
print '*'.colorize.magenta
sleep 1
end
end
while true
print read_stdin.colorize.green
end
end |
I'm sure |
Considering that the synchronisation happens entirely inside the Crystal runtime, I'm wondering if we even need to go through IOCP in order to signal completion. So something like this would do as well (maybe could replace @@read_requests = Channel({Pointer(String?), Fiber}).new
@@stdin_read = Thread.new do
while request = @@read_requests.receive?
buff_pointer, fiber = request
buff_pointer.value = STDIN.gets
fiber.enqueue
end
end
def self.read_stdin
buf = nil.as(String?)
@@read_requests.send({pointerof(buf), Fiber.current})
Fiber.suspend
buf
end |
The current reader loop intentionally does not directly interact with the event loop at all, especially considering it needs to work without |
I figure this shouldn't be an issue anymore with the execution contexts from crystal-lang/rfcs#2 though? |
Then we could come back when execution contexts are stabilized. A Windows stable release shall not depend on them. |
This has been mentioned before but I don't think there's a dedicated issue for it.
The standard streams on Windows are blocking. On Unix systems, on the other hand, they are non-blocking for TTYs. It allows to continue other fibers while waiting on IO. This is particularly relevant for
STDIN
.For example, the following program prints the current time and updates it every second, while pressing enter aborts.
On Windows, it doesn't print anything because
STDIN.gets
is blocking and the loop fiber never gets a chance to execute.We should have the same behaviour as on Unix. I'm not sure how we can best achieve it.
blocking: false
inFileDescriptor.from_stdio
on Windows. This certainly requires Support asynchronous file I/O on Windows #14321 and maybe something else. We would probably have to duplicate the file descriptor as we do on Unix, in order to avoid problems with the parent process. But I'm not sure about the Windows mechanics here.GetNumberOfConsoleInputEvents
.The text was updated successfully, but these errors were encountered: