windmill-labs/windmill · error
CreateToolhelp32Snapshot: {e}
Error message
CreateToolhelp32Snapshot: {e} What it means
On Windows, resume_process enumerates all threads via CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD) to find and ResumeThread the child's threads (the child was started suspended). If the Win32 snapshot call fails, the error is wrapped into an io::Error with this message. Snapshot failure typically means the toolhelp subsystem could not be accessed for the calling process.
Source
Thrown at backend/windmill-worker/src/common.rs:830
self.inner.try_wait()
}
}
/// Resume all threads of a process created with CREATE_SUSPENDED. We create the child
/// suspended so it can be assigned to its job object before running any code; otherwise
/// a child that forks a helper at startup could create it before the assignment and
/// leave it outside the job (escaping KILL_ON_JOB_CLOSE reaping / the memory cap).
/// (Ported from process-wrap's resume_threads.)
#[cfg(windows)]
fn resume_process(pid: u32) -> Result<(), std::io::Error> {
use windows::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Thread32First, Thread32Next, TH32CS_SNAPTHREAD, THREADENTRY32,
};
use windows::Win32::System::Threading::{OpenThread, ResumeThread, THREAD_SUSPEND_RESUME};
unsafe {
let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("CreateToolhelp32Snapshot: {e}"),
)
})?;
let mut entry = THREADENTRY32 {
dwSize: std::mem::size_of::<THREADENTRY32>() as u32,
cntUsage: 0,
th32ThreadID: 0,
th32OwnerProcessID: 0,
tpBasePri: 0,
tpDeltaPri: 0,
dwFlags: 0,
};
let mut res = Thread32First(snapshot, &mut entry);
while res.is_ok() {
if entry.th32OwnerProcessID == pid {
if let Ok(thread) = OpenThread(THREAD_SUSPEND_RESUME, false, entry.th32ThreadID) {
ResumeThread(thread);View on GitHub (pinned to e474e8803c)
Solutions
- Check the Win32 error code appended to the message (map Windows error codes, e.g. 5 = access denied) and fix the underlying permission.
- Run the worker under an account with normal process/thread access rights; avoid running inside heavily restricted sandboxes.
- Retry the job; transient snapshot allocation failures can clear under lower memory pressure.
- Update Windows/the windows crate if a known toolhelp regression applies.
Defensive patterns
Strategy: try-catch
Validate before calling
// No pre-call check possible for toolhelp snapshot availability; verify the process runs with standard user privileges on Windows before spawning suspended children.
Try / catch
match resume_process(pid) {
Err(e) if e.to_string().contains("CreateToolhelp32Snapshot") => {
log::warn!("thread snapshot failed, retrying once: {e}");
std::thread::sleep(std::time::Duration::from_millis(50));
resume_process(pid)?;
}
other => other?,
} Prevention
- Run the worker under a normal (non-restricted) Windows account
- Avoid hardened sandbox/AV configurations that block toolhelp APIs
- Monitor worker handle/memory pressure to avoid snapshot allocation failures
When it happens
Trigger: start_child_process on Windows spawning a suspended child, then resume_process calling CreateToolhelp32Snapshot and the API returning a Win32 error (e.g. ERROR_ACCESS_DENIED or out-of-memory for the snapshot).
Common situations: Hardened/restricted Windows environments (job containers, service accounts without sufficient privileges), resource exhaustion creating a system-wide thread snapshot, antivirus or policy blocking toolhelp APIs.
Related errors
- CreateJobObjectW: {e}
- SetInformationJobObject: {e}
- OpenProcess({pid}): {e}
- AssignProcessToJobObject: {e}
- preprocessor function is missing
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/8b2dd9d02681dcae.
Report an issue: GitHub.