Skip to content

Investigate native allocator lifetime failures during scene cleanup #393

Open

Description

DISCLOSURE: LLM-GENERATED TEXT

Later observation — 2026-10-18: the separately registered campaign 39132725629, source d32549a0153429a3e1b7ae65db07b65b4c2f18e7, passes the SINGLE_THREADED Standalone job. Its verified artifact 12139248791 is 858,528 bytes, SHA256 39252247927601449c2cdfa1719440b8dde2d78ee5053b6d6b4a8e52627cd547. XML records 18,383 total / 16,628 passed / zero failed / 23 inconclusive / 1,732 skipped, root Skipped:Ignored. The previously affected LoadedSceneRetrievalBorrowsActiveAndNonactiveScenes(True,7) passes with seed 983445584. This adds a passing observation without identifying the allocation owner or repairing the original occurrence. No warning suppression, retry or scene-helper source fix is claimed; cause and remaining acceptance stay open.

Current scope and acceptance

The allocator lifetime warning is still an unresolved failure. It occurs across scene fixtures and Unity versions; its native allocation origin is not established. A passing run elsewhere does not prove the warning harmless or classify the cause. Preserve unexpected-warning assertions while investigating.

New retained recurrence — 2026-10-18: campaign 39125782708, SINGLE_THREADED job 117670565816, source bc6511cc86a75dc5a7c44929284a0f8f8438313d, fails Standalone SceneHelperLifetimeTests.LoadedSceneRetrievalBorrowsActiveAndNonactiveScenes(True,7) in TearDown. The case seed is 876965602, with zero retry/repeat iterations. XML totals: 18,383 / 16,627 passed / one failed / 1,732 skipped / 23 inconclusive. Verified artifact 12137120952 SHA256: b3a9c5e1994dbfc0f4d620159aee1e8d17a78814ebea0add0f55bd5d86303871.

The complete player log records four allocator lifetime warnings with ages 10, 11, 6, 6; the failed case reports age six against a four-frame allowance. No allocation-origin stack is retained. The actual player command omits -diag-job-temp-memory-leak-validation; XML sibling order and second-resolution timestamps do not establish which preceding operation allocated the memory. This case creates an already-loaded scene, so the helper scopes borrow synchronously; the common test teardown performs its unload. Do not attribute this recurrence to the helper owned-load/unload path without evidence. Configure/build report counters contain no crash headers, while license cleanup and lock release are healthy; those observations do not resolve the lifetime error. No retry, warning suppression or source change was used to make this occurrence pass. Next: a controlled allocation-diagnostics reproduction using the existing runner flag, with allocation-origin evidence and matching direct SceneManager/helper lifecycle controls. The cause and full acceptance remain open.

PR #1025 retains another occurrence on commit 2c9784a6: Unity 6000.6 job 117548232907, Standalone SceneHelperTests.GetAllObjectsOfTypeInSceneReturnsEmptyWhenTypeMissing, TearDown, permitted lifetime 4 frames / age 5. The verified artifact and result counts are retained. The failure has no allocation-origin stack. The scene helper awaits load and owned-scene unload; its fixture tracks asynchronous disposal. Those facts locate the affected lifecycle but do not identify the allocation owner.

The single same-head repeat passes, including the previously failed scene case, with confirmed healthy cleanup. PR #1025 was merged as 5b025285 after current-head checks passed. The original failure remains unresolved; no source or warning assertion changed for the repeat. Verified repeat artifact 12121704400 has SHA256 5f81c2d274e0265d20b87057570528b75b1deb5246723bed5296676a07279a56 and Standalone totals 18,394 / 16,639 passed / zero failed / 1,732 skipped / 23 inconclusive. The distinct Editor build access-violation exit remains on #343. A successful repeat is additional evidence, not a lifetime fix.

