Background: generally, if we want to force an operation to happen asynchronously (to avoid blocking the main thread), using FILE_FLAG_OVERLAPPED is insufficient, because the operation can still complete synchronously.
So let's say that, to avoid this, we defer the operation to a worker thread dedicated to I/O. This avoids blocking the main thread.
Now the main thread can use CancelIoEx(HANDLE, LPOVERLAPPED) to cancel the I/O operation initiated (say, via ReadFile) by the worker.
However, for CancelIoEx to succeed, the main thread needs a way to guarantee that the operation has in fact started, otherwise there is nothing to cancel.
The obvious solution here is to make the worker thread set an event after its call to e.g. ReadFile returns, but that now brings us back to the original problem: since ReadFile can block, we'll have defeated the entire purpose of having a worker thread in the first place, which was to ensure that the main thread isn't blocked on the I/O.
What's the "right" way to solve this? Is there a good way to actually force an I/O operation to happen asynchronously while still being able to request its cancellation later in a race-free manner when the I/O hasn't yet finished?
The only thing I can think of is to set a timer to periodically call CancelIoEx while the I/O hasn't completed, but that seems incredibly ugly. Is there a better/more robust solution?
you need in general do next:
every file handle which you use to asynchronous I/O incapsulate to some c/c++ object (let name it
IO_OBJECT)this object need have reference count
before start asynchronous I/O operation - you need allocate another object, which incapsulate
OVERLAPPEDorIO_STATUS_BLOCK(let name itIO_IRP) insideIO_IRPstore referenced pointer toIO_OBJECTand specific io information - I/O code (read, write, etc) buffers pointers,..check return code of I/O operation for determinate, are will be I/O callback (packet queued to iocp or apc) or if operation fail (will be no callback) - call callback by self just with error code
I/O manager save pointer which you pass to I/O in
IRPstructure (UserApcContext) and pass it back to you when I/O finished (if use win32 api this pointer equal pointer to OVERLAPPED in case native api - you can direct by self control this pointer)when I/O finishid (if not synchronous fail at begin) - callback with final I/O status will be called
here you got back pointer to
IO_IRP(OVERLAPPED) - call method ofIO_OBJECTand release it reference, deleteIO_IRPif you at some point can close object handle early (not in destructor) - implement some run-down protection, for not access handle after close
run-down protection very similar to weak-refefence, unfortunatelly no user mode api for this, but not hard implement this byself
from any thread, where you have pointer (referenced of course) to your object, you can call
CancelIoExor close object handle - if file have IOCP, when last handle to file is closed - all I/O operations will be canceled. however for close - you need not callCloseHandledirect but begin run-down and callCloseHandlewhen run-down completed (inside some ReleaseRundownProtection call (this is demo name, no such api)some minimal tipical implementation:
some more full implementation
yes, despite you can safe call
CancelIoExat any time, even if no active I/O on file, by fact another thread can start new I/O operation already after you callCancelIoEx. with this call you can cancel current known single started operations. for instance - you begin conectConnectExand update UI (enable Cancel button). whenConnectExfinished - you post message to UI (disable Cancel button). if user press Cancel until I/O (ConnectEx) ative - you callCancelIoEx- as result connect will be canceled or finished normally bit early. in case periodic operations (for instanceReadFilein loop) - usuallyCancelIoExnot correct way for stop such loop. instead you need callCloseHandlefrom control thread - -which effective cancell all current I/O on file.about how
ReadFileand any asynchronous I/O api work and are we can force faster return from api call.FILE_OBJECT) to pointers, check permissions, etc. if some error on this stage - error returned for caller and I/O finishedIRP) and finally return to I/O manager. it can return orSTATUS_PENDING, which mean that I/O still not completed or complete I/O (callIofCompleteRequest) and return another status. any status other thanSTATUS_PENDINGmean that I/O completed (with success, error or canceled, but completed)STATUS_PENDINGand if file opened for synchronous I/O (flagFO_SYNCHRONOUS_IO) begin wait in place, until I/O completed. in case file opened for asynchronous I/O - I/O manager by self never wait and return status for caller, includingSTATUS_PENDINGwe can break wait in stage 3 by call
CancelSynchronousIo. but if wait was inside driver at stage 2 - impossible break this wait in any way. anyCancel*Io*orCloseHandlenot help here. in case we use asynchronous file handle - I/O manager never wait in 3 and if api call wait - it wait in 2 (driver handler) where we can not break wait.as resutl - we can not force I/O call on asynchronous file return faster. if driver under some condition will be wait.
and more - why we can not break driver wait, but can stop I/O manager wait. because unknown - how, on which object (or just Sleep), for which condition driver wait. what will be if we break thread wait before contidions meet.. so if driver wait - it will be wait. in case I/O manager - he wait for IRP complete. and for break this wait - need complete IRP. for this exist api, which mark IRP as canceled and call driver callback (driver must set this callback in case it return before complete request). driver in this callback complete IRP, this is awaken I/O manager from wait (again it wait only on synchrnous files) and return to caller
also very important not confuse - end of I/O and end of api call. in case synchronous file - this is the same. api returned only after I/O completed. but for asynchronous I/O this is different things - I/O can still be active, after api call is return (if it return
STATUS_PENDINGorERROR_IO_PENDINGfor win32 layer).we can ask for I/O complete early by cancel it. and usually (if driver well designed) this work. but we can not ask api call return early in case asynchronous I/O file. we can not control when, how fast, I/O call (
ReadFilein concrete case) return. but can early cancel I/O request after I/O call (ReadFile) return . more exactly after driver return from 2 and because I/O manager never wait in 3 - can say that I/O call return after driver return control.if one thread use file handle, while another can close it, without any synchronization - this of course lead to raice and errors. in best case
ERROR_INVALID_HANDLEcan returned from api call, after another thread close handle. in worst case - handle can be reused after close and we begin use wrong handle with undefined results. for protect from this case need use handle only inside run-down protection (similar to convert weak reference to strong ). demo implementation:with
WrongClose();call we permanent will be catch__debugbreak()(use after close) inWorkThread[Wrong]. but withobj.Close();andWorkThreadwe must never catch exception. also note thatClose()islock-freeand caller of it never wait/hang even if api call inside rundown-protection will wait.