An exit-time leak report answers a narrow question: which tracked allocations are still alive when the detector checks? That is not the same as asking whether process memory grew, whether your code owns the allocation, or whether a later runtime finalizer will release it. Before hiding a finding, identify which of those questions you are trying to answer.
This is a pattern chapter: the mechanism is in chapter 12, the configuration and the documented limits are in chapter 22. What follows is how each situation is recognised, what the reports showed, and where the evidence stops.
26.1 How the cases are labelled
In the stories below, a customer observation records a reported result; a staff proposal is an unverified next step; a staff diagnosis explains the suspected mechanism. A vendor reproduction and a customer retest add separate evidence. A build offered for testing is not a confirmed fix, and closing a support exchange does not change that. Versions identify the tested situation, not a promise about every release before or after it.
26.2 A leak report is a list of unreleased blocks, not a measurement of memory
Start by separating process memory from tracked allocations (chapter 22, section 22.1). In one case,
process memory rose to roughly twice the baseline
and did not return when the form that had been opened was freed, while the detector showed nothing at all
(customer observation). The answer explains the gap (staff diagnosis): the figures in a task manager are a poor
instrument, because the memory manager serves small allocations out of pools it has already reserved, so
allocating a hundred-odd bytes usually changes nothing there and releasing them usually changes nothing
either. The filter's own counters - AllocMemSizeEx and its companions (chapter 12, section 12.3) -
measure tracked allocations instead. Rising live-allocation totals warrant investigating retained
objects, including caches that are released only at shutdown; flat totals redirect attention to
fragmentation or allocations and resources outside that filter's coverage. A canvas cache in a system
library was one example raised by support. To inspect live allocations, use the allocation dump -
with the warning that the call blocks all memory operations, is slow, and that the process should be
terminated afterwards.
A real leak in thread-name collection. With EurekaLog 7.9.6.0, Delphi 11 and Win64 server modules,
memory grew with EurekaLog enabled, including configurations with its memory checks and extended memory
manager disabled. A small comparison project grew with EurekaLog and stayed stable without it. Support
reproduced the problem and identified an unreleased buffer returned by Windows' GetThreadDescription.
The first source patch received a positive customer retest. Later, test build 7.9.6.1 received separate positive reports for the sample and for production memory growth. The same exchange also confirmed a FastMM5 Win64 exception-stack correction in that test build; intermediate source patches had compilation problems, so do not treat every attachment offered along the way as a working release.
The useful discriminator was the same workload with and without the instrumentation, not the silence of the exit report. The support explanation for why the symptom appeared after moving from Delphi 10.4 to Delphi 11 was explicitly speculative; the unfreed thread-description buffer was not.
26.3 The check that did not run
A silent leak report can mean that the check did not run.
First inspect debugger-only activation. In one exchange, the RTL detector showed leaks while EurekaLog did not. Support read the supplied settings as debugger-only activation and suggested that this could explain the missing report when launched from the desktop. No customer retest follows, and no precise product/IDE/bitness combination is recorded. Compare the actual settings and launch mode rather than treating a missing file as proof of a clean heap.
The next question is whether finalization reached the check. An exception during shutdown can prevent the remaining cleanup and reporting from running. A case involving a destructor raising at exit received that diagnosis from support. First investigate the shutdown exception; do not infer a clean heap from the report that never appeared. Chapter 22 explains the application/module lifetime boundary.
Also distinguish a suppressed resource report from a skipped memory check. A Release build with a detected resource leak whose report was not shown triggered a separate internal-table memory finding. That reproduced case is described in section 26.5; it does not establish that all release builds or all missing debug information suppress leak reporting.
26.4 Managed thread-local data still needs cleanup
With EurekaLog 7.14.2.1 Update 2, Delphi 13 and Win32, a dynamic array held in a threadvar was
reported as leaked. The reporter expected automatic deallocation. Support rejected that premise and
quoted the compiler's thread-local-variable rules: managed types such as strings, dynamic arrays,
variants and interfaces do not receive the usual automatic heap cleanup when declared as thread
variables. Release their contents from within the owning thread before it terminates.
This is a staff explanation supported by compiler documentation, not a customer-confirmed retest. Support proposed reproducing the leak without EurekaLog using the RTL detector. The conversation does not record that experiment. The practical question is ownership at thread exit, not whether an array would normally be managed in a local variable.
Resource reports need a different ownership check. Historical cases include findings that support classified as false positives:
- a resource report for an object whose ownership had been passed to another module is a resource leak, not a memory leak, and it is a false positive, because resource checks run for the calling module only while the code passed the resource across a boundary - the same limitation, stated a version earlier, being that a resource handed to another module which deletes it later cannot be seen being deleted at all (staff diagnosis). The same answer announces that a later build would add global checking and a choice of what to track - the honest form of "this is a known limit";
- a small application that showed a message box and was closed reported leaked resources of a COM string type; those variables are not freed until late in the shutdown sequence and may still be alive when the detector runs, so the finding can be ignored (staff diagnosis), with a fix promised.
The accurate rule is narrower than an unconditional claim that detectors cannot report false positives: the memory detector describes tracked blocks unfreed at the check; a resource detector may not see a release performed outside its tracking scope. Do not turn an older per-module resource limitation or a promised enhancement into an undocumented rule for every current build. Chapter 22, section 22.8 also explains the shared-manager and unloaded-module limits.
26.5 Shutdown order: blocks freed after the check, or before it
Timing matters: the report is a snapshot, and some runtime cleanup can follow it.
Weak references and ARC. A FireMonkey project using EurekaLog 7.5.0.0 and Delphi Berlin Update 1
reported leaks and crashed during leak checking. Support attributed both to weak-reference cleanup
during System finalization, after EurekaLog had finalized: remaining references were reported, and
later deallocation could encounter the already-removed filter. The target bitness was not recorded.
Support described the Berlin runtime change, reported no usable RTL API or hook for that cleanup, and said a solution was still being investigated. The customer's acknowledgement is not a successful retest. This case is evidence against assuming all possible deallocations precede the check; it is not a diagnosis for every FireMonkey leak or a current promise that this historical failure persists.
A C++ runtime's own stream locks. A minimal VCL application described as C++Builder 10.3 Rio with EurekaLog 7.7.8.2 reported a leak when its form closed. The form submission records Win32 but conflicts with the prose on IDE/personality, so the C++/Rio boundary comes from the actual problem description. Support explained that flushing open streams keeps a block used for stream locking alive, because the lock is allocated during shutdown and released only after the finalization routines - EurekaLog among them - have run; the detector sees the block unfreed, and the runtime frees it when its own cleanup continues (staff diagnosis). The verdict is short: an expected leak, ignore it. The case also records the reporter's counter-observation that a competing product in the same build did not report it, and support declining to explain a third party's behaviour rather than denying the analysis.
Another detector's view of the tool's memory. Two cases have the runtime's own detector and a well-known open-source one reporting blocks that belong to EurekaLog. In the first, everything was fine with either the detector or EurekaLog disabled; support explains that the runtime's detector will show a couple of the tool's buffers still in memory at shutdown, calls that a false-positive indication, and recommends turning it off in favour of the tool's own detector, which shows a stack per leak (staff diagnosis). In the second, the block came from EurekaLog's initialization, and the first answer is that the tool reserves memory for its own use, freed before shutdown but not before the detector looks (staff diagnosis) - followed in the same case by the vendor's own conclusion that it is a bug in the tool's code, to be fixed (explicit confirmation). The two answers contradict each other on one symptom, which is instructive: whether the block is "freed too late" or "never freed" is invisible in the report and visible only in the source, and a finding a second tool attributes to the instrumentation belongs to the tool's own support.
The tool's own bookkeeping: reproduced, offered a build, not retested. An internal EVals table
appeared in a memory-leak report. The reporter proposed an options-object/reference-count explanation
involving resource reporting after module cleanup. Support initially could not reproduce it, then did
so with two conditions: a resource leak was detected but its report was not shown, and the application
was compiled in Release mode. Delay-loaded imports on newer IDEs were named as a way to reach this path.
Support offered 7.7.8.17. There is no explicit customer retest of the EVals failure: the next message
raises the different Hide RTL leaks issue below. Keep the reproduction, but do not promote the build
offer into a confirmed fix. The initial affected build, precise IDE and bitness are not established
in this exchange.
Options that remove blocks by name. In RAD Studio 10.3.1 a deliberately leaked standard container was not reported, because the "hide RTL leaks" option removes a fixed number of blocks of that class by name - a suppression written for an older runtime that no longer had those leaks, and which therefore swallowed a real one (user diagnosis). Support's reply is that the code is very old, its purpose is no longer documented and it is untested, and that the option belongs to old IDEs (staff diagnosis): a hiding option on the memory page is a statement about somebody else's older code, and it can hide an actionable leak in yours.
26.6 Real leaks that are not yours
These cases require separating an external allocation site from a proven external defect:
- a leak traced through a data-access layer's transaction handling, where removing the transaction statements removed the finding while the records were still stored (customer observation, user diagnosis) - the allocation site in the report and the ownership question are different things;
- a blank VCL application whose only content was a message box, reporting a leak inside the runtime's stream flush: the origin is the runtime's own code, a non-cumulative shutdown leak is not worth contorting your code over, and registering a block the runtime frees anyway answers the wrong observation (staff diagnosis, staff proposal);
- a Delphi Berlin trial FireMonkey project, where support initially could not tell whether reported leaks were real or false alarms and proposed disabling the extended memory manager while investigating. A later message offered 7.5.1.97 Alpha 2 as containing a workaround; there is no subsequent customer retest. The earlier request to close the case preceded that offer. Conflicting staff descriptions of the memory-manager architecture in this historical exchange should not replace the filter-over-existing-manager model in section 26.7;
- a leak that turned out to be in the toolchain: a 32-bit C++ application built with the newer LLVM-based compiler leaked a block allocated by the C++ implementation of the exception handler, and the same code leaked in an empty application without EurekaLog, while 64-bit builds and the classic compiler did not (independently reproduced result).
For these, the documented recourse is chapter 12, section 12.6 - register the block so it stops appearing - with the warning from chapter 22, section 22.9: you do not fix a problem, you hide it, and registering wholesale buries the reports you can act on. Register a non-cumulative leak if you must, never a cumulative one, and early enough that the registration always runs.
26.7 Two instruments, two answers
EurekaLog's memory checks use a filter over the application's selected memory manager. It is useful to distinguish that instrumentation from the underlying allocator before comparing two detectors. In particular:
- a detector with a full debug mode of its own is effectively two allocators, a release one and a debug one; adding EurekaLog puts the tool's data in blocks served by one of them, and that detector's corruption checks do not run when the write happens but when the memory is handed out again - which may be much later, or never (staff diagnosis). Two tools answering "what is wrong with this block" at different moments will disagree, and neither is lying;
- the runtime's own leak detector uses simple heap checking of the default heap manager and does not work with the tool's filter; use one or the other, not both (staff diagnosis);
- with a shared memory manager, a DLL can allocate through the host's manager and unload without freeing, and the host's shutdown check then finds a leak it cannot describe, because the module that allocated the block is gone; the answer calls that combination a bad idea and recommends refactoring the module boundary (staff diagnosis); chapter 22, section 22.8 lists the same limitation as a property of the feature;
- with runtime packages, code using the same packaged RTL already uses its manager; introducing a separate sharing mechanism is not the first remedy. The counters (chapter 12, section 12.3) answer whether tracked live allocations grow, while the allocation dump shows what is alive now. Neither is an exit-time leak report.
FastMM5 can lose symbols at finalization, even with the correct unit order. A later case used EurekaLog 7.15.0.2 Hotfix 2, Delphi 13 and Win64. FastMM5 found leaks but its leak log had no call stacks when EurekaLog was present. The customer had already placed FastMM5 first and disabled EurekaLog's extended memory manager. Support's initial unit-order and MAP-file checks did not settle the symptom.
The later diagnosis identified EFastMM5Support: it lets FastMM5 convert addresses through EurekaLog's
debug information. At FastMM5's shutdown leak check, EurekaLog has already finalized, so calling that
bridge can produce empty results. Support advised disabling the bridge when FastMM5 is the exit-time
leak detector. No customer retest follows; this is a specific staff diagnosis and proposed remedy, not
proof that 7.16 fixes the integration.
For that investigation, keep the two ordering problems separate:
- Enable FastMM5 Full Debug Mode before
EMemLeaksinitializes. A call in the.dprprogram body is too late if EurekaLog has already installed its filter.FastMM_EnterDebugModereturns whether the switch succeeded; check that result. TheFastMM_FullDebugModeconditional is another documented way to enter it during FastMM5 initialization. - If FastMM5 owns the shutdown leak report, do not rely on EurekaLog's symbol callbacks still being
alive then. A
FastMM5_FDMinitialization wrapper can referenceFastMM5while taking its place as the first unit in the project'suseslist; in the reported integration this prevents automatic addition ofEFastMM5Support. Check the resulting project, not only your originaluseslist. - The documented wrapper clears
FastMM5.FastMM_GetStackTraceandFastMM5.FastMM_ConvertStackTraceToTexttonilin its finalization, before FastMM5 finalizes, rather than retaining callbacks into finalized code. Arrange FastMM5's own symbol support and matching detailed MAP/debug-library files for this diagnostic build; removing the bridge does not itself supply symbols. - Exercise both an ordinary exception and an intentional shutdown leak in that same diagnostic build. Success means a useful exception report and a readable FastMM5 leak log, not just a successful compile or a Full Debug Mode flag.
Do not confuse this finalization problem with the positively retested Win64 exception-stack correction in 7.9.6.1 (section 26.2). They affect different report paths at different moments.
26.8 What the documentation guarantees, and what it does not
Chapter 22, section 22.8 collects what the checks cannot see - the exit-time timing, the standard heap only, the per-allocation cost, the truncated stack, the unreadable call stack for an unloaded module - and chapter 12, section 12.8 states the price in the unit's own terms. Read against the cases above, the claim is narrower than "a leak report is a list of your leaks":
- it is a list of blocks unfreed at the instant the check runs, for the allocations the filter saw;
- it exists only if the check ran: debugger-only activation excludes an ordinary desktop launch, and a failed shutdown can prevent the check from being reached;
- for resources, tracking scope matters: a release made by another module may be outside what the checking module observed;
- the report says which kind of block and where it came from; it cannot say whether a release was going to happen a millisecond later. In the Berlin weak-reference case (section 26.5), support reported no usable RTL API or hook for the late cleanup.
26.9 A checklist for "I think this report is wrong"
- Did the check run? Look for a debugger-only activation, for the memory option being off in this build, and for an exception during shutdown: silence is not proof of cleanliness.
- Compare process memory with tracked live allocations (chapter 12, section 12.3). Rising tracked totals call for ownership/cache analysis; flat totals redirect attention outside those tracked allocations or toward fragmentation. An allocation dump is evidence about the tracked heap only.
- Compare with a second instrument in a separate diagnostic configuration, knowing that its allocator coverage and check timing may differ. A disagreement is a question, not a verdict.
- Check the module boundaries if the finding is a resource. With a shared memory manager, an already-unloaded allocating DLL can also make an otherwise real memory leak's stack unreadable.
- Place the block in the shutdown order: freed after the check by the runtime, by lazy finalization of a language runtime, or by the tool itself? Is somebody else's suppression option enabled?
- Be clear which question you are asking: "does this block have a release path I control" - the tool's own workflow, chapter 22, section 22.9 - or "is my process's memory growing", the counters' question. If you register a leak anyway, write down why, keep it non-cumulative, and revisit it when the runtime or the library moves.
26.10 Where the evidence stops
The thread-description buffer has a vendor reproduction, a diagnosed missing release and positive
customer retests, including the 7.9.6.1 test build. The EVals finalization case has a reproduction and
an offered 7.7.8.17 build, but no explicit retest. The FastMM5 shutdown-symbol case ends with a staff
diagnosis and bridge-removal advice. Those are three different evidence levels.
For runtime cleanup, a recommendation to ignore one non-cumulative block belongs to the investigated runtime and build. Preserve that boundary in your own suppression notes, and revisit it when the runtime or library changes. A report establishes the tracked allocation's state at a particular instant; deciding whether it represents actionable growth requires the ownership and timing work above.