Remaining acceptance:

  • Obtain the native allocation origin and correlate it with scene load, unload, and fixture cleanup. Separate a package lifetime bug, test isolation bug, and engine defect using evidence.
  • Implement a fix for the identified mechanism. Preserve unexpected warnings and the scene behavior assertions; do not add retries or ignore rules to turn an unexplained warning green.
  • Verify the affected Standalone configurations and retain failed reports alongside later evidence. A passing rerun alone does not resolve this issue.

Historical report

The original report below records the earlier hypothesis and proposed options. Its claims that the warning is harmless or exclusively due to machine load are not established, and its suppression proposals are not an accepted fix.

Symptom

RelationalComponentsZenjectTests.SceneInitializerUsesMultiPassWhenConfigured fails in TearDown,
never in the body:

TearDown : Unhandled log message: '[Warning] Internal: deleting an allocation that is older than
its permitted lifetime of 4 frames (age = 6)'. Use UnityEngine.TestTools.LogAssert.

That warning comes from Unity's native temp allocator, not from any code in this repository. It
fires when a temp allocation outlives four frames, which depends on how many frames elapse while the
scene initializes — i.e. on machine load, not on logic.

Why it is a fragile check rather than a defect

Observed on run 31390509089,
6000.5.2f1 standalone on DAD-MACHINE. In the same run, the same test passed on
2022.3 standalone, 6000.3 standalone and 6000.5 playmode, each with zero leg failures. Same
commit, same code, same editor family — so nothing about the test's subject changed between the leg
that failed and the three that passed.

The test does not assert anything about allocator lifetimes. It fails because Unity's test framework
treats any unexpected log message as a failure, and this particular warning is emitted
nondeterministically by the engine during scene setup.

Options

  1. Expect it where it can occur. LogAssert.ignoreFailingMessages = true for the duration, or a
    targeted LogAssert.Expect(LogType.Warning, new Regex("permitted lifetime")). Targeted is better
    — blanket ignoring would also swallow a real warning this test should catch.
  2. Reduce the frame count during initialization, if the multi-pass path is holding a temp
    allocation across more frames than it needs to. Worth a look before suppressing: the warning is
    pointing at something real even if it is not a test failure.
  3. Shared handling. If other scene-initialization tests can hit the same warning, the expectation
    belongs in the common fixture base rather than in one test.

Option 2 first — the warning names a real lifetime, and suppressing it without looking would be
treating the symptom. If the lifetime turns out to be inherent to how Unity schedules scene
initialization, then option 1, targeted.

Not to be confused with

#392 — that one fails before
any test runs
and uploads no results.xml. This one has a full results.xml with 8,967 cases and
exactly one failure, which is how the two are told apart in ten seconds: if the artifact has a
results.xml, repository code ran and it is not an infrastructure failure.

