{"record":{"id":"ff5ba193b2dfed15","repo":"stride3d/stride","slug":"something-should-handle-controller-disconnect","errorCode":null,"errorMessage":"Something should handle controller disconnect","messagePattern":"Something should handle controller disconnect","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/engine/Stride.Input/SDL/GameControllerSDL.cs","lineNumber":78,"sourceCode":"        public override Guid ProductId { get; }\n\n        public override IInputSource Source { get; }\n\n        public override IReadOnlyList<GameControllerButtonInfo> ButtonInfos => buttonInfos;\n\n        public override IReadOnlyList<GameControllerAxisInfo> AxisInfos => axisInfos;\n\n        public override IReadOnlyList<GameControllerDirectionInfo> DirectionInfos => povControllerInfos;\n\n        public event EventHandler Disconnected;\n\n        public void Dispose()\n        {\n            if (!disposed)\n            {\n                SDL.JoystickClose(joystick);\n                if (Disconnected == null)\n                    throw new InvalidOperationException(\"Something should handle controller disconnect\");\n                Disconnected.Invoke(this, null);\n                disposed = true;\n            }\n        }\n\n        public override void Update(List<InputEvent> inputEvents)\n        {\n            if (SDL.JoystickGetAttached(joystick) == SdlBool.False)\n            {\n                Dispose();\n                return;\n            }\n\n            for (int i = 0; i < buttonInfos.Count; i++)\n            {\n                HandleButton(i, SDL.JoystickGetButton(joystick, i) != 0);\n            }\n","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/engine/Stride.Input/SDL/GameControllerSDL.cs#L60-L96","documentation":"GameControllerSDL.Dispose closes the SDL joystick and then invokes the Disconnected event; if no handler is attached (Disconnected == null), it throws, because the source would never learn the controller went away and would keep a dangling device. The library mandates that someone must handle controller disconnect before a controller is disposed.","triggerScenarios":"Disposing a GameControllerSDL while its Disconnected event has no subscribers — typically when the source removed its event handler, or the controller object is disposed before being wired to the source's handling code.","commonSituations":"SDL joystick hot-unplug triggering source-side cleanup that disposes the controller before re-subscribing; user code creating GameControllerSDL instances directly in tests without attaching Disconnected; refactors that accidentally unsubscribe the handler first.","solutions":["Ensure InputSourceSDL (or your owning code) subscribes to controller.Disconnected before any Dispose can occur","Do not dispose a controller without a Disconnected handler in place","In custom code, attach a no-op or logging handler if you don't need disconnect logic: controller.Disconnected += (s,e) => {};","If this fires during shutdown, unregister the device from the source before disposing"],"exampleFix":"// before\ncontroller.Dispose(); // Disconnected == null -> throws\n// after\ncontroller.Disconnected += (sender, args) => { /* handle removal */ };\ncontroller.Dispose();","handlingStrategy":"validation","validationCode":"controller.Disconnected += OnControllerDisconnected;\ncontroller.Dispose();","typeGuard":null,"tryCatchPattern":"try { controller.Dispose(); } catch (InvalidOperationException ex) { log.Warn($\"Controller {controller.Id} disposed without disconnect handler\"); }","preventionTips":["Subscribe to Disconnected immediately after a controller is created/opened","Never unsubscribe Disconnected before disposal","Use a no-op handler if your app has no disconnect logic"],"tags":["input","sdl","gamepad","event-handler","dispose"],"backgroundTag":"internal-invariant-violation","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}