Search code examples
c#.netgarbage-collectionsafehandleconstrained-execution-reg

SafeHandle with memory pressure


The SafeHandle class allows the safe access to native resources under .NET with reference counting and cooperation from the P/Invoke marshaller to avoid premature disposal of handles that could lead to an application crash.

In some situation, it would be advantageous to notify the garbage collector that some native resource is using a lot of memory, for example, when the "handle" in question is a large buffer owned by a wrapped native library. The GC.AddMemoryPressure may be used for this purpose; however, GC.RemoveMemoryPressure must be called at the end of the lifetime of the "handle".

The two approaches seem to be incompatible: SafeHandle is a CriticalFinalizerObject and its ReleaseHandle method runs in a Constrained Execution Region (CER). As GC.RemoveMemoryPressure has no ReliabilityContractAttribute, it must not be called from a CER (I guess at critical finalization time some parts of the GC concerned with memory pressure could be unavailable).

Two approaches came to my mind, both quite inelegant:

  1. The SafeHandle could be wrapped into another object that has a non-critical finalizer (and Dispose method) that adds and removes the memory pressure. This has two deficiencies: firstly, someone could dispose the SafeHandle (it must be exposed to be passed to native methods) without disposing the wrapper, thus the memory pressure is overestimated. Secondly, the wrapper may become unreferenced while the SafeHandle is still alive, thus the memory pressure is underestimated (could be more severe).

  2. The SafeHandle has a field of some non-critical finalizable type that manages the memory pressure. In this case, most problems from above are gone, however, the handle cannot dispose the memory pressure object in its Dispose or ReleaseHandle methods, as this would lead to a GC.RemoveMemoryPressure call in a CER. Another method (such as DisposeNoncriticial) could be provided, but this leads to a "slicing" issue when the handle is cast to IDisposable or used in a using block.

Is there a common pattern to create a SafeHandle with associated memory pressure?


Solution

  • It is a nice pattern to wrap a handle and not expose it under normal circumstances. You could do that part just like FileStream. It is understood that the handle is owned by the FileStream and it is illegal to close it. This is not enforced but it's the contract.

    The second issue is that you want to tie the lifetime of the SafeHandle to its wrapper. The ConditionalWeakTable class allows you to do that. The design would look like this:

    class Wrapper {
     SafeHandle handle;
     CWT cwt = new CWT() { { handle, this } };
    
     ~Wrapper() { RemoveMemoryPressure(); }
    }
    

    The CWT keeps the wrapper alive iff the handle is alive. CWT is used to associate arbitrary state to non-extensible objects and not have any GC effect doing that.

    I'm not sure all of this is worth it but it would be a solution to (mostly) satisfy your requirements.

    Also, I'm not sure to what extent memory pressure is of practical use.