Introduction
This article describes clean and quick ways of cancelling I/O operations from Windows user mode modules (applications, DLLs, etc). This applies to components that execute I/O operations such as disk read/write (through ReadFile
, WriteFile
, fwrite
, etc), device specific IOCTLs, etc.
Problem
When an application, which is currently executing an I/O operation or waiting on a pending I/O operation, is terminated or closed, it won't exit immediately (even though its GUI disappears). Until all of its I/O operations are cancelled or completed, the application would still be running. But this is not intended behavior when the end user closes the application's main Window (the end user expects that the application exits right away). Additionally, the system shut down/restart will take longer time due to such applications (since the OS would be waiting for a certain time period for those applications to terminate).
Until now (OSs up to Windows 2003), I/O operations can be cancelled ONLY by the same thread that initiated it (using CancelIo
API). So, during application exit, it is hard to cancel all outstanding I/O operations from a single thread (the termination handler).
The solution existing till now is: all threads would need to be notified (using Events or so) during application exit and upon such notifications, each thread (which would be waiting for completion of asynchronous I/O operations) would cancel I/O operations using the CancelIo
API. After all threads are done with cancellation, application exit routine will return. Yet there is a gap in this solution: during application exit, if any thread is waiting for completion of synchronous I/O operation (say, it called ReadFile
, WriteFile
synchronous operations), then the exit routine has no way of cancelling such synchronous I/O operations - it has to wait till the I/O operation completes.
In case of DLLs (during application exit, DllMain()
of all loaded DLLs will be called to handle cancellation of outstanding operations), even the asynchronous I/O cancellation solution (explained above) is not possible. It is because, while DllMain()
is called, other threads created by the DLL won't run (due to mutual exclusion mechanism built-in). Hence, when DllMain()
is called with parameter DLL_PROCESS_DETACH
during application exit, even if it notifies other threads (through Events or so), other threads won't run and hence none of the outstanding I/O operations would be cancelled.
Proposed Solution
This article (along with the code) describes how to ensure the application (or even DLLs) can cancel all I/O operations (synchronous as well as asynchronous) that are currently outstanding and exit as soon as possible, yet this cancellation happens in cleaner way.
Benefit of the proposed solution
If applications implement the solution described in this article, they would ensure that all outstanding I/O operations are cancelled in a clean manner and that the application would exit as quickly as possible once it is terminated or closed by end users, improving user responsiveness and helping to minimize time taken for system shut down/restart as well.
Background
Device I/O (Input/Output) operations are the heart and brain of all applications in OS. The bottom line goal for many applications would be either read from device or write to device (device can be anything, speaker, video, HDD, etc). Applications call APIs provided by Windows (such as ReadFile
, WriteFile
, etc) to execute device I/O operations.
How to implement the proposed solution?
Basic idea behind the solution is to make use of new APIs introduced in Windows Vista/Longhorn, which are CancelSynchronousIo and CancelIoEx.
Before understanding how to make use of these APIs, let us first understand types of I/O operations. While issuing an I/O request, applications have 2 choices; whether the I/O operation is going to be synchronous or asynchronous. As the name suggests, synchronous I/O operation APIs (ReadFile
, WriteFile
, etc) would return to the caller only after the I/O operation is completed, whereas, asynchronous I/O operation APIs (ReadFileEx
, WriteFileEx
, etc) would return immediately when the I/O operation is initiated (later, the application needs to check if asynchronous I/O operation is completed or not).
That said, applications can be designed to make use of synchronous I/O or asynchronous I/O or even both, depending on its requirements. The solution will address cancellation of all types of I/O operations.
The solution of cancellation of outstanding I/O operations will need to be implemented in a routine that will be executed when the application exits (example: WM_CLOSE
message handler or in DllMain()
when DLL_PROCESS_DETACH
is received in case of DLLs).
To cancel synchronous I/O operations, CancelSynchronousIo
API is introduced in Vista/Longhorn. This API takes the thread handle as its parameter using which it cancels synchronous I/O operation currently in progress by the thread. Now, there are 2 ways of retrieving thread handle of all threads created by this application.
- The application can maintain a global variable that contains a list of handles of all threads created (to be specific, whichever thread may execute I/O operation). Now, during cancellation, this global list can be traversed to retrieve handle of all threads.
- During cancellation of I/O operations, we can enumerate all threads (and retrieve the thread handle) pertaining to this application using APIs like
CreateToolhelp32Snapshot
, Thread32First
, OpenThread
, Thread32Next
(see code for usage of these APIs).
Now, after retrieving the thread handle of all threads of this application, CancelSynchronousIo
API can be called for each thread (Note: This API will return immediately after marking the I/O operation as cancelled). Refer to the code.
To cancel asynchronous I/O operations, the mechanism is a little complex. The application needs to maintain a list of handles to devices to which I/O operations are executed. Whenever the application opens a device for asynchronous I/O operation (say, using CreateFile
, OpenFile
, etc), it must update the global list of device handles (and remove the handle while closing the device using CloseHandle
API).
Now, during cancellation of I/O operations, the application needs to traverse a list of device handles and for each handle found in the list, it needs to call CancelIoEx
API (refer to the code).
By executing the algorithm explained above, applications can ensure that all outstanding I/O operations (synchronous and asynchronous) will be cancelled quickly and in a clean manner, improving responsiveness during termination.
Using the code
The code contains a file named CleanCancelIOs.cpp that contains routine named CancelAllIOs()
. To use this, first add this file into your application's project. Now, in the application's termination handler (WM_CLOSE
message handler or DllMain()
when DLL_PROCESS_DETACH
is received in case of DLLs or so), call the CancelAllIOs()
routine.
For handling asynchronous I/O cancellation, you need to declare global variables "HANDLE *g_hDeviceList
" (which contains a list of device handles) and "int g_nDevCount
" (which contains a count of device handles in the list) as explained in the previous section. As discussed, your application needs to update these global variables as and when a device is opened or closed.
Now, compile and run your application. Now, your application handles I/O cancellation quickly and in a clean manner.
References
There are a few articles from Microsoft that discuss I/O cancellation. But they don't mention how exactly an application can cancel all outstanding I/O operations during termination (during termination only, application needs to cancel all outstanding I/O operations, typically) - that's what this article focuses on.
- Win32 I/O cancellation Support in Windows Vista
- Cancelling pending I/O operations
History
- 12 February 2007: Original article