Commit 3a9d7f8
## Summary
Fix a race condition in `xamarin_switch_gchandle` where one thread switching a
GCHandle between weak and strong could free a handle that another thread was
simultaneously reading. This caused intermittent crashes and "Could not find
an existing managed instance" errors at runtime.
Fixes #24702.
## Problem
`xamarin_switch_gchandle` is called from retain/release to toggle an object's
GCHandle between weak (so the GC can collect the managed peer) and strong (so
the managed peer stays alive while Objective-C holds a reference).
The old implementation performed a non-atomic free-then-replace on the
object's `gc_handle` field:
1. Read the old GCHandle and its flags.
2. Allocate a new GCHandle (weak or strong).
3. Free the old GCHandle.
4. Store the new GCHandle on the object.
Between steps 3 and 4, a concurrent thread invoking an exported Objective-C
method would read the `gc_handle` field, obtain the already-freed handle, and
either crash or fail to resolve the managed object — producing errors like:
> Failed to marshal the Objective-C object 0x… Could not find an existing
> managed instance for this object, nor was it possible to create a new
> managed instance.
## Solution: dual-GCHandle scheme
Instead of replacing the object's GCHandle in place, the fix introduces a
**dual-GCHandle** design:
- **The object's `gc_handle` is always a weak handle** and is never modified
by `xamarin_switch_gchandle`. Concurrent readers always see a valid handle.
- **A separate strong GCHandle** is stored in a global mutex-protected
`CFMutableDictionary` (`strong_gchandle_hash`), keyed by the native object
pointer.
- **Switching to strong**: allocate a strong GCHandle for the managed object
and insert it into the hash table. The weak handle remains on the object
unchanged.
- **Switching to weak**: remove and free the strong GCHandle from the hash
table. The weak handle remains on the object unchanged.
Because the `gc_handle` field is never written by `xamarin_switch_gchandle`,
the race is eliminated: concurrent readers always see a valid weak GCHandle
that can resolve the managed object (the strong GCHandle in the hash table
keeps it alive and prevents collection).
## Changes
### `runtime/runtime.m`
- Add `strong_gchandle_hash` (`CFMutableDictionaryRef`) and its
`pthread_mutex_t` lock.
- Add `free_strong_gchandle()` — removes and frees the strong handle for a
native object from the hash table.
- Add `create_strong_gchandle()` — creates a strong handle and inserts it into
the hash table (no-op if one already exists, preventing duplicates from
concurrent callers).
- Rewrite `xamarin_switch_gchandle()` to use the dual-GCHandle scheme
instead of free-then-replace.
- Update `xamarin_free_gchandle()` to also clear any strong GCHandle from
the hash table (`xamarin_free_gchandle()` is called when a native object
is about to be freed).
- Clean up `strong_gchandle_hash` during runtime shutdown.
- Remove `get_gchandle_safe()` (no longer needed; replaced by
`get_gchandle_without_flags()`).
### `runtime/xamarin/trampolines.h`
- Remove `XamarinGCHandleFlags_WeakGCHandle = 1` (the flag is no longer
needed since `gc_handle` is always weak).
### `src/Foundation/NSObject2.cs`
- Remove `WeakGCHandle` from the managed `XamarinGCHandleFlags` enum.
- Stop setting `WeakGCHandle` in `CreateManagedRef()`.
### `tests/monotouch-test/ObjCRuntime/GCHandleSwitchRaceTest.cs` (new)
- Regression test that reproduces the race: one thread rapidly
retains/releases an object (toggling the GCHandle) while another thread
simultaneously invokes an exported Objective-C method (reading the
GCHandle). Without the fix this crashes; with the fix it completes
cleanly.
### `tests/dotnet/UnitTests/expected/iOS-MonoVM-*.txt`
- Remove `WeakGCHandle` from preserved API expectations.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent cf92540 commit 3a9d7f8
6 files changed
Lines changed: 176 additions & 57 deletions
File tree
- runtime
- xamarin
- src/Foundation
- tests
- dotnet/UnitTests/expected
- monotouch-test/ObjCRuntime
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
93 | 100 | | |
94 | 101 | | |
95 | 102 | | |
| |||
412 | 419 | | |
413 | 420 | | |
414 | 421 | | |
415 | | - | |
416 | | - | |
417 | | - | |
418 | | - | |
419 | | - | |
420 | | - | |
421 | | - | |
422 | | - | |
423 | | - | |
424 | | - | |
425 | 422 | | |
426 | 423 | | |
427 | 424 | | |
| |||
1567 | 1564 | | |
1568 | 1565 | | |
1569 | 1566 | | |
| 1567 | + | |
| 1568 | + | |
| 1569 | + | |
| 1570 | + | |
| 1571 | + | |
| 1572 | + | |
| 1573 | + | |
| 1574 | + | |
| 1575 | + | |
| 1576 | + | |
| 1577 | + | |
| 1578 | + | |
| 1579 | + | |
| 1580 | + | |
| 1581 | + | |
| 1582 | + | |
| 1583 | + | |
| 1584 | + | |
| 1585 | + | |
| 1586 | + | |
| 1587 | + | |
| 1588 | + | |
| 1589 | + | |
| 1590 | + | |
| 1591 | + | |
| 1592 | + | |
| 1593 | + | |
| 1594 | + | |
| 1595 | + | |
| 1596 | + | |
| 1597 | + | |
| 1598 | + | |
| 1599 | + | |
| 1600 | + | |
| 1601 | + | |
| 1602 | + | |
| 1603 | + | |
| 1604 | + | |
| 1605 | + | |
| 1606 | + | |
| 1607 | + | |
| 1608 | + | |
| 1609 | + | |
| 1610 | + | |
| 1611 | + | |
| 1612 | + | |
| 1613 | + | |
| 1614 | + | |
| 1615 | + | |
| 1616 | + | |
| 1617 | + | |
| 1618 | + | |
| 1619 | + | |
| 1620 | + | |
1570 | 1621 | | |
1571 | 1622 | | |
1572 | 1623 | | |
1573 | | - | |
1574 | 1624 | | |
1575 | | - | |
1576 | | - | |
1577 | | - | |
1578 | | - | |
1579 | | - | |
1580 | | - | |
1581 | | - | |
1582 | | - | |
1583 | | - | |
1584 | | - | |
1585 | | - | |
1586 | | - | |
1587 | | - | |
1588 | | - | |
1589 | | - | |
| 1625 | + | |
| 1626 | + | |
| 1627 | + | |
| 1628 | + | |
| 1629 | + | |
1590 | 1630 | | |
1591 | 1631 | | |
1592 | 1632 | | |
| |||
1600 | 1640 | | |
1601 | 1641 | | |
1602 | 1642 | | |
1603 | | - | |
1604 | | - | |
1605 | | - | |
| 1643 | + | |
| 1644 | + | |
| 1645 | + | |
| 1646 | + | |
| 1647 | + | |
1606 | 1648 | | |
1607 | 1649 | | |
1608 | | - | |
1609 | | - | |
| 1650 | + | |
1610 | 1651 | | |
1611 | | - | |
1612 | | - | |
1613 | | - | |
| 1652 | + | |
1614 | 1653 | | |
1615 | | - | |
1616 | | - | |
1617 | | - | |
1618 | | - | |
1619 | | - | |
1620 | | - | |
1621 | | - | |
1622 | | - | |
1623 | | - | |
1624 | | - | |
1625 | | - | |
1626 | | - | |
1627 | | - | |
1628 | | - | |
1629 | | - | |
1630 | | - | |
| 1654 | + | |
| 1655 | + | |
| 1656 | + | |
| 1657 | + | |
| 1658 | + | |
| 1659 | + | |
1631 | 1660 | | |
1632 | | - | |
| 1661 | + | |
| 1662 | + | |
1633 | 1663 | | |
1634 | 1664 | | |
1635 | | - | |
| 1665 | + | |
1636 | 1666 | | |
| 1667 | + | |
1637 | 1668 | | |
1638 | 1669 | | |
1639 | 1670 | | |
| |||
1651 | 1682 | | |
1652 | 1683 | | |
1653 | 1684 | | |
| 1685 | + | |
| 1686 | + | |
| 1687 | + | |
1654 | 1688 | | |
1655 | 1689 | | |
1656 | 1690 | | |
| |||
1952 | 1986 | | |
1953 | 1987 | | |
1954 | 1988 | | |
| 1989 | + | |
| 1990 | + | |
| 1991 | + | |
| 1992 | + | |
| 1993 | + | |
| 1994 | + | |
| 1995 | + | |
1955 | 1996 | | |
1956 | 1997 | | |
1957 | 1998 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
| 19 | + | |
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
263 | 263 | | |
264 | 264 | | |
265 | 265 | | |
266 | | - | |
| 266 | + | |
267 | 267 | | |
268 | 268 | | |
269 | 269 | | |
| |||
500 | 500 | | |
501 | 501 | | |
502 | 502 | | |
503 | | - | |
| 503 | + | |
504 | 504 | | |
505 | 505 | | |
506 | 506 | | |
| |||
Lines changed: 0 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
189 | 189 | | |
190 | 190 | | |
191 | 191 | | |
192 | | - | |
193 | 192 | | |
194 | 193 | | |
195 | 194 | | |
| |||
Lines changed: 0 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
173 | 173 | | |
174 | 174 | | |
175 | 175 | | |
176 | | - | |
177 | 176 | | |
178 | 177 | | |
179 | 178 | | |
| |||
Lines changed: 80 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
0 commit comments