Gesture causing AGGraphGetValue

I have a custom UIGestureRecognizerRepresentable that causes a crash. Only happens about once a week, and that’s with about a thousand times it gets invoked. But it’s persistent and annoying when it happens. The code looks like

func handleUIGestureRecognizerAction(_ recognizer: Recognizer, context: Context) {
    let startLocation = context.converter.convert(globalPoint: recognizer.startLocation, to: coorinateSpace)

coorinateSpace is set in init and has crashed on .global, .local, and .named("foo")

The convert function gets caught in a AGGraphGetValue loop before finally failing. It has happened while attached to the debugger, but there is not any more information than is in the logs. I don’t even know why it would invoke AttributeGraph in the first place.

Thread 0 name:   Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib        	       0x23aedd1d0 __pthread_kill + 8
1   libsystem_pthread.dylib       	       0x1eb0cf7dc pthread_kill + 268
2   libsystem_c.dylib             	       0x197a89c98 abort + 148
3   AttributeGraph                	       0x1befffef0 AG::precondition_failure(char const*, ...) + 216
4   AttributeGraph                	       0x1bf000f68 AG::Graph::input_value_ref_slow(AG::data::ptr<AG::Node>, AG::AttributeID, unsigned int, unsigned int, AGSwiftMetadata const*, unsigned char&, long) + 936
5   AttributeGraph                	       0x1beffb2a8 AGGraphGetValue + 232
6   SwiftUICore                   	       0x19626eee8 specialized UnaryLayoutComputer.updateValue() + 92
7   SwiftUICore                   	       0x196bdc9cc specialized implicit closure #1 in closure #1 in closure #1 in Attribute.init<A>(_:) + 24
8   AttributeGraph                	       0x1bf001914 AG::Graph::UpdateStack::update() + 496
9   AttributeGraph                	       0x1bf00152c AG::Graph::update_attribute(AG::data::ptr<AG::Node>, unsigned int) + 352
10  AttributeGraph                	       0x1bf000e68 AG::Graph::input_value_ref_slow(AG::data::ptr<AG::Node>, AG::AttributeID, unsigned int, unsigned int, AGSwiftMetadata const*, unsigned char&, long) + 680
11  AttributeGraph                	       0x1beffb2a8 AGGraphGetValue + 232
12  SwiftUICore                   	       0x196287604 specialized UnaryChildGeometry.value.getter + 92
13  SwiftUICore                   	       0x196a21bc0 specialized implicit closure #1 in closure #1 in closure #1 in Attribute.init<A>(_:) + 52
14  AttributeGraph                	       0x1bf001914 AG::Graph::UpdateStack::update() + 496
15  AttributeGraph                	       0x1bf00152c AG::Graph::update_attribute(AG::data::ptr<AG::Node>, unsigned int) + 352
16  AttributeGraph                	       0x1bf000e68 AG::Graph::input_value_ref_slow(AG::data::ptr<AG::Node>, AG::AttributeID, unsigned int, unsigned int, AGSwiftMetadata const*, unsigned char&, long) + 680
17  AttributeGraph                	       0x1beffb2a8 AGGraphGetValue + 232
18  SwiftUICore                   	       0x196a21b28 specialized implicit closure #1 in closure #1 in closure #1 in Attribute.init<A>(_:) + 72
19  AttributeGraph                	       0x1bf001914 AG::Graph::UpdateStack::update() + 496
20  AttributeGraph                	       0x1bf00152c AG::Graph::update_attribute(AG::data::ptr<AG::Node>, unsigned int) + 352
21  AttributeGraph                	       0x1beffb560 AG::Graph::value_ref(AG::AttributeID, unsigned int, AGSwiftMetadata const*, unsigned char&) + 296
22  AttributeGraph                	       0x1beffb2f8 AGGraphGetValue + 312
23  AttributeGraph                	       0x1bf00206c AGGraphGetInputValue + 60
24  SwiftUICore                   	       0x19626d0a8 GeometryProxy.transform.getter + 220
25  SwiftUICore                   	       0x19699ce80 GeometryProxy.convert<A>(globalPoint:to:) + 76
26  SwiftUI                       	       0x195ddf71c UIGestureRecognizerRepresentableCoordinateSpaceConverter.convert<A>(globalPoint:to:) + 76
27  myapp.debug.dylib        	             0x1033b3a30 MyAppDragGesture.handleUIGestureRecognizerAction(_:context:) + 1560
Answered by DTS Engineer in 889491022

Hi @rahmcoff,

This is not a bug in the convert call itself — it's a re-entrancy / lifetime problem with the Context's coordinate-space converter. The crash happens because context.converter.convert(globalPoint:to:) ends up reading a SwiftUI GeometryProxy/UnaryLayoutComputer value out of the AttributeGraph, and the attribute it tries to read is being evaluated at a moment when its dependencies are not in a valid, resolved state.

One thing to keep in mind is that AG::precondition_failure → abort() is AttributeGraph's way of saying "you asked the graph for a value while the graph was mid-update or the node has been invalidated."

The reason this abort() is intermittent (~1 in 1000) is that it only triggers when the gesture action fires while a layout pass affecting that representable is in flight, or after the view providing that coordinate space has been removed/recycled.

I'd suggest you consider one of the following options:

  1. The code captures coordinateSpace (or the converter) and are using it at the wrong time. You mention coordinateSpace is set in init. If you stored a .named("foo") space, that name has to resolve to a view that is currently installed in the hierarchy. During a transition, push/pop, or list-cell recycling, that named space can briefly not exist while the gesture is still firing → graph precondition failure. .global and .local crashing as well points to the same timing issue rather than the space identity.

  2. The action is firing slightly off the main run loop's layout state. For example, a gesture continuing to deliver .changed/.ended while the representable is being torn down or re-laid-out. The converter's backing geometry node is invalidated, but the recognizer still has a reference to a stale Context.

  3. The code is reading the converter from a retained Context. Context (and its converter) are only valid for the duration of the callback. Holding onto context and calling convert later is undefined and matches this failure signature exactly.

If you have any further questions, please let me know.

Cheers,

Paris X Pinkney |  WWDR | DTS Engineer

Hi @rahmcoff,

This is not a bug in the convert call itself — it's a re-entrancy / lifetime problem with the Context's coordinate-space converter. The crash happens because context.converter.convert(globalPoint:to:) ends up reading a SwiftUI GeometryProxy/UnaryLayoutComputer value out of the AttributeGraph, and the attribute it tries to read is being evaluated at a moment when its dependencies are not in a valid, resolved state.

One thing to keep in mind is that AG::precondition_failure → abort() is AttributeGraph's way of saying "you asked the graph for a value while the graph was mid-update or the node has been invalidated."

The reason this abort() is intermittent (~1 in 1000) is that it only triggers when the gesture action fires while a layout pass affecting that representable is in flight, or after the view providing that coordinate space has been removed/recycled.

I'd suggest you consider one of the following options:

  1. The code captures coordinateSpace (or the converter) and are using it at the wrong time. You mention coordinateSpace is set in init. If you stored a .named("foo") space, that name has to resolve to a view that is currently installed in the hierarchy. During a transition, push/pop, or list-cell recycling, that named space can briefly not exist while the gesture is still firing → graph precondition failure. .global and .local crashing as well points to the same timing issue rather than the space identity.

  2. The action is firing slightly off the main run loop's layout state. For example, a gesture continuing to deliver .changed/.ended while the representable is being torn down or re-laid-out. The converter's backing geometry node is invalidated, but the recognizer still has a reference to a stale Context.

  3. The code is reading the converter from a retained Context. Context (and its converter) are only valid for the duration of the callback. Holding onto context and calling convert later is undefined and matches this failure signature exactly.

If you have any further questions, please let me know.

Cheers,

Paris X Pinkney |  WWDR | DTS Engineer

Gesture causing AGGraphGetValue
 
 
Q