Debug mode - SixWays/Relay GitHub Wiki

Debug mode helps diagnose "dangling" or "lapsed" listeners, a common garbage collection issue in event-driven architecture. It tracks all AddListener calls and outputs a log of all existing relays and their listeners on demand. It also eats performance for breakfast so don't turn it on in production code.

Usage

To enable debug mode, define SIGTRAP_RELAY_DBG. Each AddListener call will be automatically tracked. Relay.LogRelays() returns a log of all relays (of all generic types) and their persistent listeners (one-time listeners are ignored), ordered by observer (the owner of the listener). Alternatively, Relay.LogRelays(object observer) returns a log for just one observer. A Unity menu item [Relay/Log Listeners] will print this to Debug.Log.

Debug mode introduces crazy-big CPU and GC overhead on AddListener, and a moderate CPU overhead on RemoveListener and RemoveAllListeners. Toggle Relay.recordDebugData to only log what you're interested in and not incur AddListener overhead constantly. This still incurs the RemoveListener and RemoveAllListeners overhead.

Example Output

3 current relay listeners for Main Camera (MyClass):
  [0] Relay<Dictionary<Int32, Single>>
    Void MyMethod(Dictionary<Int32, Single>) (1 copies) 
     AddListener traces: (including for listeners which have since been removed!)
      [0]:
        MyClass::Start() (at Assets\MyClass.cs:11)
  [1] Relay<Dictionary<Int32, Single>>
    Void MyMethod(Dictionary<Int32, Single>) (1 copies) 
     AddListener traces: (including for listeners which have since been removed!)
      [0]:
        MyClass::Start() (at Assets\MyClass.cs:12)
  [2] Relay
    Void MyMethod2() (1 copies) 
     AddListener traces: (including for listeners which have since been removed!)
      [0]:
        MyClass::Start() (at Assets\MyClass.cs:13)
1 current relay listeners for anonymous methods:
  [0] Relay
    Void <Start>m__0() (1 copies) 
     AddListener traces: (including for listeners which have since been removed!)
      [0]:
        MyClass::Start() (at Assets\MyClass.cs:14)

Output is

  • Observer
    • Relay
      • Listener
        • Relevant AddListener stacktraces

Note that there's no way to get the "name" of a Relay or which object "owns" it, as each Relay is just an object on the heap with no knowledge of which object declared it*. To aid identification of which Relay is which, the full type of the Relay is printed and a stack trace is logged when AddListener is called (subsequent duplicate traces are ignored). For instance in the above example, the first two items differ only by the line number on the stack trace.

Trace lists include all AddListener calls for that relay and that delegate. There's no way to relate a specific RemoveListener call to a specific AddListener call, so all traces are retained unless there are no listeners left on the Relay. This complication will only arise with duplicate listeners.

This is a running theme. It's also impossible to relate anonymous methods / lambdas to the object that declared them (though I could be wrong on this!), so they all get grouped together separately. Again, the stack trace should help identification. Also note the compiler-generated method name, which includes in <> the name of the method (but not the type) in which they were declared.

*I may at some point add stack trace logging to Relay constructors and include this in the output to really drill down into exactly which is which.

⚠️ **GitHub.com Fallback** ⚠️