Skip to content

macOS: daemon crashes with "Collection was modified" when devices reconnect after sleep/wake #5028

Description

@fancling

Summary

OpenTabletDriver intermittently crashes around USB device reconnection after macOS sleep/wake. It has been fairly easy to encounter in my setup, but I do not yet have a deterministic reproduction procedure. I captured an unhandled exception in the daemon's device-change callback, followed later by an unhandled RPC connection-loss exception in the UI.

AI disclosure: All source-code analysis below and the local experimental patch/tests were produced using OpenAI Codex. I am the affected user and a developer, but I do not have relevant expertise in tablet drivers, macOS HID, or this codebase. The diagnosis is a hypothesis requiring review; the stack traces are actual output captured from the installed application.

Environment

  • macOS 26.5.2 on Apple Silicon
  • OpenTabletDriver 0.6.7 macOS x86_64 build, running under Rosetta 2
  • Initial connection: Mac → USB-C cable → monitor's built-in USB hub → tablet, keyboard, and headset
  • Moving the tablet to a separate dock did not prevent the problem.

Tablet Device

Wacom CTL-471

Steps associated with the issue

  1. Start OTD with the tablet connected through the monitor hub or a separate dock.
  2. Use the tablet normally, then put the Mac to sleep.
  3. Wake the Mac; some tests used approximately 10–20 seconds of sleep. USB devices disconnected and reappeared during testing.
  4. Continue using the system and observe OTD and its captured stderr.

These are the circumstances in which I encountered the problem, not a guaranteed reproducer.

Expected Behavior

OTD handles device removal/reconnection without terminating.

Observed Behavior

The daemon exits with the exception below. The UI subsequently also reports an unhandled RPC connection-loss exception.

Diagnostic Information

The following exceptions were captured from the installed application's stderr, before any local source modifications were deployed.

Daemon

Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.Collections.Generic.List`1.Enumerator.MoveNext()
   at System.Linq.Enumerable.ExceptIterator[TSource](IEnumerable`1 first, IEnumerable`1 second, IEqualityComparer`1 comparer)+MoveNext()
   at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source, Func`2 predicate)
   at OpenTabletDriver.Daemon.DriverDaemon.<.ctor>b__1_2(Object sender, DevicesChangedEventArgs args) in /home/runner/work/OpenTabletDriver/OpenTabletDriver/OpenTabletDriver.Daemon/DriverDaemon.cs:line 56
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state)
   at System.Threading.ThreadPoolWorkQueue.DispatchItemWithAutoreleasePool(Object workItem, Thread currentThread)
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
   at System.Threading.Thread.StartCallback()

Subsequently captured UI exception

Unhandled exception. StreamJsonRpc.ConnectionLostException: The JSON-RPC connection with the remote party was lost before the request could complete.
   at StreamJsonRpc.JsonRpc.InvokeCoreAsync(JsonRpcRequest request, Type expectedResultType, CancellationToken cancellationToken)
   at StreamJsonRpc.JsonRpc.InvokeCoreAsync[TResult](RequestId id, String targetName, IReadOnlyList`1 arguments, IReadOnlyList`1 positionalArgumentDeclaredTypes, IReadOnlyDictionary`2 namedArgumentDeclaredTypes, CancellationToken cancellationToken, Boolean isParameterObject)
   at OpenTabletDriver.UX.MainForm.<HandleDaemonConnected>b__21_0() in /home/runner/work/OpenTabletDriver/OpenTabletDriver/OpenTabletDriver.UX/MainForm.cs:line 412
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_0(Object state)
   at MonoMac.AppKit.AppKitSynchronizationContext.<>c__DisplayClass1_0.<Post>b__0()
   at MonoMac.Foundation.NSAsyncActionDispatcher.Apply()

Preliminary analysis

A possible race involves mutable device collections: DevicesChangedEventArgs retains references to the previous/current collections, while Additions uses deferred Except enumeration. RootHub may modify the underlying list while the daemon enumerates args.Additions in its device-change callback. The UI exception may be a secondary failure after losing its connection to the daemon.

This seems consistent with the captured exception, but I cannot independently validate the diagnosis or whether the proposed solution fits the project's intended design.

A similar exception was reported in #3283, but in Driver.MatchDevice, so I am not assuming this is a duplicate.

Local experiment and possible follow-up

To explore this hypothesis, I have a local experimental patch based on 0.6.x that snapshots device collections and adjusts notification synchronization, plus a separate attempt to handle connection loss during UI initialization.

Four added tests failed on the unmodified baseline and passed with these changes; the macOS solution built successfully. The full suite had 12 failures also present on the unmodified baseline in this macOS environment. These results only exercise the proposed model of the problem; they do not establish that it explains or fixes the real-device crash.

The patch has not yet been validated through everyday use. I plan to test it during normal use and report the results. If you are comfortable reviewing an AI-assisted contribution, I would be happy to follow up with a PR after testing, splitting independent fixes as requested by the contribution guidelines.

Acknowledgements

  • I have reviewed the macOS FAQ and General FAQ. I did not find an entry addressing the captured collection-modification exception. This does not mean I have performed every troubleshooting step in the Wiki.
  • I have searched existing issues and have not found a confirmed duplicate of this stack trace.
  • I have written a concise and meaningful title.
  • I am using the latest release, 0.6.7.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageThis issue or PR has not been properly labeled yet

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions