Skip to content

Shutdown aware objects

Chris Guzak edited this page Dec 11, 2021 · 3 revisions

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.

Functions

ProcessShutdownInProgress

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.

DLLMain

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.

Shutdown-aware objects

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 the T automatically. During process shutdown, it leaks the T object instead of destructing it.
  • wil::shutdown_aware_object<T> constructs the T automatically. During process shutdown, it calls a special ProcessShutdown() method on the T object instead of destructing it.
  • wil::manually_managed_shutdown_aware_object<T> constructs and destructs the T object only when explicitly instructed. During process shutdown, it calls a special ProcessShutdown() method on the T 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.