I'm using IEvolution's IETIFFHandler and IEImageList in an IIS-hosted ASP.NET WebAPI on .NET Framework 4.8. The API performs page-level TIFF operations (insert/append/rotate/delete, stamp text, etc) and is hit concurrently by many internal apps.
A few times a month, memory usage on one of our 4 pooled servers steadily climbs over ~1 hour until the box degrades or stalls. Reboot clears it.
I captured a memory dump during one of these events and had an LLM analyze it. Its conclusion is that .NET's single finalizer thread + FastMM requiring a global lock to free memory creates a bottleneck. Under sustained load with ~16 active worker threads, the finalizer thread rarely acquires the FastMM lock. Finalization effectively stops for all objects (not just IEvolution's!), and memory usage slowly climbs.
Notes/thoughts:
- I'm not fully confident in the LLM's conclusion; this could be a deadlock instead. Either way, the problem seems tied to heavy finalizer pressure.
- I think this bottleneck can be mitigated by aggressively reusing IETIFFHandler instances, perhaps for the lifetime of the process.
- Longer term, it would help if IETIFFHandler implemented IDisposable so that consumers of it can opt in to making cleanup be deterministic. IEImageList already does this, for example.
- I'm using quite an old version of IEvolution, 4.3.0.
Questions:
- Do newer versions eliminate IETIFFHandler's reliance on the finalizer? (I don't see a Dispose() method in the documentation for the current version, so my guess is no.)
- Do newer versions improve the finalizer's locking behavior in a way that makes this scenario impossible (or at least less likely)?
- Is there a recommended lifecycle pattern for IETIFFHandler instances? How long is it safe/reasonable to keep instances alive? (Calling FreeData() between reuses, of course.)
|