Skip to content

Commit 439e538

Browse files
authored
Merge pull request #497 from patricoferris/posix-windows
Posix-based windows implementation
2 parents b5b5de7 + cab124d commit 439e538

28 files changed

+1542
-7
lines changed

.github/workflows/main.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,31 @@ jobs:
3232
- run: opam --cli=2.1 pin -yn --with-version=dev .
3333
- run: opam install ${{ matrix.local-packages }} --deps-only --with-test
3434
- run: opam install ${{ matrix.local-packages }} --with-test
35+
windows:
36+
runs-on: windows-latest
3537

38+
steps:
39+
- name: Checkout code
40+
uses: actions/checkout@v3
41+
42+
- name: Set-up OCaml
43+
uses: ocaml/setup-ocaml@v2
44+
with:
45+
opam-pin: false
46+
opam-depext: false
47+
ocaml-compiler: ocaml.5.0.0,ocaml-option-mingw
48+
opam-repositories: |
49+
dra27: https://github.com/dra27/opam-repository.git#windows-5.0
50+
duniverse: git+https://github.com/dune-universe/opam-overlays
51+
normal: https://github.com/ocaml/opam-repository.git
52+
default: https://github.com/ocaml-opam/opam-repository-mingw.git#opam2
53+
# --with-version=dev is not available, and --with-test also tries running tests for packages (like MDX) which fail...
54+
- run: |
55+
opam pin -yn eio.dev .
56+
opam pin -yn eio_windows.dev .
57+
opam install ocamlfind.1.9.5 kcas alcotest mdx crowbar -y
58+
opam install eio eio_windows --deps-only
59+
- run: opam exec -- dune runtest
3660
docker:
3761
runs-on: ubuntu-latest
3862
steps:

doc/dune

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
(mdx
22
(package eio_main)
33
(deps (package eio_main) (env_var "EIO_BACKEND"))
4+
(enabled_if (<> %{os_type} "Win32"))
45
(files multicore.md))

dune

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
(package eio_main)
33
(deps (package eio_main) (env_var "EIO_BACKEND"))
44
(preludes doc/prelude.ml)
5+
(enabled_if (<> %{os_type} "Win32"))
56
(files README.md))

