Skip to content

Windows fixes#1103

Open
mtelvers wants to merge 6 commits intoocsigen:masterfrom
mtelvers:windows-fixes-v2
Open

Windows fixes#1103
mtelvers wants to merge 6 commits intoocsigen:masterfrom
mtelvers:windows-fixes-v2

Conversation

@mtelvers
Copy link
Copy Markdown

@mtelvers mtelvers commented Mar 3, 2026

I have been working on ocluster/obuilder on Windows and have run into several difficulties with lwt. I would like to contribute these fixes back to the codebase. I have divided it into four separate commits to make it easier to review. Each commit has a detailed description.

I note that some of the code generation was done using an AI agent. However, I have reviewed every line of these commits and tested them on both Windows 2025 and Windows 2019.

mtelvers added 5 commits March 3, 2026 11:30
Move initialize_threading() to run before pool_mutex is accessed in
lwt_unix_start_job, rather than only inside the DETACH/SWITCH case.

On Windows, CRITICAL_SECTION must be explicitly initialized via
InitializeCriticalSection before use. Unlike pthreads where a
zero-initialized mutex (PTHREAD_MUTEX_INITIALIZER) is valid, a
zero-initialized CRITICAL_SECTION is invalid. The previous code could
access pool_mutex before initialize_threading() was called, causing
crashes or undefined behavior on Windows when the first job used
async_method != NONE.

initialize_threading() is idempotent (guarded by threading_initialized
flag), so calling it earlier is safe and has no effect on Unix platforms.
Switch from STARTUPINFO to STARTUPINFOEX with
PROC_THREAD_ATTRIBUTE_HANDLE_LIST to explicitly specify which handles
the child process should inherit.

Previously, using SetHandleInformation + bInheritHandles=TRUE caused a
race condition where all inheritable handles (including those from
concurrent process spawns) would leak into child processes. The new
approach uses the Microsoft-recommended PROC_THREAD_ATTRIBUTE_HANDLE_LIST
to restrict inheritance to only the intended stdin/stdout/stderr handles.

The handle list is deduplicated since UpdateProcThreadAttribute requires
unique handle values (e.g. when stdout and stderr point to the same
handle).

Also fixes the comment to correctly reference fd0, fd1, fd2 instead of
fd1, fd2, fd3.
Add a new Windows-specific waitpid job (windows_waitpid_job.c) that uses
OpenProcess + WaitForSingleObject + GetExitCodeProcess instead of the
POSIX-style Unix.waitpid which doesn't work properly on Windows.

The new implementation:
- Runs in a worker thread via the Lwt job system for proper async behavior
- Supports WNOHANG via a 0-timeout WaitForSingleObject call
- Returns (0, WEXITED 0) for WNOHANG when the process is still running,
  matching the POSIX convention
- Returns (pid, WEXITED exit_code) when the process has exited

The OCaml side dispatches to _win32_waitpid on Windows (which runs the
new C job) instead of _waitpid (which called Unix.waitpid synchronously).
On Windows, select() only works with socket handles, not with pipe
handles or regular file handles. Calling wait_read or wait_write on
a blocking pipe fd would fail because the underlying select() call
cannot handle non-socket HANDLEs.

Skip the wait_read/wait_write calls on Windows (guarded by Sys.win32)
and let the worker thread handle blocking directly. This is safe
because Sys.win32 is a compile-time constant, so there is no runtime
overhead on Unix platforms.

Affected call sites: read, pread, read_bigarray, write, pwrite,
write_bigarray (6 total).
Copy link
Copy Markdown
Collaborator

@raphael-proust raphael-proust left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks a lot for the contribution that's outside of my area, it's very valuable <3

i'll try to get someone to read the windows-specific C code before merging

wait_read ch >>= fun () ->
(* On Windows, select() doesn't work with pipe handles, so skip
wait_read and let the worker thread handle blocking directly. *)
(if Sys.win32 then Lwt.return_unit else wait_read ch) >>= fun () ->
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somewhat nitpicky but also i want to keep the code very readable to ease maintenance…

I think i'd rather have the branch hoisted up so that the AST is flatter (if wider)

Lazy.force ch.blocking >>= function
| true when Sys.win32 ->
  (* comment *)
  windows code
| true ->
  linux code true
| false ->
  linux code false

Copy link
Copy Markdown
Author

@mtelvers mtelvers Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've refactored this usage pattern.

}
}

#endif
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the bandwidth to review this rn. I'm completely unfamiliar with the specifics of windows. I'll try to ping someone who can help with that part.

if (async_method != LWT_UNIX_ASYNC_METHOD_NONE) {
initialize_threading();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a fix for #1104 actually

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants