6

I have a program that has multiple threads which need to use the same handle (it is opened with FILE_SHARE_READ) to write. What happens if one thread closes the handle while another currently has an I/O in progress (WriteFile, in my case)? Does CloseHandle block until the write finishes?

The MSDN documentation seems very sparse on this topic.

user2563087
  • 151
  • 1
  • 8
  • The clean solution is to let the thread which started the overlapped I/O clean up as well. And use a dedicated thread for it. – Lundin Nov 27 '18 at 07:38
  • I agree, but this will not work in my instance. The application is written to have multiple threads which issue I/O to the same handle because it is opened with GENERIC_READ | GENERIC_WRITE and FILE_SHARE_READ. I cannot use a lock on the handle because I'd need to hold it during an I/O, which will not work for many reasons. – user2563087 Nov 27 '18 at 18:35
  • @Lundin - i be say this is bad solution. call `CloseHandle` is the best way for cancel all pending I/O (begin from vista always and usual on xp). and we never need dedicated thread, never need wait, etc – RbMm Dec 06 '18 at 23:32
  • @RbMm A dedicated thread means you won't have to poll, nor do you have to interrupt the main thread. That thread will be asleep most of the time, only waking up when receiving an event. You don't "have" to use a thread, but it is a more professional solution than just relying on callbacks. – Lundin Dec 07 '18 at 07:31
  • @Lundin of course we never must poll. however exist solution, where we not use any dedicated threads, never wait - use callback and have 100% asynchronous io. and why you think that use and relying on callbacks not professional ? just interesting – RbMm Dec 07 '18 at 08:52
  • @RbMm Because they interrupt the program. If you have a single-threaded program with GUI and overlapped I/O, then the I/O might cause GUI lag. Especially true for serial communication and other slow things, that you may need to parse "on the fly". – Lundin Dec 07 '18 at 09:03
  • @Lundin - "interrupt the program" - what mean ? if we use single thread GUI overlapped I/O - this mean that we use APC completion and have message loop based on alertable wait in `MsgWaitForMultipleObjectsEx`. when we have I/O complete apc callback will be called (this you mean "interrupt program" ?) where we say can start next I/O and exit from callback. with iocp completion we use thread pool and no any problems. with serial port or say soft pipes here of course all ok, speed of communication not play any role, because we never way for io complete. not need any dedicated thread anyway – RbMm Dec 07 '18 at 09:13
  • i not once use different asynchronous I/O, write special libs for this, always rely on callbacks, never poll or wait. and all is just fine – RbMm Dec 07 '18 at 09:15
  • @RbMm You still need to handle the data. If you have for example a serial input and you need to echo each character, then that means one callback per character, after which you need to run some code - how complex the code is depends on the application. And code must be executed by a thread - it cannot execute in thin air without a CPU. So naturally the speed of the communication makes a big difference, because at higher speeds you'll get more frequent callbacks that you'll need to handle, by executing code, which you cannot do in thin air still. Threads make all the difference. – Lundin Dec 07 '18 at 09:21
  • @Lundin - if we need relative long time for process result of I/O - yes, exist sense pass this task to another thread if we use apc callback. if we use iocp callbacks with thread pool (i prefer this way and relative rarely use apc completion) - in general we can do this just in callback. here implementation logic depend from concrete task. possible of course use and dedicated thread(s) for io, but in this case usually it not need have asynchronous io - but synchronous – RbMm Dec 07 '18 at 09:34

2 Answers2

4

Depending on how close the I/O is to completion, it could either complete normally or be cancelled. Or if there's a non-zero usage count on the kernel file object (e.g. DuplicateHandle() was used), those operations could proceed normally until the other handle is also closed.

CloseHandle() may block, but if you really want to wait for completion (success or cancellation) of the pending I/O, wait on the event HANDLE in its OVERLAPPED structure after calling CloseHandle().

