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
- 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.
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
- 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.
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
- Timed out while loading beatmap ({beatmapInfo}).
- Could not obtain a lock to connect. A previous attempt is li
- Could not obtain a lock to disconnect. A previous attempt is
- Attempting to block for migration took too long.
AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13).
Data as JSON: /api/errors/4ab39ddb7ddbc1b8.
Report an issue: GitHub.