ppy/osu · warning · TimeoutException

Screenshot data did not arrive in a timely fashion

Error message

Screenshot data did not arrive in a timely fashion

What it means

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.

Source

Thrown at osu.Game/Graphics/ScreenshotManager.cs:114

                {
                    cursorVisibility.Value = false;

                    // 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
                    const int frames_to_wait = 3;

                    int framesWaited = 0;

                    using (ManualResetEventSlim framesWaitedEvent = new ManualResetEventSlim(false))
                    {
                        ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() =>
                        {
                            if (framesWaited++ >= frames_to_wait)
                                // ReSharper disable once AccessToDisposedClosure
                                framesWaitedEvent.Set();
                        }, 10, true);

                        if (!framesWaitedEvent.Wait(1000))
                            throw new TimeoutException("Screenshot data did not arrive in a timely fashion");

                        waitDelegate.Cancel();
                    }
                }

                using (Image<Rgba32>? image = await host.TakeScreenshotAsync().ConfigureAwait(false))
                {
                    if (config.Get<ScalingMode>(OsuSetting.Scaling) == ScalingMode.Everything)
                    {
                        float posX = config.Get<float>(OsuSetting.ScalingPositionX);
                        float posY = config.Get<float>(OsuSetting.ScalingPositionY);
                        float sizeX = config.Get<float>(OsuSetting.ScalingSizeX);
                        float sizeY = config.Get<float>(OsuSetting.ScalingSizeY);

                        image.Mutate(m =>
                        {
                            Rectangle rect = new Rectangle(Point.Empty, m.GetCurrentSize());

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Retry the screenshot when the window is active and the draw thread is responsive; avoid screenshotting while minimised or paused in a debugger.
  2. If reproducible, investigate the draw thread stall (profiler, driver update, GPU contention).
  3. Enable 'capture menu cursor' to bypass the frame-wait path entirely, or catch TimeoutException and degrade to capturing with the cursor visible.

Example fix

// before
if (!framesWaitedEvent.Wait(1000))
    throw new TimeoutException("Screenshot data did not arrive in a timely fashion");

// after (degrade gracefully)
if (!framesWaitedEvent.Wait(1000))
{
    Logger.Log("Screenshot frame wait timed out; capturing with cursor visible.", LoggingTarget.Runtime, LogLevel.Important);
    cursorVisibility.Value = true;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Capture cursor state only when the draw thread is responsive.
if (!host.DrawThread.Running || host.Window == null) { /* skip frame-wait path */ }

Try / catch

try { await screenshotManager.TakeScreenshotAsync(); }
catch (TimeoutException ex) when (ex.Message.Contains("Screenshot data"))
{ /* retry once, or capture with cursor visible */ }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13). Data as JSON: /api/errors/4ab39ddb7ddbc1b8. Report an issue: GitHub.