Description
DxMessaging OnValidate Reentrant-Import Crash � Root Cause and Fix Spec
Summary
The Unity Editor crashes natively in GuidReservations::Reserve (reached via
ImportAtPathV2) on domain load, reproduced on Unity 6000.4.5f1. The root
cause is a reentrant AssetDatabase.ImportAsset call originating from
ScriptableObject.OnValidate, which Unity invokes synchronously within the
asset import/load operation for the settings asset during domain-load
initialization, before that operation returns.
- Affected package:
com.wallstop-studios.dxmessaging
- Affected version:
3.0.1
- Package cache hash referenced in this document:
57ed15f89e13
- Crash class: native editor crash (not a managed exception � the
try/catch in TryRegenerateSidecar cannot catch it)
This is a hand-off artifact for the separate DxMessaging package repository. All
file:line references below are pinned to v3.0.1 / package cache hash
57ed15f89e13; the maintainer should re-confirm them against current main
before applying the fix.
Environment / Crash Signature
| Field |
Value |
| Unity Editor |
6000.4.5f1 |
| Platform |
Reproduced on the editor (Linux CI editor image and desktop editors) |
| Trigger |
Domain load / assembly reload while Assets/Editor/DxMessagingSettings.asset exists on disk |
| Native frame |
GuidReservations::Reserve |
| Native caller |
AssetDatabase import path ImportAtPathV2 |
| Managed entry point |
DxMessagingEditorInitializer static constructor ([InitializeOnLoad]) |
The crash is a hard native abort. It happens during domain reload, so it is
not catchable by the managed try/catch in
DxMessagingSettings.TryRegenerateSidecar � the process is gone before control
returns to managed code.
Verified Call Chain
Confirmed against the package source at cache hash 57ed15f89e13, v3.0.1:
DxMessagingEditorInitializer..cctor � Editor/DxMessagingEditorInitializer.cs:18
(the [InitializeOnLoad] attribute is at Editor/DxMessagingEditorInitializer.cs:13).
Runs on every domain load.
- �
ApplyEditorSettings() � called at Editor/DxMessagingEditorInitializer.cs:21;
method defined at Editor/DxMessagingEditorInitializer.cs:48.
- �
DxMessagingSettings.GetOrCreateSettings() � called at
Editor/DxMessagingEditorInitializer.cs:51; method defined at
Editor/Settings/DxMessagingSettings.cs:163.
- �
AssetDatabase.LoadAssetAtPath<DxMessagingSettings>(...) �
Editor/Settings/DxMessagingSettings.cs:165. Loading the asset
deserializes it, and Unity fires OnValidate synchronously within that
load operation, before it returns.
- �
DxMessagingSettings.OnValidate() � Editor/Settings/DxMessagingSettings.cs:229.
- �
TryRegenerateSidecar() � called at Editor/Settings/DxMessagingSettings.cs:235;
method defined at Editor/Settings/DxMessagingSettings.cs:291.
- �
DxMessagingBaseCallIgnoreSync.RegenerateSidecar(this) � called at
Editor/Settings/DxMessagingSettings.cs:295; method defined at
Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:48. Its guard
if (EditorApplication.isUpdating || EditorApplication.isCompiling) is at
Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:55.
- �
RegenerateSidecarCore(settings) � called at
Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:57 (deferred) and
Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:61 (synchronous); method
defined at Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:64.
- �
File.WriteAllText(...) at Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:86
+ AssetDatabase.ImportAsset(SidecarAssetPath) at
Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:87.
- � native crash in
GuidReservations::Reserve via ImportAtPathV2 � the
ImportAsset call is reentrant with the in-flight asset import /
deserialization that LoadAssetAtPath is still inside.
Root Cause
OnValidate is a serialization-time callback. When
AssetDatabase.LoadAssetAtPath deserializes DxMessagingSettings.asset during
domain-load initialization, Unity invokes OnValidate after deserialization
completes but still synchronously within the asset import/load operation's
scope, before that operation returns. OnValidate then synchronously walks down
to AssetDatabase.ImportAsset, which re-enters the asset import subsystem while
an import is already in flight.
Unity 6000.4 added stricter reentrancy guards in the native import path. A
reentrant import is no longer tolerated: GuidReservations::Reserve aborts the
process instead of logging an error. The same code path was merely fragile on
earlier Unity versions; on 6000.4 it is a hard crash.
Why the existing two-flag guard is insufficient
RegenerateSidecar guards with:
if (EditorApplication.isUpdating || EditorApplication.isCompiling)
{
EditorApplication.delayCall += () => RegenerateSidecarCore(settings);
return;
}
RegenerateSidecarCore(settings);
(Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:55-61)
This is insufficient for two reasons:
- The flags do not cover the crash window.
EditorApplication.isUpdating
and EditorApplication.isCompiling do not reliably report true during
the domain-load / asset-import-worker phase in which OnValidate fires here.
With both flags false, the guard takes the else branch and calls
RegenerateSidecarCore � and therefore AssetDatabase.ImportAsset �
synchronously, inside the deserialization callback. That is the reentrant
import that crashes.
- The deferred closure captures state across a reload boundary. Even when
the guard does defer, the lambda () => RegenerateSidecarCore(settings)
captures the settings object. EditorApplication.delayCall runs after the
domain reload completes; by then the captured settings reference may have
been invalidated by the reload, and the closure is never unsubscribed, so
repeated OnValidate invocations can stack duplicate one-shots.
In short: gating the deferral decision on isUpdating/isCompiling is the bug
� those flags are not a valid proxy for "is it safe to mutate the
AssetDatabase". The only safe place to check editor state is inside the
deferred one-shot when it actually runs, not at the point of scheduling.
Recommended Fix
Apply all of the following in the DxMessaging package:
- Remove the
AssetDatabase mutation from the OnValidate path entirely.
OnValidate (Editor/Settings/DxMessagingSettings.cs:229) must not lead �
directly or transitively � to AssetDatabase.ImportAsset,
AssetDatabase.SaveAssets, AssetDatabase.CreateAsset, or any other
AssetDatabase mutation. The same applies to AddIgnoredType /
RemoveIgnoredType if they can run inside an import window.
- Trigger sidecar regeneration only from a safe context: either an explicit
user action (a menu item / Project Settings button), or a single
post-domain-reload EditorApplication.delayCall one-shot that re-checks
editor state when it runs.
- Replace the two-flag guard with unconditional deferral. In
RegenerateSidecar (Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:48-62),
do not branch on isUpdating/isCompiling to decide whether to defer �
always defer. Re-check isUpdating/isCompiling (and settings != null)
inside the deferred one-shot, and bail out if the editor is still in an
unsafe state; a later user action or the next callback will retry.
- Subscribe a named method, not a closure. Use a named one-shot that
unsubscribes itself (delayCall -= Handler; delayCall += Handler; then
delayCall -= Handler; as the first line of the handler) so duplicate
one-shots cannot stack across reload boundaries and no stale settings
reference is captured.
- Reconsider whether
AssetDatabase.ImportAsset is needed at all. The
sidecar (Assets/Editor/DxMessaging.BaseCallIgnore.txt,
Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:27) is consumed by the
Roslyn analyzer via csc.rsp -additionalfile. A plain File.WriteAllText
plus a lazy import (let Unity pick up the change on its next refresh) is very
likely sufficient; the explicit ImportAsset at
Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:87 may be unnecessary
churn. Only call ImportAsset if the sidecar genuinely must be re-imported
immediately � and even then, only from a safe (non-callback) context.
- Keep the existing content-diff short-circuit. The
File.ReadAllText / string.Equals comparison at
Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:77-84 is correct and worth
keeping � it avoids writing (and importing) when nothing changed. Retain it.
Validation / Repro Steps
To reproduce the crash on an unpatched v3.0.1:
- Use Unity 6000.4.5f1 with
com.wallstop-studios.dxmessaging 3.0.1.
- Ensure
Assets/Editor/DxMessagingSettings.asset exists on disk (open the
project once so GetOrCreateSettings creates it).
- Force a domain reload � e.g. trigger a script recompile, or use
Assets > Refresh, or restart the editor.
- Observe the native crash in
GuidReservations::Reserve (ImportAtPathV2)
during domain-load initialization.
To validate the fix:
- Apply the recommended fix above.
- Repeat steps 1�3. The editor must complete domain reload with no native
crash.
- Edit the ignored-types list via the Project Settings UI and confirm the
sidecar (Assets/Editor/DxMessaging.BaseCallIgnore.txt) is still regenerated
correctly � just from a safe context, not from inside OnValidate.
- Confirm the content-diff short-circuit still suppresses redundant writes
(editing then reverting a value should not rewrite the sidecar).
- Confirm no duplicate
delayCall one-shots stack up across repeated reloads.
References
.llm/skills/defensive-editor-programming.md
� section "Never Mutate AssetDatabase from Validation Callbacks" captures this
pattern as a reusable rule.
- Package source (cache hash
57ed15f89e13, v3.0.1):
Editor/DxMessagingEditorInitializer.cs
Editor/Settings/DxMessagingSettings.cs
Editor/Settings/DxMessagingBaseCallIgnoreSync.cs
Note: All file:line references in this document are pinned to
DxMessaging v3.0.1 / package cache hash 57ed15f89e13. Line numbers
drift between releases � the maintainer should re-confirm every reference
against the current main branch before applying the fix.
Steps to Reproduce
The stuff above
Expected Behavior
Unity happy
Actual Behavior
Unity Sad
Unity Version
6000.x
Package Version
3.0.1
Platform
Additional Context
No response
Description
DxMessaging
OnValidateReentrant-Import Crash � Root Cause and Fix SpecSummary
The Unity Editor crashes natively in
GuidReservations::Reserve(reached viaImportAtPathV2) on domain load, reproduced on Unity 6000.4.5f1. The rootcause is a reentrant
AssetDatabase.ImportAssetcall originating fromScriptableObject.OnValidate, which Unity invokes synchronously within theasset import/load operation for the settings asset during domain-load
initialization, before that operation returns.
com.wallstop-studios.dxmessaging3.0.157ed15f89e13try/catchinTryRegenerateSidecarcannot catch it)This is a hand-off artifact for the separate DxMessaging package repository. All
file:linereferences below are pinned to v3.0.1 / package cache hash57ed15f89e13; the maintainer should re-confirm them against currentmainbefore applying the fix.
Environment / Crash Signature
Assets/Editor/DxMessagingSettings.assetexists on diskGuidReservations::ReserveAssetDatabaseimport pathImportAtPathV2DxMessagingEditorInitializerstatic constructor ([InitializeOnLoad])The crash is a hard native abort. It happens during domain reload, so it is
not catchable by the managed
try/catchinDxMessagingSettings.TryRegenerateSidecar� the process is gone before controlreturns to managed code.
Verified Call Chain
Confirmed against the package source at cache hash
57ed15f89e13, v3.0.1:DxMessagingEditorInitializer..cctor�Editor/DxMessagingEditorInitializer.cs:18(the
[InitializeOnLoad]attribute is atEditor/DxMessagingEditorInitializer.cs:13).Runs on every domain load.
ApplyEditorSettings()� called atEditor/DxMessagingEditorInitializer.cs:21;method defined at
Editor/DxMessagingEditorInitializer.cs:48.DxMessagingSettings.GetOrCreateSettings()� called atEditor/DxMessagingEditorInitializer.cs:51; method defined atEditor/Settings/DxMessagingSettings.cs:163.AssetDatabase.LoadAssetAtPath<DxMessagingSettings>(...)�Editor/Settings/DxMessagingSettings.cs:165. Loading the assetdeserializes it, and Unity fires
OnValidatesynchronously within thatload operation, before it returns.
DxMessagingSettings.OnValidate()�Editor/Settings/DxMessagingSettings.cs:229.TryRegenerateSidecar()� called atEditor/Settings/DxMessagingSettings.cs:235;method defined at
Editor/Settings/DxMessagingSettings.cs:291.DxMessagingBaseCallIgnoreSync.RegenerateSidecar(this)� called atEditor/Settings/DxMessagingSettings.cs:295; method defined atEditor/Settings/DxMessagingBaseCallIgnoreSync.cs:48. Its guardif (EditorApplication.isUpdating || EditorApplication.isCompiling)is atEditor/Settings/DxMessagingBaseCallIgnoreSync.cs:55.RegenerateSidecarCore(settings)� called atEditor/Settings/DxMessagingBaseCallIgnoreSync.cs:57(deferred) andEditor/Settings/DxMessagingBaseCallIgnoreSync.cs:61(synchronous); methoddefined at
Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:64.File.WriteAllText(...)atEditor/Settings/DxMessagingBaseCallIgnoreSync.cs:86+
AssetDatabase.ImportAsset(SidecarAssetPath)atEditor/Settings/DxMessagingBaseCallIgnoreSync.cs:87.GuidReservations::ReserveviaImportAtPathV2� theImportAssetcall is reentrant with the in-flight asset import /deserialization that
LoadAssetAtPathis still inside.Root Cause
OnValidateis a serialization-time callback. WhenAssetDatabase.LoadAssetAtPathdeserializesDxMessagingSettings.assetduringdomain-load initialization, Unity invokes
OnValidateafter deserializationcompletes but still synchronously within the asset import/load operation's
scope, before that operation returns.
OnValidatethen synchronously walks downto
AssetDatabase.ImportAsset, which re-enters the asset import subsystem whilean import is already in flight.
Unity 6000.4 added stricter reentrancy guards in the native import path. A
reentrant import is no longer tolerated:
GuidReservations::Reserveaborts theprocess instead of logging an error. The same code path was merely fragile on
earlier Unity versions; on 6000.4 it is a hard crash.
Why the existing two-flag guard is insufficient
RegenerateSidecarguards with:(
Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:55-61)This is insufficient for two reasons:
EditorApplication.isUpdatingand
EditorApplication.isCompilingdo not reliably reporttrueduringthe domain-load / asset-import-worker phase in which
OnValidatefires here.With both flags
false, the guard takes theelsebranch and callsRegenerateSidecarCore� and thereforeAssetDatabase.ImportAsset�synchronously, inside the deserialization callback. That is the reentrant
import that crashes.
the guard does defer, the lambda
() => RegenerateSidecarCore(settings)captures the
settingsobject.EditorApplication.delayCallruns after thedomain reload completes; by then the captured
settingsreference may havebeen invalidated by the reload, and the closure is never unsubscribed, so
repeated
OnValidateinvocations can stack duplicate one-shots.In short: gating the deferral decision on
isUpdating/isCompilingis the bug� those flags are not a valid proxy for "is it safe to mutate the
AssetDatabase". The only safe place to check editor state is inside the
deferred one-shot when it actually runs, not at the point of scheduling.
Recommended Fix
Apply all of the following in the DxMessaging package:
AssetDatabasemutation from theOnValidatepath entirely.OnValidate(Editor/Settings/DxMessagingSettings.cs:229) must not lead �directly or transitively � to
AssetDatabase.ImportAsset,AssetDatabase.SaveAssets,AssetDatabase.CreateAsset, or any otherAssetDatabasemutation. The same applies toAddIgnoredType/RemoveIgnoredTypeif they can run inside an import window.user action (a menu item / Project Settings button), or a single
post-domain-reload
EditorApplication.delayCallone-shot that re-checkseditor state when it runs.
RegenerateSidecar(Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:48-62),do not branch on
isUpdating/isCompilingto decide whether to defer �always defer. Re-check
isUpdating/isCompiling(andsettings != null)inside the deferred one-shot, and bail out if the editor is still in an
unsafe state; a later user action or the next callback will retry.
unsubscribes itself (
delayCall -= Handler; delayCall += Handler;thendelayCall -= Handler;as the first line of the handler) so duplicateone-shots cannot stack across reload boundaries and no stale
settingsreference is captured.
AssetDatabase.ImportAssetis needed at all. Thesidecar (
Assets/Editor/DxMessaging.BaseCallIgnore.txt,Editor/Settings/DxMessagingBaseCallIgnoreSync.cs:27) is consumed by theRoslyn analyzer via
csc.rsp-additionalfile. A plainFile.WriteAllTextplus a lazy import (let Unity pick up the change on its next refresh) is very
likely sufficient; the explicit
ImportAssetatEditor/Settings/DxMessagingBaseCallIgnoreSync.cs:87may be unnecessarychurn. Only call
ImportAssetif the sidecar genuinely must be re-importedimmediately � and even then, only from a safe (non-callback) context.
File.ReadAllText/string.Equalscomparison atEditor/Settings/DxMessagingBaseCallIgnoreSync.cs:77-84is correct and worthkeeping � it avoids writing (and importing) when nothing changed. Retain it.
Validation / Repro Steps
To reproduce the crash on an unpatched v3.0.1:
com.wallstop-studios.dxmessaging3.0.1.Assets/Editor/DxMessagingSettings.assetexists on disk (open theproject once so
GetOrCreateSettingscreates it).Assets > Refresh, or restart the editor.GuidReservations::Reserve(ImportAtPathV2)during domain-load initialization.
To validate the fix:
crash.
sidecar (
Assets/Editor/DxMessaging.BaseCallIgnore.txt) is still regeneratedcorrectly � just from a safe context, not from inside
OnValidate.(editing then reverting a value should not rewrite the sidecar).
delayCallone-shots stack up across repeated reloads.References
.llm/skills/defensive-editor-programming.md� section "Never Mutate AssetDatabase from Validation Callbacks" captures this
pattern as a reusable rule.
57ed15f89e13, v3.0.1):Editor/DxMessagingEditorInitializer.csEditor/Settings/DxMessagingSettings.csEditor/Settings/DxMessagingBaseCallIgnoreSync.csSteps to Reproduce
The stuff above
Expected Behavior
Unity happy
Actual Behavior
Unity Sad
Unity Version
6000.x
Package Version
3.0.1
Platform
Additional Context
No response