Skip to content

Commit 9bbc258

Browse files
haesbaerttalex5
authored andcommitted
Use Iomux.Poll.ppoll instead of epoll via ocaml-poll
An Iomux.Poll.t holds has an entry for each available file descriptor. The allocation strategy is straightforward: - Each index in the poll array maps to the fd of same number. - We toggle the poll slot activation when we move from waiters->empty and empty->waiters. This is a bit different than before as we actually call `update` after `iter_ready` unregistering the possible interest. - We have to bump maxi everytime we go over the current allocated, and we don't go back, we could decrement maxi when we close the last one, but we can't really avoid holes, so don't bother. I'm surprised this worked the first time, I still have to review it.
1 parent 9e47358 commit 9bbc258

File tree

2 files changed

+49
-42
lines changed

2 files changed

+49
-42
lines changed

lib_eio_posix/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
(language c)
77
(flags :standard -D_LARGEFILE64_SOURCE)
88
(names eio_posix_stubs))
9-
(libraries eio eio.utils eio.unix fmt poll))
9+
(libraries eio eio.utils eio.unix fmt iomux))

lib_eio_posix/sched.ml

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module Lf_queue = Eio_utils.Lf_queue
2020
module Fiber_context = Eio.Private.Fiber_context
2121
module Ctf = Eio.Private.Ctf
2222
module Rcfd = Eio_unix.Private.Rcfd
23+
module Poll = Iomux.Poll
2324

