{"record":{"id":"4ab39ddb7ddbc1b8","repo":"ppy/osu","slug":"screenshot-data-did-not-arrive-in-a-timely-fashion","errorCode":null,"errorMessage":"Screenshot data did not arrive in a timely fashion","messagePattern":"Screenshot data did not arrive in a timely fashion","errorType":"exception","errorClass":"TimeoutException","httpStatus":null,"severity":"warning","filePath":"osu.Game/Graphics/ScreenshotManager.cs","lineNumber":114,"sourceCode":"                {\r\n                    cursorVisibility.Value = false;\r\n\r\n                    // We need to wait for at most 3 draw nodes to be drawn, following which we can be assured at least one DrawNode has been generated/drawn with the set value\r\n                    const int frames_to_wait = 3;\r\n\r\n                    int framesWaited = 0;\r\n\r\n                    using (ManualResetEventSlim framesWaitedEvent = new ManualResetEventSlim(false))\r\n                    {\r\n                        ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() =>\r\n                        {\r\n                            if (framesWaited++ >= frames_to_wait)\r\n                                // ReSharper disable once AccessToDisposedClosure\r\n                                framesWaitedEvent.Set();\r\n                        }, 10, true);\r\n\r\n                        if (!framesWaitedEvent.Wait(1000))\r\n                            throw new TimeoutException(\"Screenshot data did not arrive in a timely fashion\");\r\n\r\n                        waitDelegate.Cancel();\r\n                    }\r\n                }\r\n\r\n                using (Image<Rgba32>? image = await host.TakeScreenshotAsync().ConfigureAwait(false))\r\n                {\r\n                    if (config.Get<ScalingMode>(OsuSetting.Scaling) == ScalingMode.Everything)\r\n                    {\r\n                        float posX = config.Get<float>(OsuSetting.ScalingPositionX);\r\n                        float posY = config.Get<float>(OsuSetting.ScalingPositionY);\r\n                        float sizeX = config.Get<float>(OsuSetting.ScalingSizeX);\r\n                        float sizeY = config.Get<float>(OsuSetting.ScalingSizeY);\r\n\r\n                        image.Mutate(m =>\r\n                        {\r\n                            Rectangle rect = new Rectangle(Point.Empty, m.GetCurrentSize());\r\n\r","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game/Graphics/ScreenshotManager.cs#L96-L132","documentation":"Thrown by ScreenshotManager.TakeScreenshotAsync when, after hiding the cursor, it waits on a ManualResetEventSlim(1000ms) for the draw thread to produce frames_to_wait (3) frames. If the draw thread scheduler doesn't fire the delayed delegate enough times within 1s, the wait times out and a TimeoutException is raised before the screenshot is captured.","triggerScenarios":"Taking a screenshot with 'capture menu cursor' disabled, while the draw thread is stalled, frozen, or running far below normal frame rate. The scheduler's AddDelayed(..., 10, true) must tick 3+ times within the 1s budget.","commonSituations":"The game/window is minimised, the render thread is blocked, a driver/GPU hang, debugger breakpoints pausing the draw thread, or an extremely slow system. The cursor-hide path is the only one that waits for frames.","solutions":["Retry the screenshot when the window is active and the draw thread is responsive; avoid screenshotting while minimised or paused in a debugger.","If reproducible, investigate the draw thread stall (profiler, driver update, GPU contention).","Enable 'capture menu cursor' to bypass the frame-wait path entirely, or catch TimeoutException and degrade to capturing with the cursor visible."],"exampleFix":"// before\nif (!framesWaitedEvent.Wait(1000))\n    throw new TimeoutException(\"Screenshot data did not arrive in a timely fashion\");\n\n// after (degrade gracefully)\nif (!framesWaitedEvent.Wait(1000))\n{\n    Logger.Log(\"Screenshot frame wait timed out; capturing with cursor visible.\", LoggingTarget.Runtime, LogLevel.Important);\n    cursorVisibility.Value = true;\n}","handlingStrategy":"try-catch","validationCode":"// Capture cursor state only when the draw thread is responsive.\nif (!host.DrawThread.Running || host.Window == null) { /* skip frame-wait path */ }","typeGuard":null,"tryCatchPattern":"try { await screenshotManager.TakeScreenshotAsync(); }\ncatch (TimeoutException ex) when (ex.Message.Contains(\"Screenshot data\"))\n{ /* retry once, or capture with cursor visible */ }","preventionTips":["Avoid screenshots while the window is minimised or the render thread is stalled.","Offer the 'capture menu cursor' option to bypass the frame-wait path entirely.","In debug builds, note the draw thread is often paused under breakpoints."],"tags":["screenshot","rendering","threading","timeout","graphics"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}