···235235let unregister_gc_root t =
236236 update_gc_roots (Ring_set.remove (Generic_ring.T t))
237237238238-let default_iobuf_len = 1024 * 1024 (* 1MB *)
239239-240240-let create ?(fixed_buf_len=default_iobuf_len) ?polling_timeout ~queue_depth () =
238238+let create ?polling_timeout ~queue_depth () =
241239 if queue_depth < 1 then Fmt.invalid_arg "Non-positive queue depth: %d" queue_depth;
242240 let uring = Uring.create queue_depth polling_timeout in
243243- (* TODO posix memalign this to page *)
244244- let fixed_iobuf = Bigarray.(Array1.create char c_layout fixed_buf_len) in
245245- Uring.register_bigarray uring fixed_iobuf;
246241 let data = Heap.create queue_depth in
247242 let id = object end in
243243+ let fixed_iobuf = Cstruct.empty.buffer in
248244 let t = { id; uring; fixed_iobuf; data; dirty=false; queue_depth } in
249245 register_gc_root t;
250246 t
···254250 | 0 -> ()
255251 | n -> Fmt.invalid_arg "%s: %d request(s) still active!" op n
256252257257-let realloc t iobuf =
258258- ensure_idle t "realloc";
259259- Uring.unregister_buffers t.uring;
253253+let set_fixed_buffer t iobuf =
254254+ ensure_idle t "set_fixed_buffer";
255255+ if Bigarray.Array1.dim t.fixed_iobuf > 0 then
256256+ Uring.unregister_buffers t.uring;
260257 t.fixed_iobuf <- iobuf;
261261- Uring.register_bigarray t.uring iobuf
258258+ if Bigarray.Array1.dim iobuf > 0 then (
259259+ match Uring.register_bigarray t.uring iobuf with
260260+ | () -> Ok ()
261261+ | exception Unix.Unix_error(Unix.ENOMEM, "io_uring_register_buffers", "") -> Error `ENOMEM
262262+ ) else Ok ()
262263263264let exit t =
264265 ensure_idle t "exit";
+25-14
lib/uring/uring.mli
···2525(** A handle for a submitted job, which can be used to cancel it.
2626 If an operation returns [None], this means that submission failed because the ring is full. *)
27272828-val create : ?fixed_buf_len:int -> ?polling_timeout:int -> queue_depth:int -> unit -> 'a t
2929-(** [create ?fixed_buf_len ~queue_depth] will return a fresh Io_uring structure
3030- [t]. Each [t] has associated with it a fixed region of memory that is used
3131- for the "fixed buffer" mode of io_uring to avoid data copying between
3232- userspace and the kernel.
2828+val create : ?polling_timeout:int -> queue_depth:int -> unit -> 'a t
2929+(** [create ~queue_depth] will return a fresh Io_uring structure [t].
3030+ Initially, [t] has no fixed buffer. Use {!set_fixed_buffer} if you want one.
3331 @param polling_timeout If given, use polling mode with the given idle timeout (in ms).
3432 This requires privileges. *)
35333634val queue_depth : 'a t -> int
3735(** [queue_depth t] returns the total number of submission slots for the uring [t] *)
38363939-val buf : 'a t -> Cstruct.buffer
4040-(** [buf t] is the fixed internal memory buffer associated with uring [t].
3737+val exit : 'a t -> unit
3838+(** [exit t] will shut down the uring [t]. Any subsequent requests will fail.
3939+ @raise Invalid_argument if there are any requests in progress *)
4040+4141+(** {2 Fixed buffers}
4242+4343+ Each uring may have associated with it a fixed region of memory that is used
4444+ for the "fixed buffer" mode of io_uring to avoid data copying between
4545+ userspace and the kernel. *)
4646+4747+val set_fixed_buffer : 'a t -> Cstruct.buffer -> (unit, [> `ENOMEM]) result
4848+(** [set_fixed_buffer t buf] sets [buf] as the fixed buffer for [t].
4949+4150 You will normally want to wrap this with {!Region.alloc} or similar
4242- to divide the buffer into chunks. *)
5151+ to divide the buffer into chunks.
5252+5353+ If [t] already has a buffer set, the old one will be removed.
5454+5555+ Returns [`ENOMEM] if insufficient kernel resources are available
5656+ or the caller's RLIMIT_MEMLOCK resource limit would be exceeded.
43574444-val realloc : 'a t -> Cstruct.buffer -> unit
4545-(** [realloc t buf] will replace the internal fixed buffer associated with
4646- uring [t] with a fresh one.
4758 @raise Invalid_argument if there are any requests in progress *)
48594949-val exit : 'a t -> unit
5050-(** [exit t] will shut down the uring [t]. Any subsequent requests will fail.
5151- @raise Invalid_argument if there are any requests in progress *)
6060+val buf : 'a t -> Cstruct.buffer
6161+(** [buf t] is the fixed internal memory buffer associated with uring [t]
6262+ using {!set_fixed_buffer}, or a zero-length buffer if none is set. *)
52635364(** {2 Queueing operations} *)
5465
+17-7
tests/main.ml
···123123 check_raises ~__POS__ (Invalid_argument "Non-positive queue depth: 0")
124124 (fun () -> ignore (Uring.create ~queue_depth:0 ()))
125125126126-let with_uring ?(fixed_buf_len=1024) ~queue_depth fn =
127127- let t = Uring.create ~fixed_buf_len ~queue_depth () in
126126+let with_uring ~queue_depth fn =
127127+ let t = Uring.create ~queue_depth () in
128128 fn t;
129129 Uring.exit t (* Only free if there wasn't an error *)
130130···210210 check_bool ~__POS__ ~expected:true @@ get ~resolve:Uring.Resolve.empty "..";
211211 check_bool ~__POS__ ~expected:false @@ get ~resolve:Uring.Resolve.beneath ".."
212212213213+let set_fixed_buffer t size =
214214+ let fbuf = Bigarray.(Array1.create char c_layout size) in
215215+ match Uring.set_fixed_buffer t fbuf with
216216+ | Ok () -> fbuf
217217+ | Error `ENOMEM -> failwith "Resource limit exceeded"
218218+213219let test_read () =
214220 with_uring ~queue_depth:1 @@ fun t ->
221221+ let fbuf = set_fixed_buffer t 1024 in
215222 Test_data.with_fd @@ fun fd ->
216216-217223 let off = 3 in
218224 let len = 5 in
219225 let file_offset = Int63.of_int 2 in
···224230 assert_ ~__POS__ (token = `Read);
225231 check_int ~__POS__ read ~expected:len;
226232227227- let fbuf = Uring.buf t in
228233 let got = Cstruct.of_bigarray fbuf ~off ~len in
229234 check_string ~__POS__ ~expected:"test " (Cstruct.to_string got)
230235···261266 check_string ~__POS__ ~expected:"Gathered [A te] and [st ]" (Cstruct.to_string b)
262267263268let test_region () =
264264- with_uring ~queue_depth:1 ~fixed_buf_len:64 @@ fun t ->
269269+ with_uring ~queue_depth:1 @@ fun t ->
270270+ let fbuf = set_fixed_buffer t 64 in
265271 Test_data.with_fd @@ fun fd ->
266266- let region = Uring.Region.init (Uring.buf t) 4 ~block_size:16 in
272272+ let region = Uring.Region.init fbuf 4 ~block_size:16 in
267273 let chunk = Uring.Region.alloc region in
268274 assert_some ~__POS__ (Uring.read_chunk t fd chunk `Read ~file_offset:Int63.zero);
269275 let token, read = consume t in
···283289(* Ask to read from a pipe (with no data available), then cancel it. *)
284290let test_cancel () =
285291 with_uring ~queue_depth:5 @@ fun t ->
292292+ let _fbuf = set_fixed_buffer t 1024 in
286293 (* while true do *)
287294 let r, w = Unix.pipe () in
288295 let read = Uring.read_fixed t ~file_offset:Int63.zero r ~off:0 ~len:1 `Read |> Option.get in
···313320(* By the time we cancel, the request has already succeeded (we just didn't process the reply yet). *)
314321let test_cancel_late () =
315322 with_uring ~queue_depth:5 @@ fun t ->
323323+ let _fbuf = set_fixed_buffer t 1024 in
316324 let r = Unix.openfile "/dev/zero" Unix.[O_RDONLY] 0 in
317325 let read = Uring.read_fixed t ~file_offset:Int63.zero r ~off:0 ~len:1 `Read |> Option.get in
318326 check_int ~__POS__ (Uring.submit t) ~expected:1;
···333341 ) else (
334342 (* This isn't the case we want to test, but it can happen sometimes. *)
335343 check_int ~__POS__ ~expected:(-125) r_read; (* ECANCELED *)
336336- check_int ~__POS__ ~expected:1 r_cancel; (* Success *)
344344+ check_int ~__POS__ ~expected:0 r_cancel; (* Success *)
337345 );
338346 Unix.close r
339347340348(* By the time we cancel, we already knew the operation was over. *)
341349let test_cancel_invalid () =
342350 with_uring ~queue_depth:5 @@ fun t ->
351351+ let _fbuf = set_fixed_buffer t 1024 in
343352 let r = Unix.openfile "/dev/zero" Unix.[O_RDONLY] 0 in
344353 let read = Uring.read_fixed t ~file_offset:Int63.zero r ~off:0 ~len:1 `Read |> Option.get in
345354 let token, r_read = consume t in
···353362354363let test_free_busy () =
355364 let t = Uring.create ~queue_depth:1 () in
365365+ let _fbuf = set_fixed_buffer t 1024 in
356366 let r, w = Unix.pipe () in
357367 Fun.protect ~finally:(fun () -> Unix.close r) @@ fun () ->
358368 assert_some ~__POS__ (Uring.read_fixed t ~file_offset:Int63.minus_one r ~off:0 ~len:1 `Read);
+13-6
tests/urcp_fixed_lib.ml
···158158 let t = { freelist; block_size; insize; offset=Int63.zero; reads=0; writes=0; write_left=insize; read_left=insize; infd; outfd } in
159159 Logs.debug (fun l -> l "starting: %a bs=%d qd=%d" pp t block_size queue_depth);
160160 let fixed_buf_len = queue_depth * block_size in
161161- let uring = Uring.create ~fixed_buf_len ~queue_depth () in
162162- copy_file uring t;
163163- Unix.close infd;
164164- Unix.close outfd;
165165- Uring.exit uring;
166166- Gc.compact () (* TODO to aid debugging with valgrind, remove soon *)
161161+ let uring = Uring.create ~queue_depth () in
162162+ let fbuf = Bigarray.(Array1.create char c_layout fixed_buf_len) in
163163+ Fun.protect
164164+ (fun () ->
165165+ match Uring.set_fixed_buffer uring fbuf with
166166+ | Ok () -> copy_file uring t
167167+ | Error `ENOMEM -> failwith "Can't lock memory (check RLIMIT_MEMLOCK)"
168168+ )
169169+ ~finally:(fun () ->
170170+ Unix.close infd;
171171+ Unix.close outfd;
172172+ Uring.exit uring
173173+ )