windmill-labs/windmill · error

fclose is not supported

Error message

fclose is not supported

What it means

`fclose` (close a FILE* stream) is a panicking stub in the R parser's WASM libc shim layer. Since no stream can be opened in the first place (fopen/fdopen are unsupported), reaching fclose means code is unconditionally running a stdio lifecycle the sandbox never implemented.

Source

Thrown at backend/parsers/windmill-parser-r/src/wasm_libc.rs:256

#[no_mangle]
pub unsafe extern "C" fn fputs(_s: *const c_void, _file: *mut c_void) -> c_int {
    panic!("fputs is not supported");
}

#[no_mangle]
pub unsafe extern "C" fn fputc(_c: c_int, _file: *mut c_void) -> c_int {
    panic!("fputc is not supported");
}

#[no_mangle]
pub unsafe extern "C" fn fdopen(_fd: c_int, _mode: *const c_void) -> *mut c_void {
    panic!("fdopen is not supported");
}

#[no_mangle]
pub unsafe extern "C" fn fclose(_file: *mut c_void) -> c_int {
    panic!("fclose is not supported");
}

#[no_mangle]
pub unsafe extern "C" fn fwrite(
    _ptr: *const c_void,
    _size: usize,
    _nmemb: usize,
    _stream: *mut c_void,
) -> usize {
    panic!("fwrite is not supported");
}

#[no_mangle]
pub unsafe extern "C" fn vsnprintf(
    _buf: *mut c_char,
    _size: usize,
    _format: *const c_char,
    _args: ...

View on GitHub (pinned to e474e8803c)

Solutions

  1. Remove/guard the stream-cleanup code path since no real streams exist in the WASM sandbox
  2. Implement fclose as a no-op returning 0 if linked code unconditionally calls it
  3. Rewrite the R/C code to avoid FILE* connections, using supported input/output mechanisms
  4. Track as an unsupported-symbol gap and emulate a minimal stream lifecycle if needed

Example fix

// before (stub)
#[no_mangle]
pub unsafe extern "C" fn fclose(_file: *mut c_void) -> c_int {
    panic!("fclose is not supported");
}
// after (safe no-op for unreachable streams)
#[no_mangle]
pub unsafe extern "C" fn fclose(_file: *mut c_void) -> c_int {
    0 // no real streams exist in WASM; succeed harmlessly
}
Defensive patterns

Strategy: try-catch

Validate before calling

// If no stream can be opened, fclose can never be legitimate: flag stream lifecycle code
if (/\b(fopen|fdopen|fclose)\s*\(/.test(source)) {
  console.warn("source uses FILE* lifecycle APIs unsupported by the R parser WASM");
}

Type guard

// Rust: FILE* handles from this sandbox are always null/invalid
fn stream_valid(file: *mut c_void) -> bool { !file.is_null() } // always false here; any close path is dead code

Try / catch

let res = std::panic::catch_unwind(AssertUnwindSafe(|| run_wasm_r(source)));
match res {
    Ok(v) => v,
    Err(p) if panic_msg(&p).contains("fclose is not supported") => {
        // strip or stub the stream-cleanup path in the source and retry once
        retry_without_stream_teardown(source)
    }
    Err(p) => std::panic::resume_unwind(p),
}

Prevention

When it happens

Trigger: Calling the exported `fclose` shim at backend/parsers/windmill-parser-r/src/wasm_libc.rs:256, typically cleanup code in compiled C/R runtime that closes streams on exit or error paths.

Common situations: Library teardown code always calls fclose; R script uses file connections that compile to stdio calls; a dependency's destructor flushes and closes streams.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/234c34e1f2d31e30. Report an issue: GitHub.