dune-project

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,13 @@
5656
(package
5757
(name eio_windows)
5858
(synopsis "Eio implementation for Windows")
59-
(description "An Eio implementation using I/O Completion Ports")
59+
(description "An Eio implementation using OCaml's Unix.select")
6060
(allow_empty) ; Work-around for dune bug #6938
6161
(depends
62-
(eio (= :version))))
62+
(eio (= :version))
63+
(cstruct-unix (= dev))
64+
(kcas (and (>= 0.3.0) :with-test))
65+
(alcotest (and (>= 1.4.0) :with-test))))
6366
(package
6467
(name eio_main)
6568
(synopsis "Effect-based direct-style IO mainloop for OCaml")

eio_windows.opam

100644100755
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# This file is generated by dune, edit dune-project instead
22
opam-version: "2.0"
33
synopsis: "Eio implementation for Windows"
4-
description: "An Eio implementation using I/O Completion Ports"
4+
description: "An Eio implementation using OCaml's Unix.select"
55
maintainer: ["[email protected]"]
66
authors: ["Anil Madhavapeddy" "Thomas Leonard"]
77
license: "ISC"
@@ -11,6 +11,9 @@ bug-reports: "https://github.com/ocaml-multicore/eio/issues"
1111
depends: [
1212
"dune" {>= "3.7"}
1313
"eio" {= version}
14+
"cstruct-unix" {= "dev"}
15+
"kcas" {>= "0.3.0" & with-test}
16+
"alcotest" {>= "1.4.0" & with-test}
1417
"odoc" {with-doc}
1518
]
1619
build: [
@@ -28,3 +31,10 @@ build: [
2831
]
2932
]
3033
dev-repo: "git+https://github.com/ocaml-multicore/eio.git"
34+
pin-depends: [
35+
# Removes base bytes for crowbar
36+
[ "ocplib-endian.dev" "git+https://github.com/Leonidas-from-XIV/ocplib-endian#fda4d5525063c8444020be369c63de23d39c246e" ]
37+
# Needed for the cstruct read and writes without copying
38+
[ "cstruct.dev" "git+https://github.com/djs55/ocaml-cstruct#471ca03b49b3a372945fcf13c89e0447a8bd3932" ]
39+
[ "cstruct-unix.dev" "git+https://github.com/djs55/ocaml-cstruct#471ca03b49b3a372945fcf13c89e0447a8bd3932" ]
40+
]

eio_windows.opam.template

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pin-depends: [
2+
# Removes base bytes for crowbar
3+
[ "ocplib-endian.dev" "git+https://github.com/Leonidas-from-XIV/ocplib-endian#fda4d5525063c8444020be369c63de23d39c246e" ]
4+
# Needed for the cstruct read and writes without copying
5+
[ "cstruct.dev" "git+https://github.com/djs55/ocaml-cstruct#471ca03b49b3a372945fcf13c89e0447a8bd3932" ]
6+
[ "cstruct-unix.dev" "git+https://github.com/djs55/ocaml-cstruct#471ca03b49b3a372945fcf13c89e0447a8bd3932" ]
7+
]

lib_eio/tests/dune

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
(mdx
22
(package eio)
3+
(enabled_if (<> %{os_type} "Win32"))
34
(deps
45
(package eio)
56
(file ./dscheck/fake_sched.ml)

lib_eio/unix/fork_action.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
#include <errno.h>
66

77
#include <caml/mlvalues.h>
8+
#include <caml/unixsupport.h>
89

910
#include "fork_action.h"
1011

12+
#ifndef _WIN32
1113
void eio_unix_run_fork_actions(int errors, value v_actions) {
1214
int old_flags = fcntl(errors, F_GETFL, 0);
1315
fcntl(errors, F_SETFL, old_flags & ~O_NONBLOCK);
@@ -19,6 +21,7 @@ void eio_unix_run_fork_actions(int errors, value v_actions) {
1921
}
2022
_exit(1);
2123
}
24+
#endif
2225

2326
static void try_write_all(int fd, char *buf) {
2427
int len = strlen(buf);
@@ -67,13 +70,17 @@ CAMLprim value eio_unix_fork_execve(value v_unit) {
6770
}
6871

6972
static void action_fchdir(int errors, value v_config) {
73+
#ifdef _WIN32
74+
eio_unix_fork_error(errors, "action_fchdir", "Unsupported operation on windows");
75+
#else
7076
value v_fd = Field(v_config, 1);
7177
int r;
7278
r = fchdir(Int_val(v_fd));
7379
if (r != 0) {
7480
eio_unix_fork_error(errors, "fchdir", strerror(errno));
7581
_exit(1);
7682
}
83+
#endif
7784
}
7885

7986
CAMLprim value eio_unix_fork_fchdir(value v_unit) {
@@ -95,6 +102,9 @@ CAMLprim value eio_unix_fork_chdir(value v_unit) {
95102
}
96103

97104
static void set_blocking(int errors, int fd, int blocking) {
105+
#ifdef _WIN32
106+
eio_unix_fork_error(errors, "set_blocking", "Unsupported operation on windows");
107+
#else
98108
int r = fcntl(fd, F_GETFL, 0);
99109
if (r != -1) {
100110
int flags = blocking
@@ -108,9 +118,13 @@ static void set_blocking(int errors, int fd, int blocking) {
108118
eio_unix_fork_error(errors, "fcntl", strerror(errno));
109119
_exit(1);
110120
}
121+
#endif
111122
}
112123

113124
static void set_cloexec(int errors, int fd, int cloexec) {
125+
#ifdef _WIN32
126+
eio_unix_fork_error(errors, "set_cloexec", "Unsupported operation on windows");
127+
#else
114128
int r = fcntl(fd, F_GETFD, 0);
115129
if (r != -1) {
116130
int flags = cloexec
@@ -124,6 +138,7 @@ static void set_cloexec(int errors, int fd, int cloexec) {
124138
eio_unix_fork_error(errors, "fcntl", strerror(errno));
125139
_exit(1);
126140
}
141+
#endif
127142
}
128143

129144
static void action_dups(int errors, value v_config) {

lib_eio/unix/stubs.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
#include <caml/unixsupport.h>
66

77
CAMLprim value eio_unix_is_blocking(value v_fd) {
8+
#ifdef _WIN32
9+
// We should not call this function from Windows
10+
uerror("Unsupported blocking check on Windows", Nothing);
11+
#else
812
int fd = Int_val(v_fd);
913
int r = fcntl(fd, F_GETFL, 0);
1014
if (r == -1)
1115
uerror("fcntl", Nothing);
1216

1317
return Val_bool((r & O_NONBLOCK) == 0);
18+
#endif
1419
}

lib_eio_windows/domain_mgr.ml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
(*
2+
* Copyright (C) 2023 Thomas Leonard
3+
*
4+
* Permission to use, copy, modify, and distribute this software for any
5+
* purpose with or without fee is hereby granted, provided that the above
6+
* copyright notice and this permission notice appear in all copies.
7+
*
8+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10+
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15+
*)
16+
17+
open Eio.Std
18+
19+
[@@@alert "-unstable"]
20+
21+
module Fd = Eio_unix.Fd
22+
23+
(* Run an event loop in the current domain, using [fn x] as the root fiber. *)
24+
let run_event_loop fn x =
25+
Sched.with_sched @@ fun sched ->
26+
let open Effect.Deep in
27+
let extra_effects : _ effect_handler = {
28+
effc = fun (type a) (e : a Effect.t) : ((a, Sched.exit) continuation -> Sched.exit) option ->
29+
match e with
30+
| Eio_unix.Private.Get_monotonic_clock -> Some (fun k -> continue k (Time.mono_clock : Eio.Time.Mono.t))
31+
| Eio_unix.Private.Socket_of_fd (sw, close_unix, unix_fd) -> Some (fun k ->
32+
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
33+
(* TODO: On Windows, if the FD from Unix.pipe () is passed this will fail *)
34+
(try Unix.set_nonblock unix_fd with Unix.Unix_error (Unix.ENOTSOCK, _, _) -> ());
35+
continue k (Flow.of_fd fd :> Eio_unix.socket)
36+
)
37+
| Eio_unix.Private.Socketpair (sw, domain, ty, protocol) -> Some (fun k ->
38+
match
39+
let unix_a, unix_b = Unix.socketpair ~cloexec:true domain ty protocol in
40+
let a = Fd.of_unix ~sw ~blocking:false ~close_unix:true unix_a in
41+
let b = Fd.of_unix ~sw ~blocking:false ~close_unix:true unix_b in
42+
Unix.set_nonblock unix_a;
43+
Unix.set_nonblock unix_b;
44+
(Flow.of_fd a :> Eio_unix.socket), (Flow.of_fd b :> Eio_unix.socket)
45+
with
46+
| r -> continue k r
47+
| exception Unix.Unix_error (code, name, arg) ->
48+
discontinue k (Err.wrap code name arg)
49+
)
50+
| Eio_unix.Private.Pipe sw -> Some (fun k ->
51+
match
52+
let r, w = Low_level.pipe ~sw in
53+
let source = (Flow.of_fd r :> Eio_unix.source) in
54+
let sink = (Flow.of_fd w :> Eio_unix.sink) in
55+
(source, sink)
56+
with
57+
| r -> continue k r
58+
| exception Unix.Unix_error (code, name, arg) ->
59+
discontinue k (Err.wrap code name arg)
60+
)
61+
| _ -> None
62+
}
63+
in
64+
Sched.run ~extra_effects sched fn x
65+
66+
let v = object
67+
inherit Eio.Domain_manager.t
68+
69+
method run_raw fn =
70+
let domain = ref None in
71+
Eio.Private.Suspend.enter (fun _ctx enqueue ->
72+
domain := Some (Domain.spawn (fun () -> Fun.protect fn ~finally:(fun () -> enqueue (Ok ()))))
73+
);
74+
Domain.join (Option.get !domain)
75+
76+
method run fn =
77+
let domain = ref None in
78+
Eio.Private.Suspend.enter (fun ctx enqueue ->
79+
let cancelled, set_cancelled = Promise.create () in
80+
Eio.Private.Fiber_context.set_cancel_fn ctx (Promise.resolve set_cancelled);
81+
domain := Some (Domain.spawn (fun () ->
82+
Fun.protect (run_event_loop (fun () -> fn ~cancelled))
83+
~finally:(fun () -> enqueue (Ok ()))))
84+
);
85+
Domain.join (Option.get !domain)
86+
end

lib_eio_windows/dune

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
(library
22
(name eio_windows)
33
(public_name eio_windows)
4+
(library_flags :standard -ccopt -lbcrypt)
45
(enabled_if (= %{os_type} "Win32"))
5-
(libraries eio eio.utils fmt))
6+
(foreign_stubs
7+
(language c)
8+
(include_dirs ../lib_eio/unix/include)
9+
(names eio_windows_stubs))
10+
(libraries eio eio.unix eio.utils fmt cstruct-unix))
11+
12+
(rule
13+
(targets config.ml)
14+
(action (run ./include/discover.exe)))

lib_eio_windows/eio_windows.ml

100644100755
Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,49 @@
1-
(* Can base this on the eio_posix directory structure.
2-
See HACKING.md for instructions on creating a new backend. *)
3-
let run _main = failwith "TODO: Windows support."
1+
(*
2+
* Copyright (C) 2023 Thomas Leonard
3+
*
4+
* Permission to use, copy, modify, and distribute this software for any
5+
* purpose with or without fee is hereby granted, provided that the above
6+
* copyright notice and this permission notice appear in all copies.
7+
*
8+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10+
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15+
*)
16+
17+
module Low_level = Low_level
18+
19+
type stdenv = <
20+
stdin : <Eio.Flow.source; Eio_unix.Resource.t>;
21+
stdout : <Eio.Flow.sink; Eio_unix.Resource.t>;
22+
stderr : <Eio.Flow.sink; Eio_unix.Resource.t>;
23+
net : Eio.Net.t;
24+
domain_mgr : Eio.Domain_manager.t;
25+
clock : Eio.Time.clock;
26+
mono_clock : Eio.Time.Mono.t;
27+
fs : Eio.Fs.dir Eio.Path.t;
28+
cwd : Eio.Fs.dir Eio.Path.t;
29+
secure_random : Eio.Flow.source;
30+
debug : Eio.Debug.t;
31+
>
32+
33+
let run main =
34+
let stdin = (Flow.of_fd Eio_unix.Fd.stdin :> <Eio.Flow.source; Eio_unix.Resource.t>) in
35+
let stdout = (Flow.of_fd Eio_unix.Fd.stdout :> <Eio.Flow.sink; Eio_unix.Resource.t>) in
36+
let stderr = (Flow.of_fd Eio_unix.Fd.stderr :> <Eio.Flow.sink; Eio_unix.Resource.t>) in
37+
Domain_mgr.run_event_loop main @@ object (_ : stdenv)
38+
method stdin = stdin
39+
method stdout = stdout
40+
method stderr = stderr
41+
method debug = Eio.Private.Debug.v
42+
method clock = Time.clock
43+
method mono_clock = Time.mono_clock
44+
method net = Net.v
45+
method domain_mgr = Domain_mgr.v
46+
method cwd = failwith "file-system operations not supported on Windows yet"
47+
method fs = failwith "file-system operations not supported on Windows yet"
48+
method secure_random = Flow.secure_random
49+
end

lib_eio_windows/eio_windows.mli

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
(** Fallback Eio backend for Windows using OCaml's [Unix.select]. *)
2+
3+
type stdenv = <
4+
stdin : <Eio.Flow.source; Eio_unix.Resource.t>;
5+
stdout : <Eio.Flow.sink; Eio_unix.Resource.t>;
6+
stderr : <Eio.Flow.sink; Eio_unix.Resource.t>;
7+
net : Eio.Net.t;
8+
domain_mgr : Eio.Domain_manager.t;
9+
clock : Eio.Time.clock;
10+
mono_clock : Eio.Time.Mono.t;
11+
fs : Eio.Fs.dir Eio.Path.t;
12+
cwd : Eio.Fs.dir Eio.Path.t;
13+
secure_random : Eio.Flow.source;
14+
debug : Eio.Debug.t;
15+
>
16+
(** An extended version of {!Eio.Stdenv.t} with some extra features available on Windows. *)
17+
18+
val run : (stdenv -> 'a) -> 'a
19+
(** [run main] runs an event loop and calls [main stdenv] inside it.
20+
21+
For portable code, you should use {!Eio_main.run} instead, which will call this for you if appropriate. *)
22+
23+
module Low_level = Low_level
24+
(** Low-level API. *)

0 commit comments

Comments
 (0)