2425
type exit = [`Exit_scheduler]
2526

@@ -40,6 +41,7 @@ type t = {
4041
run_q : runnable Lf_queue.t;
4142

4243
poll : Poll.t;
44+
mutable poll_maxi : int;
4345

4446
(* When adding to [run_q] from another domain, this domain may be sleeping and so won't see the event.
4547
In that case, [need_wakeup = true] and you must signal using [eventfd]. *)
@@ -106,41 +108,44 @@ let clear_event_fd t =
106108
let buf = Bytes.create 8 in
107109
let got = Unix.read t.eventfd_r buf 0 (Bytes.length buf) in
108110
assert (got > 0);
109-
Poll.set t.poll t.eventfd_r Poll.Event.read
111+
Poll.(set_index t.poll (Iomux.Util.fd_of_unix t.eventfd_r) t.eventfd_r Flags.pollin)
110112

111-
let ready t fd (event : Poll.Event.t) =
113+
let update t waiters fd =
114+
let fdi = Iomux.Util.fd_of_unix fd in
115+
let flags =
116+
match not (Lwt_dllist.is_empty waiters.read),
117+
not (Lwt_dllist.is_empty waiters.write) with
118+
| false, false -> Poll.Flags.empty
119+
| true, false -> Poll.Flags.pollin
120+
| false, true -> Poll.Flags.pollout
121+
| true, true -> Poll.Flags.(pollin + pollout)
122+
in
123+
if flags = Poll.Flags.empty then (
124+
Poll.(invalidate_index t.poll fdi);
125+
Hashtbl.remove t.fd_map fd
126+
) else (
127+
Poll.(set_index t.poll fdi fd flags);
128+
if fdi > t.poll_maxi then
129+
t.poll_maxi <- fdi
130+
)
131+
132+
let ready t _index fd revents =
133+
(* Reason about this *)
112134
if fd == t.eventfd_r then (
113135
clear_event_fd t
114136
(* The scheduler will now look at the run queue again and notice any new items. *)
115137
) else (
116138
let waiters = Hashtbl.find t.fd_map fd in
117139
let pending = Lwt_dllist.create () in
118-
if event.readable then Lwt_dllist.transfer_l waiters.read pending;
119-
if event.writable then Lwt_dllist.transfer_l waiters.write pending;
120-
(* Everything is marked as ONESHOT, so if we want to use anything later we need to re-arm with EPOLL_CTL_MOD.
121-
Except that if the FD gets closed and reassigned then we'll need to use EPOLL_CTL_ADD instead.
122-
Since we don't know whether it will be closed or not, stop watching it immediately. *)
123-
let event2 : Poll.Event.t = {
124-
readable = not (Lwt_dllist.is_empty waiters.read);
125-
writable = not (Lwt_dllist.is_empty waiters.write);
126-
} in
127-
Poll.set t.poll fd event2;
128-
if event2 = Poll.Event.none then Hashtbl.remove t.fd_map fd;
129-
Lwt_dllist.iter_node_r (remove_and_resume t) pending
130-
)
140+
if Poll.Flags.(mem revents (pollout + pollhup + pollerr)) then
141+
Lwt_dllist.transfer_l waiters.write pending;
142+
if Poll.Flags.(mem revents (pollin + pollhup + pollerr)) then
143+
Lwt_dllist.transfer_l waiters.read pending;
144+
(* If pending has things, it means we modified the waiters, refresh our view *)
145+
if not (Lwt_dllist.is_empty pending) then
146+
update t waiters fd;
147+
Lwt_dllist.iter_node_r (remove_and_resume t) pending)
131148

132-
(* todo: remove event when empty. Or when closed? But hard to know when that happens. *)
133-
let update t waiters fd =
134-
let event : Poll.Event.t = {
135-
readable = not (Lwt_dllist.is_empty waiters.read);
136-
writable = not (Lwt_dllist.is_empty waiters.write);
137-
} in
138-
try
139-
Poll.set t.poll fd event
140-
with Unix.Unix_error(Unix.EPERM, _, "") ->
141-
(* [fd] doesn't support polling (e.g. it's a regular file). Assume it won't block
142-
and wake the caller immediately. *)
143-
ready t fd event
144149

145150
let add_read t fd k =
146151
let waiters = get_waiters t fd in
@@ -183,13 +188,13 @@ let rec next t : [`Exit_scheduler] =
183188
let time = Mtime.to_uint64_ns time in
184189
let now = Mtime.to_uint64_ns now in
185190
let diff_ns = Int64.sub time now in
186-
Poll.Timeout.after diff_ns
187-
| `Nothing -> Poll.Timeout.never
191+
Poll.Nanoseconds diff_ns
192+
| `Nothing -> Poll.Infinite
188193
in
189194
if not (Lf_queue.is_empty t.run_q) then (
190195
Lf_queue.push t.run_q IO; (* Re-inject IO job in the run queue *)
191196
next t
192-
) else if timeout = Never && t.active_ops = 0 then (
197+
) else if timeout = Infinite && t.active_ops = 0 then (
193198
(* Nothing further can happen at this point. *)
194199
Lf_queue.close t.run_q; (* Just to catch bugs if something tries to enqueue later *)
195200
`Exit_scheduler
@@ -200,18 +205,14 @@ let rec next t : [`Exit_scheduler] =
200205
If [need_wakeup] is still [true], this is fine because we don't promise to do that.
201206
If [need_wakeup = false], a wake-up event will arrive and wake us up soon. *)
202207
Ctf.(note_hiatus Wait_for_work);
203-
let r =
204-
match Poll.wait t.poll timeout with
205-
| `Ok | `Timeout as x -> x
206-
| exception Unix.Unix_error(Unix.EINTR, _, "") -> `EINTR
208+
let nready =
209+
try Poll.ppoll t.poll (t.poll_maxi + 1) timeout []
210+
with Unix.Unix_error(Unix.EINTR, _, "") -> 0
207211
in
208212
Ctf.note_resume system_thread;
209213
Atomic.set t.need_wakeup false;
210214
Lf_queue.push t.run_q IO; (* Re-inject IO job in the run queue *)
211-
begin match r with
212-
| `EINTR | `Timeout -> ()
213-
| `Ok -> Poll.iter_ready t.poll ~f:(ready t); Poll.clear t.poll
214-
end;
215+
Poll.iter_ready t.poll nready (ready t);
215216
next t
216217
) else (
217218
(* Someone added a new job while we were setting [need_wakeup] to [true].
@@ -236,10 +237,16 @@ let with_sched fn =
236237
assert was_open
237238
in
238239
let poll = Poll.create () in
239-
let cleanup () = Poll.close poll; cleanup () in
240+
for i = 0 to Poll.maxfds poll - 1 do
241+
Poll.invalidate_index poll i
242+
done;
240243
let fd_map = Hashtbl.create 10 in
241-
let t = { run_q; poll; fd_map; eventfd; eventfd_r; active_ops = 0; need_wakeup = Atomic.make false; sleep_q } in
242-
Poll.set poll eventfd_r Poll.Event.read;
244+
let t = { run_q; poll; poll_maxi = 0; fd_map; eventfd; eventfd_r;
245+
active_ops = 0; need_wakeup = Atomic.make false; sleep_q } in
246+
let eventfd_ri = Iomux.Util.fd_of_unix eventfd_r in
247+
Poll.(set_index t.poll eventfd_ri eventfd_r Flags.pollin);
248+
if eventfd_ri > t.poll_maxi then
249+
t.poll_maxi <- eventfd_ri;
243250
match fn t with
244251
| x -> cleanup (); x
245252
| exception ex ->

0 commit comments

Comments
 (0)