{"record":{"id":"3ff886f62c8a476b","repo":"JosefNemec/Playnite","slug":"game-is-already-being-tracked","errorCode":null,"errorMessage":"Game is already being tracked.","messagePattern":"Game is already being tracked\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"source/Playnite/Controllers/GenericGameController.cs","lineNumber":680,"sourceCode":"                }\r\n                else\r\n                {\r\n                    throw new NotSupportedException();\r\n                }\r\n            }\r\n        }\r\n\r\n        public void StartTracking(\r\n            Func<bool> trackingAction,\r\n            Func<int> startupCheck = null,\r\n            Action<int> gameStartedAction = null,\r\n            Action gameStoppedAction = null,\r\n            int trackingFrequency = 2000,\r\n            int trackingStartDelay = 0)\r\n        {\r\n            if (watcherToken != null)\r\n            {\r\n                throw new Exception(\"Game is already being tracked.\");\r\n            }\r\n\r\n            watcherToken = new CancellationTokenSource();\r\n            Task.Run(async () =>\r\n            {\r\n                ulong playTimeMs = 0;\r\n                var trackingWatch = new Stopwatch();\r\n                var maxFailCount = 5;\r\n                var failCount = 0;\r\n\r\n                if (trackingStartDelay > 0)\r\n                {\r\n                    await Task.Delay(trackingStartDelay, watcherToken.Token).ContinueWith(task => { });\r\n                }\r\n\r\n                if (startupCheck != null)\r\n                {\r\n                    while (true)\r","sourceCodeStart":662,"sourceCodeEnd":698,"githubUrl":"https://github.com/JosefNemec/Playnite/blob/5911f4e964e628aa7a69c2030ab35101afa63067/source/Playnite/Controllers/GenericGameController.cs#L662-L698","documentation":"Thrown as a generic Exception when StartTracking is called while a tracking task is already active (watcherToken != null). The controller uses a single CancellationTokenSource to manage one concurrent tracking loop per game controller instance. Calling StartTracking a second time without first stopping the previous tracker is a lifecycle violation.","triggerScenarios":"StartTracking is invoked twice on the same GenericGameController instance without calling the stop/dispose path in between. This can occur if a game's start sequence triggers multiple tracking setups (e.g., both Process and Directory tracking modes), or if a plugin manually calls StartTracking after the controller already started its own.","commonSituations":"A custom plugin overrides OnGameStarted and calls StartTracking in addition to the base controller. A race condition where the game is launched twice rapidly. A tracking mode configuration triggers overlapping StartTracking calls. The controller was not properly disposed between game sessions.","solutions":["Ensure only one tracking loop is active per controller: call Dispose/StopTracking before starting a new tracking session.","If overriding OnGameStarted in a plugin, do not call StartTracking if the base controller already handles tracking.","Use a null-check on watcherToken before calling StartTracking in custom code.","Ensure game controllers are properly disposed and recreated per game session, not reused."],"exampleFix":"// before — calling StartTracking without checking existing state\npublic void StartCustomTracking()\n{\n    controller.StartTracking(() => IsRunning(), trackingFrequency: 2000);\n}\n\n// after — guard against double-tracking\npublic void StartCustomTracking()\n{\n    if (controller.IsTrackingActive) // check watcherToken state\n    {\n        logger.Warn(\"Tracking already active, skipping.\");\n        return;\n    }\n    controller.StartTracking(() => IsRunning(), trackingFrequency: 2000);\n}","handlingStrategy":"validation","validationCode":"if (controller.IsTracking) // expose watcherToken != null as a property\n{\n    logger.Warn(\"Tracking already active; stopping previous tracker.\");\n    controller.StopTracking();\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    controller.StartTracking(() => IsRunning());\n}\ncatch (Exception ex) when (ex.Message.Contains(\"already being tracked\"))\n{\n    logger.Warn(\"Attempted to start duplicate tracking; ignoring.\");\n}","preventionTips":["Expose an IsTracking property backed by watcherToken and check it before StartTracking.","Ensure StopTracking/Dispose is called in the OnGameStopped handler before any new StartTracking.","Do not call StartTracking from plugin event handlers if the base controller already tracks."],"tags":["tracking","concurrency","lifecycle","cancellation-token","game-controller"],"backgroundTag":null,"analyzedSha":"5911f4e964e628aa7a69c2030ab35101afa63067","analyzedAt":"2026-08-13T17:38:25.713Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}