memstechtips/Winhance · critical · OperationCanceledException

DISM operation timed out after {HardTimeoutSeconds}s

Error message

DISM operation timed out after {HardTimeoutSeconds}s

What it means

OperationCanceledException thrown by DismSessionManager.ExecuteAsync<T> when a native DISM call (run on a pooled thread under a single-semaphore lock) does not complete within the 30-second HardTimeoutSeconds deadline. Native DISM cannot be cancelled via CancellationToken, so Task.WhenAny races the work task against a 30s delay; if the delay wins, the thread is abandoned and the exception thrown to abort the caller.

Source

Thrown at src/Winhance.Infrastructure/Features/Common/Utilities/DismSessionManager.cs:77

                        log?.Invoke("[DismSession] Calling DismCloseSession...");
                        DismApi.DismCloseSession(session);
                        log?.Invoke("[DismSession] DismCloseSession done");
                    }
                }
                finally
                {
                    log?.Invoke("[DismSession] Calling DismShutdown...");
                    DismApi.DismShutdown();
                    log?.Invoke("[DismSession] DismShutdown done");
                }
            }, ct);

            var timeoutTask = Task.Delay(TimeSpan.FromSeconds(HardTimeoutSeconds), ct);

            if (await Task.WhenAny(workTask, timeoutTask).ConfigureAwait(false) == timeoutTask)
            {
                log?.Invoke($"[DismSession] HARD TIMEOUT after {HardTimeoutSeconds}s — native DISM call is unresponsive, abandoning thread");
                throw new OperationCanceledException($"DISM operation timed out after {HardTimeoutSeconds}s");
            }

            return await workTask.ConfigureAwait(false);
        }
        catch (OperationCanceledException)
        {
            log?.Invoke($"[DismSession] Operation cancelled/timed out in ExecuteAsync<T>");
            throw;
        }
        catch (Exception ex)
        {
            log?.Invoke($"[DismSession] EXCEPTION in ExecuteAsync<T>: {ex.GetType().Name}: {ex.Message}");
            throw;
        }
        finally
        {
            _lock.Release();
            log?.Invoke($"[DismSession] Semaphore released. Total elapsed={sw.ElapsedMilliseconds}ms");

View on GitHub (pinned to f23d554eb2)

Solutions

  1. Retry once after a reboot — a transient DISM hang often clears when the servicing stack resets.
  2. Move the image to a local fast drive (avoid network/USB sources) and retry.
  3. Run `DISM /Online /Cleanup-Image /RestoreHealth` and `sfc /scannow` to repair a corrupt servicing stack, then retry.
  4. If the image is large and the 30s deadline is genuinely too short on this hardware, raise HardTimeoutSeconds (note: it is a const — make it configurable).
  5. Ensure no antivirus is scanning the image during the DISM call (add an exclusion).

Example fix

// before: hard-coded 30s, thread abandoned on timeout
private const int HardTimeoutSeconds = 30;
if (await Task.WhenAny(workTask, timeoutTask).ConfigureAwait(false) == timeoutTask)
    throw new OperationCanceledException($"DISM operation timed out after {HardTimeoutSeconds}s");

// after: configurable timeout, longer for known-slow operations
private static int HardTimeoutSeconds => AppContext.GetData("DISM_HARD_TIMEOUT") is string s && int.TryParse(s, out var v) ? v : 60;
if (await Task.WhenAny(workTask, timeoutTask).ConfigureAwait(false) == timeoutTask)
    throw new OperationCanceledException($"DISM operation timed out after {HardTimeoutSeconds}s. The image may be corrupt or on slow storage.");
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: ensure the image is local and the servicing stack is healthy.
bool IsHealthyForNativeDism(string imagePath) =>
    _fileSystemService.FileExists(imagePath) && !imagePath.StartsWith(@"\\");

Try / catch

catch (OperationCanceledException ex) when (ex.Message.Contains("DISM operation timed out"))
{
    // After a hard timeout the DISM engine may be wedged; advise a reboot before any retry.
    // Do NOT immediately retry on the same image — that re-wedges the same pooled thread.
}

Prevention

When it happens

Trigger: ExecuteAsync<T> acquires the global DISM semaphore, Task.Run's the native call (DismInitialize + the action + DismShutdown), and Task.WhenAny returns the timeoutTask first. Happens when DISM hangs on a corrupt image, a network-backed source, a locked file, or a servicing stack in a bad state. Also if the system is under heavy I/O and DISM is slow to respond.

Common situations: The WIM/image path is on a slow network share. The image is corrupted and DISM loops internally. Another DISM process holds resources. The 30s budget is too short for a large image on a slow machine. Antivirus is scanning the image and slowing DISM I/O.

Understand the failure class

Related errors


AI-assisted analysis of memstechtips/Winhance@f23d554eb2 (2026-08-13). Data as JSON: /api/errors/927df7dbd41fdbfb. Report an issue: GitHub.