-
Notifications
You must be signed in to change notification settings - Fork 240
Shutdown aware objects
The WIL error handling helpers include helpers which permit objects to change behavior during process shutdown, usually by bypassing unnecessary work.
All of the functions and types are in the wil
namespace.
Note that these helpers are required only in DLLs, because executables cannot be unloaded dynamically. Any destructors in executables necessarily run at process shutdown.
bool wil::ProcessShutdownInProgress();
This function is the basis for the shutdown-aware objects.
It returns true
if the process is shutting down.
You are responsible for calling wil::DLLMain
(below)
in your DllMain
function's DLL_PROCESS_DETACH
handler
in order to inform WIL that the process is shutting down.
Since executables do not receive a
DLL_PROCESS_DETACH
notification,
this function is not effective for WIL objects
in executables.
However, there is no need for shutdown-aware objects
in executables because executables cannot be
unloaded dynamically.
void wil::DLLMain(HINSTANCE, DWORD reason, void* reserved);
Your DLLMain
function must forward all calls
to the wil::DLLMain
function so that
WIL can become aware of process shutdown.
Example:
BOOL CALLBACK DllMain(HINSTANCE hinst, DWORD reason, void* reserved)
{
// let WIL know about process lifetime
wil::DLLMain(hinst, reason, reserved);
... normal DllMain code goes here ...
}
Note that the capitalization of the WIL DLLMain
function
differs from the capitalization of the
conventional DllMain
function.
There are three types of shutdown-aware objects, with increasing complexity. They all wrap a T
object.
-
wil::object_without_destructor_on_shutdown<T>
constructs theT
automatically. During process shutdown, it leaks theT
object instead of destructing it. -
wil::shutdown_aware_object<T>
constructs theT
automatically. During process shutdown, it calls a specialProcessShutdown()
method on theT
object instead of destructing it. -
wil::manually_managed_shutdown_aware_object<T>
constructs and destructs theT
object only when explicitly instructed. During process shutdown, it calls a specialProcessShutdown()
method on theT
object instead of destructing it.
This table summarizes the behavior and requirements.
Wrapper class |
object_without_ destructor_on_ shutdown<T>
|
shutdown_aware_ object<T>
|
manually_managed_ shutdown_aware _object<T>
|
---|---|---|---|
Constructs the T
|
Automatically | Automatically | When you call construct()
|
Cleans up the T
|
Automatically | Automatically | When you call destroy()
|
If process shutting down | Does nothing | Calls T::ProcessShutdown()
|
Calls T::ProcessShutdown()
|
If process not shutting down | Destructs the T
|
Destructs the T
|
Destructs the T
|
Constructibility of T
|
Public default constructor | Public default constructor | Public default constructor |
Destructibility of T
|
Public destructor | Public destructor | Public destructor |
Other requirements | Public method void ProcessShutdown()
|
Public method void ProcessShutdown()
|
The intended usage is as follows:
Wrapper class |
object_without_ destructor_on_ shutdown<T>
|
shutdown_aware_ object<T>
|
manually_managed_ shutdown_aware _object<T>
|
---|---|---|---|
Declaration | Declare a global variable | Declare a global variable | Declare a global variable |
In DLL_PROCESS_ATTACH
|
Call construct()
|
||
In DLL_PROCESS_DETACH
|
Call destroy()
|
||
In your destructor | Clean up everything | Clean up everything | Clean up everything |
In your ProcessShutdown()
|
N/A | Clean up minimal | Clean up minimal |
Note, since the T is leaked, use in DLLs that can unload results in a leak when unloaded. Only use these helpers if the DLL will never unload.
Minimal cleanup may consist of flushing lazy-written data.
In the case of a manually_managed_shutdown_aware_object
,
construct()
may be called
only when the object is in its empty/destroyed state,
and destroy()
may be called only when the object is in the constructed state.
Double-construction or double-destruction results in undefined behavior.
All three template classes have the following member function:
-
T& get()
Returns a reference to the wrapped object.
Example:
class FeatureUsageData
{
public:
FeatureUsageData() = default;
~FeatureUsageData()
{
SaveUsageData();
}
void ProcessShutdown()
{
SaveUsageData();
}
void LogUsage(std::string const& feature)
{
auto guard = m_lock.lock_exclusive();
++m_usage[feature];
}
private:
wil::srwlock m_lock;
std::map<std::string, int> m_usage;
};
wil::shutdown_aware_object<FeatureUsageData> featureUsageData;
This hypothetical class records feature usage statistics.
The usage statistics are cached in the m_usage
map.
When the DLL unloads or the process terminates,
the usage statistics are saved by the hypothetical
SaveUsageData
method.
In the case of process termination, we save the usage data,
but do not destruct the map,
thereby short-circuiting unnecessary work.