#8684: hWaitForInput cannot be interrupted by async exceptions on unix
-------------------------------------+-------------------------------------
Reporter: nh2 | Owner: (none)
Type: bug | Status: new
Priority: normal | Milestone:
Component: Core Libraries | Version: 7.6.3
Resolution: | Keywords:
Operating System: Unknown/Multiple | Architecture:
| Unknown/Multiple
Type of failure: None/Unknown | Test Case:
Blocked By: 13497, 13525 | Blocking:
Related Tickets: #12912, #13525 | Differential Rev(s): Phab:D42
Wiki Page: |
-------------------------------------+-------------------------------------
Comment (by nh2):
I managed to make it work on Windows for `FILE_TYPE_CHAR`, by using
`WaitForMultipleObjects()` and passing it 2 objects: The `HANDLE` we're
interested in, an Event object that we signal when we want to interrupt
the thread.
After searching for a very long time, I ended up with the above solution.
Most important links:
* [https://social.msdn.microsoft.com/Forums/sqlserver/en-
US/dd7ce0e9-847d-4727-b0a6-efd68bd5626e/synchronous-readfile-on-stdin-
cannot-be-unblocked-by-cancelsynchronousio?forum=windowssdk Post by Ben
Golding in this thread] that proposes this solution
* [https://stackoverflow.com/questions/47336755/how-to-
cancelsynchronousio-on-waitforsingleobject-waiting-on-stdin My
StackOverflow question] (answers trickled in only after I had already
implemented a work-in-progress solution
Here's the backstory of me finding it out via the `#ghc` and `#winapi` IRC
channels:
In `#ghc`:
{{{
JaffaCake_: may I steal your attention for a minute? `ccall
interruptible` doesn't seem to work for me on Windows, it claims that
there's nothing to cancel, do you have an idea what this could be?
https://ghc.haskell.org/trac/ghc/ticket/8684#comment:25
no idea, sorry
there was a bug reported recently in process that also looks
like foreign ccall interruptible not working on windows
JaffaCake_: yes, I'm aware of it and have talked a bit with
Neil about it, I'll try to make that interruptible after I've made
hWaitForInput interruptible (already works on Linux -threaded for me), but
currently it seems interruptible doesn't work at all for me on Windows
TBH I don't remember whether it ever worked
JaffaCake_: there's another thing that I've already looked
into a couple hours but haven't found a way yet:
My non-Windows fix works nicely on -threaded, but for
nonthreaded it doesn't, because the scheduler makes it immediately re-
enter the foreign call, so the `throwTo` in `timeout` doesn't get a chance
to run.
Would it be possible to say, in the scheduler: Whenever a
thread returns from a foreign call, yield, so that other Haskell threads
have a chance to run?
I think that's probably a bad idea
if the timeout has fired, then there should be a blocked
exception
and that should get thrown immediately the FFI call returns
hWaitForInput/fdReady() gets woken up by the SIGVTALRM timer
signal, and then the scheduler immediately runs fdReady() again
so `timeout` never actually gets a chance to throwTo the
exception
so in other words, the timeout can never fire
nh2[m], you're not running with -threaded?
JaffaCake_: no, that's what I meant, it's working fine with
-threaded on Linux, but not with non-threaded. This problem (and my
question on whether we could yield after FFI return) is only for non-
threaded
oh, I'm not worried about non-threaded
JaffaCake_: but I am :D I'm trying to make it work equally
well across both threaded and non-threaded; in general I care a lot about
non-threaded as for some applications it is significantly faster, and it
is also easier to debug
but you'll need to add hacks to make it work, hacks that
could add overhead for -threaded
JaffaCake_: my hope is that such overhead would be
negligible, as it would occur only for `interruptible` syscalls (which by
nature are expensive, so a bit of bookkeeping around them should not
matter much). For example, all calls to `fdReady()` that are performance-
sensitive, nonblocking (timeout=0), already use `unsafe` and thus wouldn't
suffer any overhead
having non-threaded not working would be quite a drag for
us, as we found it to perform way better for sequential-IO-heavy (e.g.
network) code
and with things like `timeout` not working for
`hWaitForInput`, we can't put timeouts on reads or writes in high-level
the code (would have to handle such logic at the very low level of
hWaitForInput itself)
e.g. `timeout N $ seriesOfVariousNetworkProtocolActions`
}}}
In `#winapi`:
{{{
nh2
18:54 hi, I'm trying to
WaitForSingleObject(GetStdHandle(STD_INPUT_HANDLE), ...) on stdin, and to
cancel this waiting using CancelSynchronousIo(), but the cancellation does
nothing (returns 0 and GetLastError() is ERROR_NOT_FOUND). Any idea what I
could be doing wrong?
SleepyISIS (IRC)
19:29 nh2[m]: If this function cannot find a request to cancel, the return
value is 0 (zero), and GetLastError returns ERROR_NOT_FOUND.
19:30 Are you sure that you want to cancel IO, not the wait itself?
nh2
19:31 SSleepyISIS: can you elaborate a bit on that distinction? To me
those sound like the same things
19:33 what I need to do is use a function that blocks until input is
available on the given HANDLE to a FILE_TYPE_CHAR, up to a maximum amount
of time (e.g. in milliseconds), but if some other event occurs, I need to
cancel that blocking wait
John___ (IRC)
19:33 nh2[m], there's SleepEx()
19:34 which does what you want.
19:35 also WaitForMultiple/SingleObject()
19:35 depends what your goal is.
nh2
19:35 JJohn___: that one seems to just wait a given amount of time,
independent from input being available on some HANDLE. I want to wait
until either the specified time has passed, or there's input available on
the HANDLE, or the waiting has been cancelled (e.g. using
CancelSynchronousIO)
SleepyISIS (IRC)
19:35 nh2[m]: Is terminating a thread an option?
John___ (IRC)
19:36 nh2[m], then WaitForSingleObject()
nh2
19:37 JJohn___: yes, that is what I am using, and what doesn't seem to
work (as I wrote above)
SleepyISIS (IRC)
19:37 Or stick to asynch solution.
John___ (IRC)
19:37 it waits for a timeout or when input is available on a handle.
19:37 I was not here to see what the problem was. mind pasting it again?
nh2
19:37 SSleepyISIS: regarding terminating a thread, likely not, it should
ideally work in a single-threaded use case (I'm doing this for an
improvement in the GHC Haskell compiler), but I would still very much like
to hear what you have in mind, maybe it is possible
SleepyISIS (IRC)
19:38 John___: If I got nh2[m] correctly, (s)he wants to abort synch IO
before the wait timeouts.
nh2
19:38 JJohn___: I've just written up the problem more clearly on
https://stackoverflow.com/questions/47336755/how-to-cancelsynchronousio-
on-waitforsingleobject-waiting-on-stdin
How to CancelSynchronousIo() on WaitForSingleObject() waiting on stdin?
On Windows 10, I'm waiting for input from the console using
WaitForSingleObject( GetStdHandle(STD_INPUT_HANDLE), ... ) and to cancel
this waiting using CancelSynchronousIo(). But the cancellatio...
SleepyISIS (IRC)
19:39 nh2[m]: https://stackoverflow.com/questions/34372611/how-to-signal-
file-handle-waiting-with-waitforsingleobject
How to signal file HANDLE waiting with WaitForSingleObject
This code, which I have no control over, reads a file using overlapped
I/O: // Read file asynchronously HANDLE hFile = CreateFile(...,
FILE_FLAG_OVERLAPPED, ...); BYTE buffer[10]; OVERLAPPED oRead...
19:39 Hmm, what value do you get from GetStdHandle?
John___ (IRC)
19:40 nh2[m], synchronous solutions cannot be "unblocked"
19:40 that's why you use async.
19:40 let me read the stack post first though before I reply differently.
nh2
19:42 JJohn___: it was my understanding so far that
CancelSynchronousIo()'s purpose is to cancel specifically functions like
WaitForSingleObject(), so I'm surprised that it doesn't work
John___ (IRC)
19:42 I don't think it's for those purposes.
nh2
19:42 SSleepyISIS: the HANDLE returned from GetStdHandle() is 0 for the
stdin
SleepyISIS (IRC)
19:43 >My colleague suggested WaitForMultipleObjects() as a workaround. So
we can wait on either the console/stdin handle or an event which signals
termination.
19:43 Good solution.
19:43 0 doesn't seem to be a valif handle.
19:44 Yeah: If an application does not have associated standard handles,
such as a service running on an interactive desktop, and has not
redirected them, the return value is NULL.
19:44 https://docs.microsoft.com/en-us/windows/console/getstdhandle
John___ (IRC)
19:44 yeah, that ^
19:45 using that in combination with SetEvent()
19:45 allows you to either make WaitForMultipleObjects() to catch on
WAIT_SIGNALED or WAIT_TIMEOUT
nh2
19:45 SSleepyISIS: oh sorry, I typoed my printf. The HANDLE returned from
GetStdHandle() is 84 for the stdin
John___ (IRC)
19:45 or whatevet
19:45 so you can make it either trigger when the handle input is available
nh2
19:48 SSleepyISIS: JJohn___ OK, so I would get rid of the
CancelSynchronousIo() completely, and always make sure that any
WaitForSingleObject() on stdin is instead a WaitForMultipleObjects() on
stdin AND the special separate event handle I use for "early termination"
of the wait?
Mysoft (IRC)
19:50 nh2[m]
19:50 you tried to use CancelIoEx
19:50 isntead of CancelIoSynchronous?
nh2
19:55 MMysoft: I will try that now; what would you pass as the
lpOverlapped argument, NULL?
Mysoft (IRC)
19:56 yeah
19:56 and which OS you are trying that?
19:56 and you're running from console?
19:56 or inside an IDE?
nh2
19:57 MMysoft: that call fails with GetLastError() == 6
(ERROR_INVALID_HANDLE). I'm on Windows 10, running from cmd.exe
Mysoft (IRC)
19:58 ok
19:58 so this was a behavior change
19:58 because here it works
19:58 i think it changes in some update on win7
19:58 (bug?)
19:59 there's a OldNewThing about the subject
19:59 https://blogs.msdn.microsoft.com/oldnewthing/20150323-00/?p=44413
20:00 but anyway using WaitForMultipleObjects is the way to go
20:00 as i think using CancelIoEx... looks as bad as "ungetc" :P
nh2
20:04 MMysoft: ah wait, I am using CancelIoEx() wrong. For the
CancelSynchronousIo() I had to provide a thread handle as an argument, but
for CancelIoEx() I have to provide the actual file handle as the argument.
Let me retry
20:05
nh2
20:06 MMysoft: OK, with that fixed, CancelIoEx() gives me GetLastError()
== ERROR_NOT_FOUND, like for CancelSynchronousIo()
20:07 I'll try the oldnewthing example code on my system
[Note by nh2: I didn't actually manage to make that compile immediately
with msys, and it didn't turn out to be necessary to solve the issue]
John___ (IRC)
20:46 nh2[m], yes, you pass 2 handles and you check which got signaled.
20:46 the result is WAIT_OBJECT_0 to (WAIT_OBJECT_0 + nCount– 1)
nh2
00:11 JJohn__: SSleepyISIS MMysoft : thanks for your help, I just got the
first working implementation for GHC that fixes its behaviour on Windows,
using your proposed extra event + WaitForMultipleObjects() approach!
}}}
Back in `#ghc`:
{{{
JaffaCake: I think I have finally figured out how to fix the
Windows problem with CancelSynchronousIo() having no effect. Some people
on #winapi helped me, saying that this function might not work in practice
and instead we could use WaitForMultipleObjects(), with 2 objects, one for
the actual FD HANDLE and one Event we'd signal if we want
WaitForMultipleObjects() to return early. I've coded that up and it works.
But for efficiency, I need one Event per thread, so that I
don't wake up all threads in `interruptWorkerTask()`, but only the target
one.
My plan is thus to have a `HANDLE interruptOSThreadEvent;`
in the `struct Task`
But I need to access that in `inputReady.c`, which is in
base, where I cannot get at the currently running task with `myTask()`
is there a way I can ask for the currently running Haskell
task from that location?
<JaffaCake> I'm pretty sure we do that in integer-gmp
JaffaCake: I don't know how thread-local storage actually
works internally, but I suppose it's done by symbol names somehow, and if
I just declare `extern __thread Task *my_task;` in `inputReady.c`, it
would refer to the right thing technically? (though I'd still have to
bring `Task` into scope)
<JaffaCake> just expose a function from the RTS to return the thread-
local Event
<JaffaCake> I was thinking of rts_unsafeGetMyCapability()
<JaffaCake> which is a similar kind of thing
JaffaCake: which would be the best header file to put it in?
Probably somewhere under `includes/rts`?
<JaffaCake> next to rts_unsafeGetMyCapability()
JaffaCake: OK, and it would have to go somewhere in
RtsSymbols.c, probably `RTS_MINGW_ONLY_SYMBOLS`, is that right?
}}}
--
Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/8684#comment:28
GHC http://www.haskell.org/ghc/
The Glasgow Haskell Compiler