Good additional information: https://community.osr.com/discussion/213975

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I can have it handle whatever the result is, I just want to verify that no really bizarre results come back (e.g. GetOverlappedResult says the I/O completed successfully when it really didn't). – user2563087 Nov 26 '18 at 23:24
3

from system view - this is correct, safe, ok call CloseHandle at any time. this independed from are exist some I/O request in progress on file to which this handle pointed. even more call CloseHandle the best way (begin from vista, on xp - this is driver depended, usual I/O canceled in this case too) cancel all pending I/O requests

What happens if one thread closes the handle while another currently has an I/O in progress

if this is not last handle to file - nothing hapen. otherwise (this is usual case and think you mean exactly this case - you have single handle open on file) the IRP_MJ_CLEANUP handler will be called in driver (which create device objec, on which your file created). then already is no general and common answer - driver specific. however most windows built-in drivers at this point just complete with some error (usual with STATUS_CANCELLED but not always, say npfs.sys (pipe driver) use STATUS_PIPE_BROKEN error here) all pending requests and return control. on xp/2003 - this is all. so this is driver duty complete all pending requests at this point. however some 3-th party drivers can and not do this - so some I/O can still be pending (undefined time) after handle closed. but begin from vista here was major change. the FILE_OBJECT was extended (on xp/2003 the CompletionContext was last field) - added IrpList. now inside FILE_OBJECT system mantain list of some active I/O request on file. when we send asynchronous IRP request it queued to Thread (always before vista) or to FILE_OBJECT - if IOCP port accosiated with this file object, ApcContext not 0 (from win32 view - pointer to OVERLAPPED not 0) and no user event in IRP (from win32 view OVERLAPPED.hEvent == 0). this affect when system cancel IRP - when thread exit or when last file handle closed. in vista, after IRP_MJ_CLEANUP handler return - system check IrpList and if it not empty - call IoCancelIrp for every Irp from this list. of course than again driver depended, but all well design drivers (including all microsoft drivers) when return pending for request (Irp) mandatory register CancelRoutine to be called if the specified IRP is canceled. this routine called when system call IoCancelIrp (if not removed before) and inside this routine driver complete request with STATUS_CANCELLED. this mean that if driver not complete Irp inside IRP_MJ_CLEANUP - system by self cancel this irp just after IRP_MJ_CLEANUP return and this cause that driver anyway complete this Irp inside it CancelRoutine

so conclusion - close last handle on file (last call CloseHandle ) is effective way for cancell all I/O requests on this file (with some exceptions on xp and only for 3-rd party drivers).this is legal, safe and effective (if we want cancell al io and close file)

Does CloseHandle block until the write finishes?

because inside this call called driver supplied IRP_MJ_CLEANUP handler - can be all. even block. but if say about built-in windows drivers - this never happens. and even for 3-rd party drivers - i never view this behaviour. so in practic - CloseHandle not block and not wait when all I/O requests on file finished

so call CloseHandle is safe at any time, but here exist another problem, not related to system design but related to your code design. in your code (and usual) handle is shared resourse - several different threads will use it. and this is usual problem, when object (handle in your case) used by several threads - who and when must close handle. what happens if one your thread close handle when another thread begin I/O request with this handle, after it was closed ? you or got STATUS_INVALID_HANDLE from api call, if handle not valid at this time. but possible and more bad case. after you close handle - some another code can create another object. and for new created object will be assigned the last closed handle ( system use stack model for free handles). as result handle can be already valid when you begin I/O operation (after close handle from another thread), but.. this handle already will point to another object. if this is not file object handle (say event or thread handle) - you got STATUS_OBJECT_TYPE_MISMATCH from I/O request. in whe worst case - this will be file object handle, but for another file. so you can begin I/O operation with another file and unpredicatable result (say write data to another, arbitrary file).

so you need somehow protect or synchronize shared handle usage and closing, between threads. first think here - use reference counting for handle and call CloseHandle when no more reference. but this is not usable on practic - if say some thread permanent do I/O on file(pipe) (say read from it, when read complete - just call another read and so on) - we have no chance call CloseHandle from another thread.

for concrete example we call ReadDirectoryChangesW for file asynchronous. when this call complete - just again call ReadDirectoryChangesW and so on. gow break this loop ? set some stop flag and call CancelIoEx ? but possible that CancelIoEx will be called in between 2 calls of ReadDirectoryChangesW, whe really no any I/O on file. so it nothing cancel.. effective way stop this - call CloseHandle, but if we do this without protection - exist risk that next call to ReadDirectoryChangesW will be use arbitrary object (handle value will be the same, but it will be invalid or point to another object). use reference counting - not way here - who permanent call ReadDirectoryChangesW need have own reference and reference never rich 0. so we never call CloseHandle and never break loop.

the effective and nice solution here - use Run-Down Protection for handle. unfortunatelly not api in user mode for support this, but not hard yourself implement this

possible usage (if implement exactly how this implemented in kernel)

threads before use m_hFile, protected by m_RunRef, do next code

ULONG dwError = ERROR_INVALID_HANDLE;

if (AcquireRundownProtection(&m_RunRef))
{
  // use m_hFile in some I/O request
  // for example
  // dwError = ReadDirectoryChangesW(m_hFile, ..);
  ReleaseRundownProtection(&m_RunRef);
}

thread which want close handle do next

WaitForRundownProtectionRelease(&m_RunRef);
CloseHandle(m_hFile);

note that WaitForRundownProtectionRelease wait not when all I/O complete but only when all another threads exit from AcquireRundownProtection / ReleaseRundownProtection block. also after call WaitForRundownProtectionRelease AcquireRundownProtection always return false - so prevent new acquire and what for release existing.

however i prefer another implementation for begin rundown:

asynchronous call to BeginRundown(&m_RunRef) (after which all new AcquireRundownProtection return false) - but not wait at this place. end when rundown completed - callback will be called. and inside this callback we safe call CloseHandle - no new usage of handle at this place, and all old usage of handle (in io call) already completed. note - usage of handle completed - not mean I/O completed - it in progress maybe. but handle need only for start I/O

RbMm
  • 31,280
  • 3
  • 35
  • 56