A freeze report does not prove that the application can never recover. It records a detector's observation: a watched thread failed a responsiveness check, or Wait Chain Traversal found a cyclic wait. A busy UI thread, a suspended laptop and a genuine deadlock can require very different remedies.
Chapter 14 explains the detectors and their APIs. Here, start from what happened before and after the report: was the watched thread dispatching messages, was the machine running, and could the reporting thread itself make progress? Keep a customer observation, a staff proposal, a reproduced defect and a successful retest separate. A closed conversation is not evidence that a proposed fix worked.
28.1 What a freeze report asserts, and what it does not
For message-based detection, the message The application seems to be frozen. means a responsiveness probe was not answered within the configured interval. It does not measure useful work in every thread or establish a wait cycle.
A detailed staff explanation dated 2021-08-04, in a EurekaLog 7.9.4.1 / Delphi 10.4 / Win32 case,
describes a private window and posted messages rather than WM_NULL. Its reason is important:
a wait loop can process sent messages while leaving posted work unserved, so answering a sent probe
need not demonstrate normal UI dispatch. A later answer and explanatory documentation name WM_NULL
instead. That disagreement does not establish an implementation change; section 28.5 gives the
later case's date and reported version. Do not base a heartbeat workaround on a particular message ID.
Attribution is the second thing to read carefully. The call stack in a freeze report is built for the thread the check was told to watch - by default the main thread (chapter 14, section 14.4) - so the "exception thread" is the watched thread by construction, and lines for other threads, when they are there, come from a separate step with its own caveats (chapters 11 and 15).
28.2 A main thread that is working, not frozen
With EurekaLog 7.9.4.1, Delphi 10.4 and Win32, a developer needed to monitor an external-library call that could either take a long time or genuinely hang. When it merely ran slowly, the application eventually continued behind the freeze dialog. The developer wanted the dialog to disappear, or a way to reset the timer from the main thread.
Support first suggested increasing the timeout or bracketing work with PauseFreezeCheck and
ResumeFreezeCheck. The customer confirmed that the API pair worked, but explicitly reported that
false freeze messages continued in the slow regions that still needed monitoring. An earlier
message also described one missed hang immediately after resuming checks; the suggested explanation
about a sleeping watchdog was the customer's interpretation, not a diagnosed timing defect.
The distinction changed the advice. Pausing the detector through the troublesome call would also
hide the genuine hangs the developer needed to catch, so the customer rejected it for that region.
Support then suggested a more suitable timeout or moving the work to a worker thread and reporting
an exceeded application deadline with RaiseFreezeException. The customer said they would
investigate; no successful implementation or end to the false reports is recorded.
Use pause/resume only for an interval you deliberately choose not to monitor, with balanced calls
in try/finally as chapter 14 shows. If slow and stuck must be distinguished inside the same
operation, define an acceptable deadline or observable progress condition instead. A one-off
heartbeat cannot establish that the next long operation is still making progress.
One variant has no confirmed cause. In a case whose report did not fit the pattern, the staff reply asked whether a EurekaLog events component was in use, what its handlers customised, and whether the main form was minimised or hidden behind a modal dialog - and no answer is recorded. Those questions are a hypothesis about how "the application" looks from a window message's point of view: a report of this shape has to be explained from its own stack, not from the pattern.
28.3 A machine that is not running
A Windows 11 24H2 laptop running a Delphi 13 Win64 application with EurekaLog 7.14.2.1 Update 2 reported a freeze after sleeping longer than its five-minute timeout. The customer described an idle instance whose captured stack was in the message pump, two instances reporting together, and normal main-thread activity after resume. These are customer-reported observations, not an independent inspection of the reports or logs.
The proposed explanation was Modern Standby: suspended wall-clock time was apparently being counted as UI unresponsiveness. The customer cited the 7.2.5.0 release note about sleep/hibernation false positives, but that older fix does not prove coverage of this Windows power-state scenario. Neither the Modern Standby root cause nor a distinction between precisely which power states the old fix handles was established by a vendor reproduction.
There is also a practical race: the application already used power notifications and pause/resume, but its window-message notification was described as arriving about four seconds after freeze detection. A guard delivered through the suspended UI thread may arrive too late.
Support proposed pause/resume as a temporary workaround, said a fix was intended for a subsequent
build, and supplied a test project followed by a changed EFreeze.pas attachment. No customer retest
or named successful release follows. There is no evidence here that a different callback mechanism
is supported or would win the race. Record power transitions alongside your application activity,
and exercise real suspend/resume on the affected machines before trusting a workaround.
A quieter member of the same group is the application with no UI to watch. A case about a Win32 service produced the same explanation from two staff answers: the check asks the main thread to answer, and in a VCL service that thread does nothing but run the service startup and wait, so the check has nothing meaningful to measure - while the deadlock check, which needs no window, still works, provided the configured timeout is shorter than the time the service manager allows before giving up on the service. The suggestion was to run the work in background threads derived from EurekaLog's own thread class, so that the threads doing the work are the ones whose state EurekaLog tracks (chapters 15 and 21).
28.4 A report that builds itself into a freeze
The third group is the freeze in which the reporting machinery is itself the second victim. A case's
report showed that EurekaLog had caught a freeze - the main thread not responding - and then frozen
while creating the report about it, because a system critical section the report needed was held by
that same main thread; the diagnosis is a staff reading of the supplied report. First, Wait Chain
Traversal reported no deadlock: other threads were waiting on a critical section owned by the main
thread, but that thread was not waiting on anything, so the cycle the check looks for did not exist. The
staff answer names the shape a live lock rather than a deadlock, and adds the limitation that a hang
caused by a system critical section cannot be diagnosed from inside the process at all - an external
debugger is needed. Second, the same answer recommends the PauseFreezeCheck bracket (chapter 14) around
the heavy main-thread work: the medicine of section 28.2, applied where the freeze was real. The recorded
outcome is a proposal.
28.5 Freezes that produce nothing
A report that never arrives needs a separate investigation. Distinguish a disabled check, an unobservable stall and a reporter that cannot finish.
The debugger. The Disable under debugger option matters independently of whether hang detection
is enabled. A staff answer states that freeze checks are skipped when that option is on and
IsDebuggerPresent returns True. Test your intended deployment mode as well as the IDE launch;
an intentional infinite loop is a control for that configuration, not proof that every real stall
will be detectable.
The wrong detector for the shape. A staff answer on a case about a deliberate deadlock states the
limits plainly: detection covers the main thread ceasing to respond to messages, and Wait Chain
Traversal reporting a cyclic wait, and nothing else - "any other type of freeze" is not detected, live
locks among them. A hang whose evidence lives in a worker thread may therefore be invisible to the
default configuration, and the answer's remedy is the self-reporting route of chapter 14, section 14.4:
if your code can recognise the stall - a deadline, a timeout, a missing heartbeat - call
RaiseFreezeException for the thread that did not finish.
A control hang reported, the real hang did not. In a case dated 2026-03-26, the support form reported EurekaLog 7.7.6.0, Delphi 10.3 Rio, Win64. An intentional infinite loop produced a report on the affected machine, but a real main-thread freeze during background cleanup did not.
Support listed the debugger exclusion and hypothesised that the application might still be
processing WM_NULL probes, then requested a thread snapshot. No resolution follows. Chronologically,
this WM_NULL answer is later than the 2021 non-WM_NULL explanation in section 28.1, although
the submitted EurekaLog version is older. Do not reverse the dates, treat the version field as proof
of the implementation, or invent a message-protocol change to reconcile them. The useful next
step is an external thread snapshot during the actual stall (chapter 4), not assumptions about
which messages it must be processing.
28.6 The thread that never reports
A background-thread failure can be missed for another reason: EurekaLog may be inactive in that
thread. Background-thread coverage depends on the selected activation policy and the application
type; enabling EurekaLog in the application does not automatically prove coverage of every worker.
Support repeatedly directs developers to check that state before changing stack settings. Activate
the threads you own deliberately, rather than indiscriminately reporting exceptions belonging to
frameworks or the operating system. The per-thread calls are SetEurekaLogStateInThread and NameThread
(chapters 6 and 21), and they are the two lines the documentation's own template puts at the top of a
thread's function.
The cases that follow that path show the repair and its limits. One documents a server-style background
thread whose exception produced no stack at all until SetEurekaLogStateInThread(0, True) was added;
the call produced a trace that still lacked information the developer needed, and the staff answer
explained where the call stack lives on the exception information object and promised a workaround in a
later build (chapters 8 and 11). Another case, whose worker threads came from a framework, received the
same question about enabling EurekaLog for all background threads including the framework's. Neither
records an outcome for the underlying complaint.
A third group is the exception that was collected and never processed. Support explained
the two-stage model: raising an exception collects its information, and handling it is a separate act,
so a thread whose handling stage does nothing leaves the collected information unused. TThread is the
canonical example - it stores an exception escaping Execute into FatalException and invokes no
handler (chapter 21, section 21.4). The answers are consistent about the order of repair: make the
application handle thread exceptions correctly without EurekaLog first, then add the EurekaLog call.
One explains why the product does not simply do it for you - an option that silently handles every
thread exception breaks code that reads FatalException, does nothing in a build without EurekaLog, and
needs low-level hooks - and names TThreadEx's AutoHandleException as the version you can use.
A genuine activation defect, followed by a narrower retest. In EurekaLog 7.8.2.39 RC 3, Win32, with Rio 10.3 identified in the support form and C++Builder code discussed in the messages, the developer tried a lightweight DLL, explicit handling and per-thread activation. Support identified a copy-paste defect in the activation routine and proposed its low-level counterpart as a workaround. The customer reported that the sample worked and planned to transfer the change to the real application.
Support later offered 7.8.2.53 beta, saying it should fix the discovered issues. The conversation then moved to C++Builder Release/Clang handling and incomplete reporting; a separate source-level workaround produced a report in the sample while exception-code and stack questions remained. Preserve the positive activation-workaround retest without promoting it into confirmation of all production or Release configurations. Do not copy an internal low-level workaround into current code instead of using the supported thread APIs in chapter 21.
28.7 Per-thread activation and naming as the fix
For a worker that your application owns, first make activation and naming explicit. These two calls
belong inside the worker's execution routine, using EBase; they are not a complete thread handler:
NameThread('Worker: import');
SetEurekaLogStateInThread(0, True);
The name makes the report attributable; the state enables collection for that thread. Neither call guarantees that an exception reaches the processing stage. For a dedicated worker and for a reused pool thread, use the complete lifetime/restore patterns in chapter 21, sections 21.3 and 21.4. In particular, do not leave a pooled thread with the name and activation state of a previous task.
What these calls do not fix is the half of section 28.6 that has nothing to do with state: if the framework that
owns the thread swallows the exception, or it sits in a property nobody reads, enabling EurekaLog
changes nothing, because the processing stage is the one that calls EurekaLog. The documented ways to
get a TThread's exception into a handler are chapter 21, section 21.4.
28.8 Deadlock evidence, and how to read it
When the report says a deadlock was detected, what has been observed is a cyclic wait reported by Wait Chain Traversal. Three properties matter when interpreting that evidence.
Which threads it covers. A staff answer says hang reports include EurekaLog-enabled threads regardless of the ordinary option for capturing their stacks. Ensure the participants you need to investigate are enabled and named; an incomplete report cannot establish that omitted threads are uninvolved. See chapter 21 for activation policy.
What a stack pair means. In one case the developer read a deadlock from two stacks, one waiting on a lock and one holding it, and the staff answer corrects the reading: there is no deadlock in the information provided, because the holder is not itself waiting - it is an ordinary lock, and it will be released. The distinction to keep is cycle versus wait, and the same answer asks for a snapshot of all threads, because a deadlock can only be established from what every participant is doing.
That the report can be the cause. One case is a controlled observation with a staff diagnosis: two threads building call stacks at the same time deadlocked occasionally, because reading another thread's stack means suspending it; if the suspended thread holds a memory-manager lock needed by the reporter, the reporter can wait indefinitely. Additional stack capture is not a free diagnostic improvement. Enable it for a reason and retain an external capture route when the process cannot report on itself.
Historical answers distinguish the older options that keep threads paused during processing from short suspension for context capture. That distinction does not prove every later stack-building operation is lock-free. The observation to preserve is narrower: collecting a stopped thread's state and constructing the report are separate stages, either of which can affect a concurrent program.
The mechanism behind all of this is chapter 14 (the detection classes and their documented use of
SendMessageTimeout and Wait Chain Traversal), chapter 15 (the per-thread record the report is built
from) and chapter 21 (per-thread state, thread classes, and how a thread's exception reaches a
handler); chapter 20, section 20.9, is the reading side, on how a hang or deadlock is presented.
28.9 What to check, in the order that saves work
- Whose thread is the report about? Message-based checking watches the configured thread, normally the main thread (section 28.1); a worker's progress may need a separate deadline.
- Was the interval spent working, or not running at all? Long non-dispatching work (section 28.2) and OS suspend (section 28.3) can both exceed a responsiveness timeout. Correlate application activity and power-transition times instead of diagnosing from the dialog alone.
- Could the detector see this shape at all? Check debugger exclusions. A non-cyclic wait or live lock is not a WCT deadlock; message responsiveness is a different test. Use an external snapshot when the in-process report is missing or inconclusive.
- Is the failing thread enabled, named and correctly handled? Naming and activation help
collection and attribution (section 28.7), but exception processing still needs a handler.
RaiseFreezeExceptioncan report an application-detected stall (chapter 14); a pair of waiting stacks establishes deadlock only when the relevant wait relationships form a cycle (section 28.8).