Activity

  1. wallstop commented on Aug 11, 2026

    @wallstop
    CollaboratorAuthor

    Hit again on PR #409, and on a different, unrelated fixture — which settles the diagnosis and
    changes which of this issue's three options is right.

    WallstopStudios.UnityHelpers.Tests.Utils.RuntimeSingletonTests.InstanceComponentIsEnabled
    TearDown : Unhandled log message: '[Warning] Internal: deleting an allocation that is older than
    its permitted lifetime of 4 frames (age = 7)'
    

    Run 31465329995,
    6000.3.16f1 standalone. Fifteen of the sixteen Unity legs on that same commit passed, including
    the other three IL2CPP standalone legs and every editmode and playmode leg. results.xml is present
    with exactly this one failure, so by this issue's own ten-second test it is repository code failing,
    not infrastructure.

    Option 2 is now ruled out. RuntimeSingletonTests.InstanceComponentIsEnabled enables a component
    on a singleton; it initializes no scene and holds no temp allocation, so there is no frame count to
    reduce. The warning is not pointing at anything this repository does — it is Unity's native temp
    allocator reacting to how many frames elapsed, which is machine load.

    Option 3 is now the indicated one, and more broadly than it was written: the expectation does not
    belong in "scene-initialization tests", because this fixture is not one. Any test whose teardown can
    straddle a slow frame can hit it, so the handling belongs wherever unhandled-log strictness is
    configured for the runtime suites as a whole.

    The awkward part worth stating up front for whoever takes it: LogAssert.Expect is the wrong tool,
    because it requires the message to appear and this one appears nondeterministically. That leaves
    LogAssert.ignoreFailingMessages scoped as narrowly as possible, which is blanket within its scope —
    so the scope, not the pattern, is what has to be argued. That is a global change to test strictness
    across the runtime suites and cannot be verified in the devcontainer at all (Tests/Runtime is a
    PlayMode assembly and the MCP bridge cannot run PlayMode), so it wants its own change with the full
    matrix behind it rather than being folded into an unrelated PR.

  2. wallstop commented on Aug 20, 2026

    @wallstop
    CollaboratorAuthor

    Investigated both options the issue proposes. Option 2 has no target, and Unity has no API for
    option 1 as written.

    Option 2: there is no temp allocation to shorten. RelationalComponentSceneInitializer.Initialize
    is straight-line synchronous — rg -n 'yield return|IEnumerator|Coroutine' Runtime/Integrations/
    returns nothing — and the multi-pass branch's only engine call, FindObjectsOfTypeShim, returns a
    managed Object[]. Everything else there is a pooled managed collection. Repository-wide there is
    zero Allocator.Temp, Allocator.TempJob, NativeList or NativeHashMap; the only
    Unity.Collections use anywhere is GetRawTextureData<T> in ColorExtensions, synchronous and
    unrelated. The test's yield return null happens after Initialize() has fully returned, so
    nothing the production code allocates can outlive the call, let alone four frames.

    The warning comes from the engine during CommonTestBase's multi-frame teardown, which pumps frames
    with an UnloadSceneAsync in flight (CreateTempScene registers it as tracked async disposal).

    Four fixtures can reach it, all through the same LogAssert.NoUnexpectedReceived() at
    Tests/Core/CommonTestBase.cs:641, which is PlayMode-gated: RelationalComponentsZenjectTests,
    RelationalComponentsVContainerTests (which has a structurally identical multi-pass test),
    RelationalComponentsReflexTests and SceneHelperTests. So a per-test expectation would have to be
    pasted into four places and would rot on the fifth — the fix belongs in the base, as option 3 says.

    But LogAssert has no tolerate-if-present API. Enumerated in a live Unity 6000.4.6f1, its
    entire public surface is:

    Expect (4 overloads) | NoUnexpectedReceived | ignoreFailingMessages
    

    Which rules out both of the issue's option-1 forms:

    • LogAssert.Expect(LogType.Warning, new Regex("permitted lifetime")) is mandatory-match. The
      warning is nondeterministic — it passed on three of four legs in the cited run — so expecting it
      inverts the flake: the test then fails on every leg where the warning does not appear.
    • ignoreFailingMessages = true is blanket, and CommonTestBase.cs:1103-1104 already records why
      that was rejected: it masks genuine regressions.
    • ExpectError cannot see this message at all. Its ExpectedErrorSuppressingHandler sits on
      Debug.unityLogger.logHandler, which only intercepts managed Debug.Log* calls; this is a native
      log that reaches LogAssert without passing through it. Using it here would suppress nothing and
      then fail teardown as "never matched".

    That leaves one implementable option: reflect into UnityEngine.TestTools.Logging.LogScope.Current
    and drop matching entries from FailingLogs before NoUnexpectedReceived(). It is an internal UTF
    type, so it is fragile across Test Framework versions, and it would sit in the base class every
    fixture inherits — which is why I have not shipped it unilaterally. If you want it, it should be
    guarded so that a failed lookup falls through to today's behaviour rather than silently passing.

  3. wallstop commented on Aug 26, 2026

    @wallstop
    CollaboratorAuthor

    RCA, and a fix that was built, measured, shipped to CI, and reverted

    The failure site is this repository's own guard

    CommonTestBase.UnityTearDown calls LogAssert.NoUnexpectedReceived() in PlayMode as a
    cross-test log-pollution guard. Measured on 6000.4.6f1: a bare [Warning] inside a LogScope
    fails that call
    with UnhandledLogMessageException. So the engine's temp-allocator warning does
    not need to be an error to redden the test; any warning at all does.

    Option 2 in this issue is refuted

    The issue proposes looking first at whether the multi-pass path holds a temp allocation longer than
    it needs to. It holds none. There is no Allocator.Temp, NativeArray, NativeList or TempJob
    on any path the test touches; the only native allocations anywhere in Runtime/ are
    texture.GetRawTextureData<T>() in ColorExtensions. The warning is the engine's own scene
    machinery, and nothing in this repository can shorten its lifetime.

    What was built

    A tolerance that is not ignoreFailingMessages: count the engine messages that arrive while the
    framework's own per-test scope is open, then register exactly that many LogAssert.Expect
    expectations before the guard runs. Six properties were measured on 6000.4.6f1 to make that safe:

    question measured
    does a bare [Warning] fail NoUnexpectedReceived? yes, UnhandledLogMessageException
    does Expect match a log already recorded? yes
    is one expectation enough for two occurrences? no -- one per occurrence
    does an unrelated warning still fail afterwards? yes
    does an expectation matching nothing fail? yes, UnexpectedLogMessageException at end of scope
    does ignoreFailingMessages change retroactive matching? no

    Against the 6000.4.6f1 editor the fixture ran 10/10 and the protocol fixture 16/16.

    Why it was reverted

    The retroactive property is not universal. On 2021.3.45f1 PlayMode, CI failed on the very
    fixture that proves the mechanism:

    TearDown : Expected log did not appear: [Warning] Regex: deleting an allocation that is
    older than its permitted lifetime of \d+ frames
    

    The expectation was registered for a message that had demonstrably arrived -- the handler counted
    it -- and that Test Framework version did not reconcile it. That turns a warning which sometimes
    fails a test into one that always does. Worse than the flake.

    So the finding to carry forward is a constraint, not a fix: no fix for this issue may depend on
    LogAssert.Expect semantics
    , because this package ships across four editor versions and that
    behaviour differs between them. It is the same class as
    #553: a confident answer from
    one editor for a question that is resolved out of Unity's own implementation.

    What is left for whoever takes it

    Of the issue's own options, 1 and 3 survive:

    • LogAssert.ignoreFailingMessages = true scoped as narrowly as the fixture allows. Blunt, and the
      issue is right that it swallows real warnings, but it is version-independent.
    • A targeted LogAssert.Expect inside the one fixture, accepting that it fails when the warning
      does not appear -- which on the evidence is most of the time, so this is not viable alone.
    • Replacing the base's NoUnexpectedReceived() guard with one built on this repository's own
      Application.logMessageReceivedThreaded record, which is the only route that owns its own
      semantics across editors. That is a larger change than this issue is worth on its own, but it is
      the one that would work.

    Frequency, for whoever prices those: one leg of one run
    (31390509089),
    with the same commit green on 2022.3 standalone, 6000.3 standalone and 6000.5 playmode.

  4. wallstop commented on Aug 26, 2026

    @wallstop
    CollaboratorAuthor

    Option 1's cheaper half is eliminated too, measured

    Follow-up to the RCA above, because the obvious next idea after "targeted Expect is not
    version-portable" is "then just set ignoreFailingMessages when the tolerated warning shows up".

    It does not work. Measured on 6000.4.6f1, driving a real LogScope with a control:

    case outcome
    A: warning arrives, nothing done (control) FAILS UnhandledLogMessageException -- the probe can see a failure
    B: warning arrives, then LogAssert.ignoreFailingMessages = true FAILS UnhandledLogMessageException
    C: does a fresh scope inherit the flag? no, it reads False

    The flag is consulted when a log is added to the failing set, not when the set is evaluated. By
    the time anything in this repository can react to the warning, it is already recorded, and nothing
    set afterwards removes it.

    So the option list is now:

    • targeted LogAssert.Expect after the fact -- works on 6000.4, not on 2021.3 (above).
    • ignoreFailingMessages after the fact -- works on no editor; measured.
    • ignoreFailingMessages = true before the warning can arrive, i.e. for the whole PlayMode
      test. This is the only lever that works, and it is the blanket suppression this issue explicitly
      rejected: it would disable unexpected-log detection across the entire PlayMode suite, including
      the cross-test pollution guard CommonTestBase was built to provide.
    • Replace that guard with one built on this repository's own
      Application.logMessageReceivedThreaded record, so the semantics are ours rather than the Test
      Framework's. This is the only route that both fixes the flake and keeps the guard, and it is a
      larger change than one nondeterministic warning justifies on its own -- but it is the answer if
      the warning starts appearing on more than one leg of one run.

    Recorded so the next attempt starts from three eliminated options rather than repeating them.

  5. added a commit that references this issue on Aug 28, 2026
    dddd0b1
  6. wallstop commented on Oct 17, 2026

    @wallstop
    CollaboratorAuthor

    DISCLOSURE: LLM-GENERATED TEXT

    Reproduced during session closeout in run 39007855641, job 117323949587, head 7508f55a, Unity 6000.6.0f1 Standalone. SceneHelperTests.GetObjectOfTypeInScene fails only in TearDown on the native warning Internal: deleting an allocation that is older than its permitted lifetime of 4 frames (age = 5). The player log also contains age=6. XML reports 18,302 cases: 16,546 passed, one failed, 1,732 skipped, 23 inconclusive. There are no native crash headers in the full job log; license cleanup and lock release are confirmed healthy.

    Artifact 12092951328 is bound to this run/head. Downloaded ZIP SHA-256 is 97666be37dda368f3b9917767e7e310dcb3faa6d68ee4f62d1e83ba4f34f5b5b. XML identifies exactly this teardown warning, with no assertion failure or managed stack trace. All Runtime/Editor/test C# and analyzer inputs are unchanged from main c5ce8ed4; #1020 changes CI scripts and documentation only.

    The #586 fix leaves the native path uncovered: Tests/Core/CommonTestBase.cs:304 seeds a tolerated warning in ExpectedErrorSuppressingHandler, which handles managed logger calls, while UnityTearDown still calls LogAssert.NoUnexpectedReceived() at line 697. The previous investigations above already explain why native engine logs bypass that handler and why retroactive expectations and blanket suppression are unsuitable. The current artifact shows the exact class remains possible after the issue was closed.

    Reopening this defect. A current-head retry of only the failed normal CI job can qualify the affected tooling PR's checks; it does not fix this warning or close this investigation. Per the user's wrap-up instruction, no broader test-framework implementation, warning suppression, or new native experiment is being started. Acceptance remains a native-log-aware guard that preserves unrelated unexpected-log failures across supported editor versions.

  7. wallstop commented on Oct 17, 2026

    @wallstop
    CollaboratorAuthor

    DISCLOSURE: LLM-GENERATED TEXT

    The unchanged-head single-job retry passes. Its fresh Standalone artifact 12093684375, created 07:54:35 UTC in run 39007855641, matches SHA-256 5a616190a71ecae2cc8549deac7cf42df98d476d9b13721b325a83e117c59229. XML reports 18,302 total, 16,547 passed, zero failed, 1,732 skipped and 23 inconclusive; SceneHelperTests.GetObjectOfTypeInScene explicitly passes. The original failed artifact 12092951328 remains retained and its checksum is recorded above.

    This demonstrates another successful execution, not a fix for the native warning or the test guard. No code, warning filter, or test selection changed. NUnit seeds differ between the two normal executions. This issue stays open.

  8. wallstop commented on Oct 17, 2026

    @wallstop
    CollaboratorAuthor

    DISCLOSURE: LLM-GENERATED TEXT

    The same native allocator warning recurred in #1022 Unity 6000.5 job 117345529671, unchanged head 27743eeb. This time the failed case is SceneHelperLifetimeTests.LoadedSceneRetrievalBorrowsActiveAndNonactiveScenes(False,7), again in TearDown, with lifetime=4 frames and age=14. No Runtime/Editor/test C# or analyzer inputs differ from main. Full-log inspection shows no native crash headers and confirms healthy license cleanup and lock release.

    Fresh failed artifact 12094819473 is bound to this run/head. The ZIP was downloaded and byte-verified against SHA-256 157f0e4dec0d41c796b5457a46c04ec26e03a1b3963eb91c091711fa807a4638; XML identifies exactly one failed case and the warning above. This is additional evidence that the shared native-log guard defect remains open across fixtures and Unity versions. No warning suppression or harness implementation is being added during closeout. The single failed normal leg can be retried only once the containing workflow's remaining Unity 2022 job finishes; such a retry will not close this issue.

  9. wallstop commented on Oct 17, 2026

    @wallstop
    CollaboratorAuthor

    DISCLOSURE: LLM-GENERATED TEXT

    The unchanged-head Unity 6000.5 retry also passes. Its fresh Standalone artifact 12095573638, created 09:06:24 UTC in run 39007870117, matches SHA-256 ec83814fb66b56d7285ebb4faa076873f343bc34d5efd8d2433a74763774f110. XML reports 18,302 total, 16,547 passed, zero failed, 1,732 skipped and 23 inconclusive; the previously failed SceneHelperLifetimeTests.LoadedSceneRetrievalBorrowsActiveAndNonactiveScenes(False,7) case explicitly passes. The original failed artifact and checksum remain retained above.

    NUnit seeds differ between the two normal executions. No source, test selection, or warning policy changed. This adds successful-execution evidence; it does not fix the native warning or its test guard. This issue remains open.

  10. wallstop commented on Oct 18, 2026

    @wallstop
    CollaboratorAuthor

    DISCLOSURE: LLM-GENERATED TEXT

    The allocator lifetime warning recurred on PR #1025 head 492b61fc5869d01daa1a88d3dab80039980355be, SINGLE_THREADED Unity 6000.6 job 117502936658. Standalone XML reports 18,187 total, 16,431 passed, one failed and 1,732 skipped. The failure is SceneHelperTests.GetObjectOfTypeInScene in TearDown: a native allocation exceeded its four-frame lifetime (age 7). The final mode verifier and aggregate correctly fail; the license cleanup and lock-release steps succeed.

    This is another affected scene fixture, not evidence that the warning is harmless or fixed. No assertion or warning policy has been weakened. The original failed run remains available while subsequent protobuf and rule-snapshot changes are developed for the same PR.

  11. wallstop commented on Oct 18, 2026

    @wallstop
    CollaboratorAuthor

    DISCLOSURE: LLM-GENERATED TEXT

    The same allocator-lifetime warning recurred on PR #1025 head 2c9784a63ae1b6286678d8d2adf751ed3920313d, regular Unity 6000.6 job 117548232907. Its sole failed case is SceneHelperTests.GetAllObjectsOfTypeInSceneReturnsEmptyWhenTypeMissing, in TearDown: a native allocation exceeded the four-frame lifetime (age 5). The test body checks an empty result after scene loading; its tracked disposal unloads the owned scene. The report contains no allocation-origin stack, so this identifies the observed lifetime failure, not its native cause.

    Failed Standalone artifact 12120886610 is bound to this run/head and independently matches SHA-256 053711a5005c45cd74f182980eec8c90f04150e7b25184ffe2213feb9d98ff11. XML reports 18,394 total: 16,638 passed, one failed, 1,732 skipped and 23 inconclusive. EditMode and PlayMode report zero failures, and WProto-only Standalone passes 82/82. License cleanup and lock release are confirmed healthy.

    No scene assertion or native-warning policy was weakened. The other matrix jobs are still running. Any later successful execution will be additional evidence, not a fix or closure of this issue.

  12. changed the title [-]test: a Zenject scene test fails on Unity's temp-allocator lifetime warning, nondeterministically[/-] [+]Investigate native allocator lifetime failures during scene cleanup[/+] on Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions