-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
Description
We are encountering random crashes in Delegate.Remove (specifically around InternalEqualTypes / MINT_INTRINS_GET_TYPE) when running on Mono Interpreter (iOS, Android, and Windows).
All signs point to a GC liveness/rooting issue where the Delegate object is prematurely collected by a Minor GC, resulting in a wild pointer access during the subsequent Delegate.Remove call.
After enabling the GC debug option check-remset-consistency, we caught a definitive log indicating a missing write barrier.
Configuration
- Version: .NET 9.0.11 (Mono Runtime)
- OS: iOS, Android, Windows
- Execution Mode: Interpreter enabled
- GC: SGen
Reproduction Steps
The issue occurs with C# event operations involving a long-lived object (Old Gen) referencing a short-lived Delegate (New Gen).
Pseudo-code context:
// Scenario: Use a custom EventProxy to manage events on a long-lived EventDispatcher.
partial class EventProxy
{
// EventDispatcher is a long-lived object (likely in Old Gen)
readonly EventDispatcher mDispatcher;
// ... verification logic ...
public event Action ActivityNewyear2026RefreshPreviewSellPrice
{
add
{
// The 'value' is a new Delegate (likely in New Gen/Nursery).
// We verify logic and then modify the field on mDispatcher.
// PROBLEM TRIGGER: This operation (+=) seems to miss a Write Barrier in Interpreter mode.
mDispatcher.EventActivityNewyear2026RefreshPreviewSellPrice += value;
}
remove
{
// CRASH HAPPENS HERE:
// When attempting to remove, 'value' or the field in 'mDispatcher'
// points to garbage memory because it was collected in a prior Minor GC.
mDispatcher.EventActivityNewyear2026RefreshPreviewSellPrice -= value;
}
}
}
partial class EventDispatcher
{
// This object is promoted to Old Space.
// Offset 8888 corresponds to this field.
public event Action? EventActivityNewyear2026RefreshPreviewSellPrice = null;
}Analysis & Logs
We enabled the GC consistency check via environment variable/argument:
MONO_GC_DEBUG=check-remset-consistency
After executing the += operation (add), the following error is logged immediately before the crash occurs in a subsequent operation:
59:46 Begin heap consistency check...
2026-02-27 10:59:46 Oldspace->newspace reference 000002770A8153D0 at offset 8888 in object 0000027709A63798 (Tenth.EventDispatcher) not found in remsets.
2026-02-27 10:59:46 Heap consistency check done.
Interpretation:
Tenth.EventDispatcher(0000027709A63798) is in Old Gen (Oldspace).- The field at offset
8888(EventActivityNewyear2026RefreshPreviewSellPrice) references000002770A8153D0. - The referenced object
000002770A8153D0(The Delegate) is in New Gen (Newspace). - Error: "not found in remsets".
This confirms that the Write Barrier was missing when the Interpreter executed the field assignment (likely inside the Combine implementation or the storing of the combined delegate back to the field).
Because the remset does not record this relationship, the subsequent Minor GC assumes the Delegate is unreachable (if no other roots exist) and reclaims it. When Delegate.Remove is called later, it tries to access this reclaimed object, causing a crash or ArgumentException inside InternalEqualTypes.
Related Issues
We are aware of issue #85318 which discusses missing write barriers, but that issue focuses on native code modifying managed fields. In our case, all operations (add/remove) are performed in pure C#, suggesting the issue lies within the Mono Interpreter's handling of delegate field updates (possibly stfld or specific delegate intrinsics).
Impact
This causes random stability issues on all platforms using the Mono Interpreter (including iOS/Android release builds) that are extremely hard to debug without consistency checks.