···28282929(* Free-list allocator *)
3030type 'a t =
3131- { data: 'a entry array
3131+ { mutable data: 'a entry array
3232 (* Pool of potentially-empty data slots. Invariant: an unfreed pointer [p]
3333 into this array is valid iff [free_tail_relation.(p) = slot_taken]. *)
3434 ; mutable free_head: ptr
3535- ; free_tail_relation: ptr array
3535+ ; mutable free_tail_relation: ptr array
3636 (* A linked list of pointers to free slots, with [free_head] being the first
3737 element and [free_tail_relation] mapping each free slot to the next one.
3838 Each entry [x] signals a state of the corresponding [data.(x)] slot:
···6666 in
6767 { data; free_head; free_tail_relation; in_use = 0 }
68686969-exception No_space
6969+let in_use t = t.in_use
7070+7171+let is_released t = t.in_use < 0
7272+7373+let maybe_already_released t =
7474+ if is_released t then
7575+ invalid_arg "Heap already released!"
7676+7777+let release t =
7878+ if t.in_use > 0 then invalid_arg "Heap still in use!";
7979+ maybe_already_released t;
8080+ t.in_use <- -100;
8181+ t.free_head <- free_list_nil
8282+8383+(* Note: t must be full *)
8484+let grow t =
8585+ maybe_already_released t;
8686+ if t.free_head <> free_list_nil then invalid_arg "Heap is not full";
8787+ let old_len = Array.length t.free_tail_relation in
8888+ if old_len = Sys.max_array_length then
8989+ invalid_arg "Heap at Sys.max_array_length already";
9090+ let new_len = min (max 64 (old_len * 2)) Sys.max_array_length in
9191+ (* Build new t.free_tail_relation, keep in sync with create() *)
9292+ let new_free_tail_relation =
9393+ Array.init new_len
9494+ (fun i ->
9595+ if i < old_len then
9696+ t.free_tail_relation.(i)
9797+ else succ i)
9898+ in
9999+ new_free_tail_relation.(new_len - 1) <- free_list_nil;
100100+ (* First element of enlarged array *)
101101+ let new_free_head = old_len in
102102+ (* Note: Keep in sync with create() *)
103103+ let new_data =
104104+ Array.init new_len
105105+ (fun i ->
106106+ if i < old_len then
107107+ t.data.(i)
108108+ else
109109+ Empty)
110110+ in
111111+ (* Commit *)
112112+ t.free_tail_relation <- new_free_tail_relation;
113113+ t.free_head <- new_free_head;
114114+ t.data <- new_data
7011571116let alloc t data ~extra_data =
117117+ if t.free_head = free_list_nil then grow t;
72118 let ptr = t.free_head in
7373- if ptr = free_list_nil then raise No_space;
74119 let entry = Entry { data; extra_data; ptr } in
75120 t.data.(ptr) <- entry;
7676-77121 (* Drop [ptr] from the free list. *)
78122 let tail = t.free_tail_relation.(ptr) in
79123 t.free_tail_relation.(ptr) <- slot_taken;
80124 t.free_head <- tail;
81125 t.in_use <- t.in_use + 1;
8282-83126 entry
8412785128let free t ptr =
···109152110153 datum
111154112112-let in_use t = t.in_use
113113-114114-let release t =
115115- if t.in_use > 0 then invalid_arg "Heap still in use!"
116116- else if t.in_use < 0 then invalid_arg "Heap already released!";
117117- t.in_use <- -100;
118118- t.free_head <- free_list_nil
119119-120120-let is_released t = t.in_use < 0
+4-5
lib/uring/heap.mli
···3030(** [ptr e] is the index of [e].
3131 @raise Invalid_arg if [e] has already been freed. *)
32323333-exception No_space
3434-3533val alloc : 'a t -> 'a -> extra_data:'b -> 'a entry
3636-(** [alloc t a ~extra_data] adds the value [a] to [t] and returns a pointer to that value,
3737- or raises {!No_space} if no space exists in [t].
3434+(** [alloc t a ~extra_data] adds the value [a] to [t] and returns a
3535+ pointer to that value, or raises {!Invalid_arg} if no extra space
3636+ can be created for [t], or [t] has already been [release]d.
3837 @param extra_data Prevent this from being GC'd until [free] is called. *)
39384039val free : 'a t -> ptr -> 'a
···4544(** [in_use t] is the number of entries currently allocated. *)
46454746val release : _ t -> unit
4848-(**[ release t] marks [t] as unusable. Future operations on it will fail. [t] must be idle. *)
4747+(** [release t] marks [t] as unusable. Future operations on it will fail. [t] must be idle. *)
49485049val is_released : _ t -> bool
5150(** [is_released t] is [true] once {!release} has succeeded. *)
+10-12
lib/uring/uring.ml
···344344345345let with_id_full : type a. a t -> (Heap.ptr -> bool) -> a -> extra_data:'b -> a job option =
346346 fun t fn datum ~extra_data ->
347347- match Heap.alloc t.data datum ~extra_data with
348348- | exception Heap.No_space ->
349349- check t; (* Check if it's because we exited already. *)
350350- None
347347+ match Heap.alloc t.data datum ~extra_data with
348348+ | exception (Invalid_argument _ as ex) -> check t; raise ex
351349 | entry ->
352352- let ptr = Heap.ptr entry in
353353- let has_space = fn ptr in
354354- if has_space then (
355355- Some entry
356356- ) else (
357357- ignore (Heap.free t.data ptr : a);
358358- None
359359- )
350350+ let ptr = Heap.ptr entry in
351351+ let has_space = fn ptr in
352352+ if has_space then
353353+ Some entry
354354+ else (
355355+ ignore (Heap.free t.data ptr : a);
356356+ None
357357+ )
360358361359let with_id t fn a = with_id_full t fn a ~extra_data:()
362360
+43-55
tests/heap.md
···2626Test normal usage:
27272828```ocaml
2929-# let max_size = 10 in
3030- let t = Heap.create max_size in
3131- let reference : (Heap.ptr, int) Hashtbl.t = Hashtbl.create max_size in
2929+# let initial_size = 10 in
3030+ let t = Heap.create initial_size in
3131+ let reference : (Heap.ptr, int) Hashtbl.t = Hashtbl.create initial_size in
3232 let currently_allocated = ref 0 in
3333 for _ = 1 to 100_000 do
3434 let attempt_alloc = !currently_allocated = 0 || Random.bool () in
3535 match attempt_alloc with
3636 | true ->
3737- if !currently_allocated = max_size then
3838- try ignore (Heap.alloc t 0); assert false
3939- with Heap.No_space -> ()
4040- else
4141- let data = Random.int 5000 in
4242- let ptr = Heap.ptr (Heap.alloc t data) in
4343- assert (not (Hashtbl.mem reference ptr));
4444- Hashtbl.add reference ptr data;
4545- incr currently_allocated
3737+ let data = Random.int 5000 in
3838+ let ptr = Heap.ptr (Heap.alloc t data) in
3939+ assert (not (Hashtbl.mem reference ptr));
4040+ Hashtbl.add reference ptr data;
4141+ incr currently_allocated
4642 | false ->
4743 let (k, v) = random_hashtbl_elt reference in
4844 let v' = Heap.free t k in
4945 Hashtbl.remove reference k;
5046 assert (v = v');
5147 decr currently_allocated
5252- done;;
4848+ done;
4949+ Hashtbl.iter (fun k _ -> ignore (Heap.free t k)) reference;
5050+ Heap.release t;;
5151+- : unit = ()
5252+```
5353+5454+```ocaml
5555+let shuffle_list l =
5656+ List.map (fun i -> Random.bits (), i) l |>
5757+ List.sort (fun a b -> compare (fst a) (fst b)) |>
5858+ List.map snd
5959+```
6060+6161+```ocaml
6262+# let t = Heap.create 0 in
6363+ let add_l = List.init 1024 (fun i -> i) |> shuffle_list in
6464+ assert (Heap.in_use t = 0);
6565+ let free_l = List.map (fun i -> Heap.alloc t i |> Heap.ptr) add_l |>
6666+ shuffle_list
6767+ in
6868+ assert (Heap.in_use t = 1024);
6969+ List.iter (fun p -> ignore (Heap.free t p)) free_l;
7070+ assert (Heap.in_use t = 0);
7171+ Heap.release t;;
5372- : unit = ()
5473```
5574···6483- : int = 1
6584# Heap.free t p;;
6685Exception: Invalid_argument "Heap.free: pointer already freed".
8686+# let t : unit = Heap.release t;;
8787+val t : unit = ()
6788```
68896990Double free in a non-empty heap:
···7192```ocaml
7293# let t : int Heap.t = Heap.create 2;;;
7394val t : int Heap.t = <abstr>
7474-# let p = Heap.ptr @@ Heap.alloc t 1;;;
7575-val p : Heap.ptr = 0
7676-# let _ = Heap.ptr @@ Heap.alloc t 2;;;
7777-- : Heap.ptr = 1
7878-# Heap.free t p;;
7979-- : int = 1
8080-# Heap.free t p;;
8181-Exception: Invalid_argument "Heap.free: pointer already freed".
8282-```
8383-8484-Out of space:
8585-8686-```ocaml
8787-# let t : unit Heap.t = Heap.create 0 (* 1 > 0 *);;
8888-val t : unit Heap.t = <abstr>
8989-# Heap.ptr @@ Heap.alloc t ();;
9090-Exception: Uring__Heap.No_space.
9191-```
9292-9393-```ocaml
9494-# let t : unit Heap.t = Heap.create 2;;
9595-val t : unit Heap.t = <abstr>
9696-# Heap.ptr @@ Heap.alloc t ();;
9797-- : Heap.ptr = 0
9898-# Heap.ptr @@ Heap.alloc t ();;
9999-- : Heap.ptr = 1
100100-# Heap.ptr @@ Heap.alloc t () (* 3 > 2 *);;
101101-Exception: Uring__Heap.No_space.
102102-```
103103-104104-```ocaml
105105-# let t : int Heap.t = Heap.create 3;;
106106-val t : int Heap.t = <abstr>
107107-# let p1 = Heap.ptr @@ Heap.alloc t 1;;
9595+# let p1 = Heap.ptr @@ Heap.alloc t 1;;;
10896val p1 : Heap.ptr = 0
109109-# Heap.ptr @@ Heap.alloc t 2;;
110110-- : Heap.ptr = 1
9797+# let p2 = Heap.ptr @@ Heap.alloc t 2;;;
9898+val p2 : Heap.ptr = 1
11199# Heap.free t p1;;
112100- : int = 1
113113-# Heap.ptr @@ Heap.alloc t 3;;
114114-- : Heap.ptr = 0
115115-# Heap.ptr @@ Heap.alloc t 4;;
116116-- : Heap.ptr = 2
117117-# Heap.ptr @@ Heap.alloc t 5 (* 2 - 1 + 3 > 3 *);;
118118-Exception: Uring__Heap.No_space.
101101+# Heap.free t p1;;
102102+Exception: Invalid_argument "Heap.free: pointer already freed".
103103+# Heap.free t p2;;
104104+- : int = 2
105105+# let t : unit = Heap.release t;;
106106+val t : unit = ()
119107```
+31-1
tests/main.md
···2525 Format.printf (fmt ^^ "@.")
2626```
27272828-## Invalid queue depth
2828+## Queue depth
29293030```ocaml
3131# Uring.create ~queue_depth:0 ();;
3232Exception: Invalid_argument "Non-positive queue depth: 0".
3333+```
3434+3535+Prove we can wait more entries than queue depth
3636+3737+```ocaml
3838+# let t : [ `Read ] Uring.t = Uring.create ~queue_depth:1 ();;
3939+val t : [ `Read ] Uring.t = <abstr>
4040+4141+# let fd = Unix.openfile "/dev/zero" Unix.[O_RDONLY] 0;;
4242+val fd : Unix.file_descr = <abstr>
4343+# let b = Cstruct.create 1;;
4444+val b : Cstruct.t = {Cstruct.buffer = <abstr>; off = 0; len = 1}
4545+# Uring.read t fd b `Read ~file_offset:Int63.minus_one;;
4646+- : [ `Read ] Uring.job option = Some <abstr>
4747+# Uring.submit t;;
4848+- : int = 1
4949+# Uring.read t fd b `Read ~file_offset:Int63.minus_one;;
5050+- : [ `Read ] Uring.job option = Some <abstr>
5151+# Uring.read t fd b `Read ~file_offset:Int63.minus_one;;
5252+- : [ `Read ] Uring.job option = None
5353+# Uring.submit t;;
5454+- : int = 1
5555+# consume t;;
5656+- : [ `Read ] * int = (`Read, 1)
5757+# consume t;;
5858+- : [ `Read ] * int = (`Read, 1)
5959+# let fd : unit = Unix.close fd;;
6060+val fd : unit = ()
6161+# Uring.exit t;;
6262+- : unit = ()
3363```
34643565## Noop