Skip to content

Commit

Permalink
Merge pull request #55 from dinosaure/fuzz-rowex
Browse files Browse the repository at this point in the history
Fuzz rowex
  • Loading branch information
dinosaure authored Apr 10, 2023
2 parents 225ef1e + 2c57f0c commit 70be87b
Show file tree
Hide file tree
Showing 13 changed files with 299 additions and 60 deletions.
2 changes: 1 addition & 1 deletion .ocamlformat
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version=0.21.0
version=0.25.1
disable=true
13 changes: 7 additions & 6 deletions bin/insert.ml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ let insert _ path (key : Rowex.key) value =
let th0 =
let open Part in
let* () = open_index writer ~path:(Fpath.to_string path) in
let* () = insert key value in
close in
match Part.(run closed th0) with
| _closed, () -> `Ok ()
| exception Rowex.Duplicate ->
`Error (false, Fmt.str "%S already exists into %a." (key :> string) Fpath.pp path)
let* res = insert key value in
let* () = close in
match res with
| Ok () -> return (`Ok ())
| Error `Already_exists ->
return (`Error (false, Fmt.str "%S already exists into %a." (key :> string) Fpath.pp path)) in
Part.(run closed th0) |> snd

open Cmdliner

Expand Down
4 changes: 2 additions & 2 deletions bin/rwx.ml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ let insert state key value =
let open Part in
insert key value in
match Part.run state th0 with
| state, () -> state
| exception Rowex.Duplicate ->
| state, Ok () -> state
| state, Error `Already_exists ->
Fmt.pr "# %S already exists.\n%!" (key :> string) ;
state

Expand Down
10 changes: 10 additions & 0 deletions fuzz/dune
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
(modules monolith)
(libraries art monolith))

(executable
(name fuzz_rowex)
(modules fuzz_rowex)
(libraries rowex.mem crowbar))

(rule
(alias runtest)
(action
Expand All @@ -17,3 +22,8 @@
(alias monolith)
(action
(run ./monolith.exe)))

(rule
(alias runtest)
(action
(run ./fuzz_rowex.exe)))
21 changes: 21 additions & 0 deletions fuzz/fuzz_rowex.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
open Crowbar

let key = map [ bytes ] @@ fun k ->
if k = "" then bad_test () ;
try let k = Rowex.key k in k
with Invalid_argument _ -> bad_test ()

let memory = Bytes.create 0xFFFFFFF
module Mem = Mem.Make (struct let memory = memory end)
module Art = Rowex.Make (Mem)

let () =
add_test ~name:"simple" [ list (pair key int) ] @@ fun lst ->
let root = Art.make () in
List.fold_left (fun acc (k, v) -> match Art.insert root k v with
| () -> (k, v) :: acc
| exception Out_of_memory -> bad_test ()
| exception Rowex.Duplicate -> acc) [] lst
|> List.iter @@ fun (k, v) -> match Art.find root k with
| v' -> check_eq v v'
| exception Not_found -> failf "%S not found" (k :> string)
6 changes: 6 additions & 0 deletions lib/dune
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
(:include sse.sexp))
(names rowex)))

(library
(name mem)
(modules mem)
(public_name rowex.mem)
(libraries rowex))

(library
(name persistent)
(modules persistent hashset)
Expand Down
187 changes: 187 additions & 0 deletions lib/mem.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
open Rowex

let src = Logs.Src.create "rowex.mem"
module Log = (val Logs.src_log src : Logs.LOG)

external bytes_get_uint16 : bytes -> int -> int = "%caml_bytes_get16"
external bytes_set_uint16 : bytes -> int -> int -> unit = "%caml_bytes_set16"
external bytes_get_uint32 : bytes -> int -> int32 = "%caml_bytes_get32"
external bytes_set_uint32 : bytes -> int -> int32 -> unit = "%caml_bytes_set32"
external bytes_get_uint64 : bytes -> int -> int64 = "%caml_bytes_get64"
external bytes_set_uint64 : bytes -> int -> int64 -> unit = "%caml_bytes_set64"
external swap16 : int -> int = "%bswap16"
external swap32 : int32 -> int32 = "%bswap_int32"
external swap64 : int64 -> int64 = "%bswap_int64"

let bytes_get_leuint16 =
if Sys.big_endian
then fun buf idx -> swap16 (bytes_get_uint16 buf idx)
else bytes_get_uint16

let bytes_get_leuint32 =
if Sys.big_endian
then fun buf idx -> swap32 (bytes_get_uint32 buf idx)
else bytes_get_uint32

let bytes_get_leuint64 =
if Sys.big_endian
then fun buf idx -> swap64 (bytes_get_uint64 buf idx)
else bytes_get_uint64

let bytes_set_leuint16 =
if Sys.big_endian
then fun buf idx v -> bytes_set_uint16 buf idx (swap16 v)
else bytes_set_uint16

let bytes_set_leuint32 =
if Sys.big_endian
then fun buf idx v -> bytes_set_uint32 buf idx (swap32 v)
else bytes_set_uint32

let bytes_set_leuint64 =
if Sys.big_endian
then fun buf idx v -> bytes_set_uint64 buf idx (swap64 v)
else bytes_set_uint64

(** This module does not want to provide an optimised version of ROWEX nor
a parallel version (compatible with OCaml 5). It provides a /dumb/
implementation which can be used by fuzzers to test the ROWEX implementation
regardless ACID properties.
Don't use it! *)
module Make (Memory : sig val memory : bytes end) = struct
type 'a t = 'a

open Memory

let bind x f = f x
let return x = x
let get : type c v. 'a Addr.t -> (c, v) value -> v t = fun addr t ->
let addr = Addr.unsafe_to_int addr in (* TODO(dinosaure): [(addr :> int)] does not work, why? *)
match t with
| Int8 -> Bytes.get memory addr |> Char.code
| LEInt when Sys.word_size = 32 ->
bytes_get_leuint32 memory addr |> Int32.to_int
| LEInt when Sys.word_size = 64 ->
bytes_get_leuint64 memory addr |> Int64.to_int
| LEInt16 ->
bytes_get_leuint16 memory addr
| LEInt31 ->
bytes_get_leuint32 memory addr |> Int32.to_int
| LEInt64 ->
bytes_get_leuint64 memory addr
| LEInt128 ->
Bytes.sub_string memory addr (addr + 16)
| Addr_rd when Sys.word_size = 32 ->
bytes_get_leuint32 memory addr |> Int32.to_int |> Addr.of_int_to_rdonly
| Addr_rd when Sys.word_size = 64 ->
bytes_get_leuint64 memory addr |> Int64.to_int |> Addr.of_int_to_rdonly
| C_string ->
let buf = Buffer.create 0x10 in
let idx = ref 0 in
while Bytes.get memory (addr + !idx) <> '\000'
do Buffer.add_char buf (Bytes.get memory (addr + !idx)) ; incr idx done ;
Log.debug (fun m -> m "%016x loaded (%d byte(s)): %S" addr !idx (Buffer.contents buf));
Buffer.contents buf
| LEInt | Addr_rd -> assert false

let atomic_get : type v. 'a rd Addr.t -> (atomic, v) value -> v t =
fun addr k -> get addr k

let atomic_set : type v. 'a wr Addr.t -> (atomic, v) value -> v -> unit t =
fun addr t v ->
let addr = Addr.unsafe_to_int addr in
match t with
| Int8 -> Bytes.set memory addr (Char.chr v)
| LEInt when Sys.word_size = 32 ->
bytes_set_leuint32 memory addr (Int32.of_int v)
| LEInt when Sys.word_size = 64 ->
bytes_set_leuint64 memory addr (Int64.of_int v)
| LEInt16 ->
bytes_set_leuint16 memory addr v
| LEInt31 ->
bytes_set_leuint32 memory addr (Int32.of_int v)
| LEInt64 ->
bytes_set_leuint64 memory addr v
| LEInt128 ->
Bytes.blit memory addr (Bytes.of_string v) 0 16
| Addr_rd when Sys.word_size = 32 ->
bytes_set_leuint32 memory addr (Int32.of_int (Addr.unsafe_to_int v))
| Addr_rd when Sys.word_size = 64 ->
bytes_set_leuint64 memory addr (Int64.of_int (Addr.unsafe_to_int v))
| LEInt | Addr_rd -> assert false

let now () = int_of_float (Unix.gettimeofday ())
let free = Hashtbl.create 0x10
let keep = Hashtbl.create 0x10
let brk = ref 0

let delete addr len =
try let vs = Hashtbl.find free len in
Hashtbl.add free len (Addr.unsafe_to_int addr :: vs)
with Not_found ->
Hashtbl.add free len [ Addr.unsafe_to_int addr ]

let collect () =
let commit = now () in
Hashtbl.filter_map_inplace (fun time (addr, len) ->
if time < commit
then ( delete (Addr.of_int_to_rdwr addr) len ; None )
else Some (addr, len)) keep

let lint ~kind addr len payloads =
Bytes.blit_string (String.concat "" payloads) 0 memory addr len;
if kind = `Node then bytes_set_leuint64 memory (addr + _header_owner) (Int64.of_int (now ()))

let allocate ~kind ?len payloads =
let len = match len with
| Some len -> len
| None -> List.fold_left (fun a s -> a + String.length s) 0 payloads in
let rec alloc tries =
if tries <= 0
then
if !brk + len > Bytes.length memory
then raise Out_of_memory
else ( let addr = !brk in
lint ~kind addr len payloads
; brk := !brk + len
; Addr.of_int_to_rdwr addr )
else match Hashtbl.find_opt free len with
| None | Some [] -> collect () ; alloc (pred tries)
| Some (cell :: rest) ->
Hashtbl.replace free len rest;
lint ~kind cell len payloads;
Addr.of_int_to_rdwr cell in
alloc 1

let collect : _ Addr.t -> len:int -> uid:int -> unit = fun addr ~len ~uid:time ->
Hashtbl.add keep time (Addr.unsafe_to_int addr, len)

let fetch_add
: rdwr Addr.t -> (atomic, int) value -> int -> int t
= fun addr t v ->
let v' = get addr t in
atomic_set addr t (v + v') ; v'

let fetch_or
: rdwr Addr.t -> (atomic, int) value -> int -> int t
= fun addr t v ->
let v' = get addr t in
atomic_set addr t (v lor v') ; v'

let fetch_sub
: rdwr Addr.t -> (atomic, int) value -> int -> int t
= fun addr t v ->
let v' = get addr t in
atomic_set addr t (v - v') ; v'

let compare_exchange
: type v. ?weak:bool -> rdwr Addr.t -> (atomic, v) value -> v Atomic.t -> v -> bool t
= fun ?weak:_ addr t seen v ->
let v' = get addr t in
if v' = Atomic.get seen then ( atomic_set addr t v ; true )
else false

let pause_intrinsic = ()
let persist _addr ~len:_ = ()
end
1 change: 1 addition & 0 deletions lib/mem.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module Make (S : sig val memory : bytes end) : Rowex.S with type 'a t = 'a
47 changes: 29 additions & 18 deletions lib/part.ml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ type 'c capabilities =
| Writer : rdwr capabilities

type ('fd, 'c) fd =
| Unix_file_descr : Unix.file_descr -> (Unix.file_descr, rdwr) fd
| None : (none, ro) fd
| Truncate_and_file_descr : Ipc.t * Unix.file_descr -> (Unix.file_descr, rdwr) fd
| Truncate : Ipc.t -> (none, ro) fd
and none = |

let reader uid = Reader uid
Expand All @@ -33,7 +33,7 @@ type ('p, 'q, 'a) t =
| Create : string * int -> (closed, closed, (unit, [> `Msg of string ]) result) t
| Close : ('c opened, closed, unit) t
| Find : key -> ('c rd opened, 'c rd opened, int) t
| Insert : key * int -> (rdwr opened, rdwr opened, unit) t
| Insert : key * int -> (rdwr opened, rdwr opened, (unit, [> `Already_exists ]) result) t

let return x = Return x
let open_index c ~path = Open (c, path)
Expand All @@ -44,6 +44,11 @@ let bind x f = Bind (x, f)
let create ?(len= 1048576) path = Create (path, len)
let ( let* ) = bind


let is_closed : type v. v state -> bool = function
| Closed -> true
| Opened _ -> false

(* XXX(dinosaure): an explanation is needed between [truncate] and [remap].
* [truncate] updates the size of the index file and invalidate any current
* readers then about their [memory] values.
Expand Down Expand Up @@ -99,10 +104,10 @@ let rec waiting_readers trc readers =
waiting_readers trc readers

let truncate
: type v c. Ipc.t -> Ipc.t -> (v, c) fd -> readers:int Persistent.Hashset.t -> Persistent.memory -> len:int64 -> Persistent.memory
= fun ipc trc -> function
| None -> fun ~readers:_ _memory ~len:_ -> failwith "Illegal truncate"
| Unix_file_descr fd -> fun ~readers memory ~len ->
: type v c. Ipc.t -> (v, c) fd -> readers:int Persistent.Hashset.t -> Persistent.memory -> len:int64 -> Persistent.memory
= fun ipc -> function
| Truncate _ -> fun ~readers:_ _memory ~len:_ -> failwith "Illegal truncate"
| Truncate_and_file_descr (trc, fd) -> fun ~readers memory ~len ->
let old = Unix.LargeFile.fstat fd in
let len = Int64.(div (add len (of_int page_size)) (of_int page_size)) in
let len = Int64.(mul len (of_int page_size)) in
Expand Down Expand Up @@ -142,7 +147,8 @@ let rec run
Persistent.(run mmu (Persistent.find mmu key))
| Insert (key, value), Opened (mmu, capabilities, fd) ->
Opened (mmu, capabilities, fd),
Persistent.(run mmu (insert mmu key value))
( try Persistent.(run mmu (insert mmu key value)) ; Ok ()
with Rowex.Duplicate -> Error `Already_exists )
| Open (Reader uid, path), Closed ->
let ipc = Ipc.connect (Fmt.str "%s.socket" path) in
let trc = Ipc.connect (Fmt.str "%s-truncate.socket" path) in
Expand All @@ -155,12 +161,14 @@ let rec run
let memory = Bigarray.array1_of_genarray memory in
Unix.close fd ; memory in
let memory = Ipc.with_lock ~f ipc in
let mmu = Persistent.ro ~truncate:(truncate ipc trc None) ipc memory in
let mmu = Persistent.ro ~truncate:(truncate ipc (Truncate trc)) ipc memory in
Sys.set_signal Sys.sigusr1 (Signal_handle (remap trc path mmu)) ;
Ipc.enqueue ipc uid ; Opened (mmu, Reader uid, None), ()
| Close, Opened (mmu, Reader uid, _) ->
(* TODO(dinosaure): keep [trc] to properly close it! *)
Ipc.enqueue ipc uid ; Opened (mmu, Reader uid, Truncate trc), ()
| Close, Opened (mmu, Reader uid, Truncate trc) ->
let ipc = Persistent.ipc mmu in
Ipc.enqueue ipc uid ; Closed, ()
Ipc.enqueue ipc uid ;
Ipc.close ipc ; Ipc.close trc ; Closed, ()
| Open (Writer, path), Closed ->
let fd = Unix.openfile path Unix.[ O_RDWR ] 0o644 in
let len = ((Unix.fstat fd).st_size + page_size) / page_size in
Expand All @@ -171,8 +179,9 @@ let rec run
let ipc = Ipc.connect (Fmt.str "%s.socket" path) in
let trc = Ipc.connect (Fmt.str "%s-truncate.socket" path) in
let mmu = Persistent.rdwr
~truncate:(truncate ipc trc (Unix_file_descr fd)) ipc memory in
Opened (mmu, Writer, Unix_file_descr fd), ()
~truncate:(truncate ipc (Truncate_and_file_descr (trc, fd))) ipc memory in
(* TODO(dinosaure): keep [trc] to properly close it! *)
Opened (mmu, Writer, Truncate_and_file_descr (trc, fd)), ()
| Create (path, len), Closed ->
let fd = Unix.openfile path Unix.[ O_CREAT; O_RDWR ] 0o644 in
let _ = Unix.lseek fd len Unix.SEEK_SET in
Expand All @@ -187,12 +196,14 @@ let rec run
let ipc = Ipc.connect (Fmt.str "%s.socket" path) in
let trc = Ipc.connect (Fmt.str "%s-truncate.socket" path) in
let _mmu = Persistent.rdwr
~truncate:(truncate ipc trc None) ipc memory in
~truncate:(truncate ipc (Truncate trc)) ipc memory in
let _mmu = Persistent.run _mmu
(Persistent.make ~truncate:(truncate ipc trc None) ipc memory) in
Ipc.close ipc ; Unix.close fd ; Closed, Ok ()
(Persistent.make ~truncate:(truncate ipc (Truncate trc)) ipc memory) in
Ipc.close ipc ; Ipc.close trc ; Unix.close fd ; Closed, Ok ()
| Error err, _ | _, Error err -> Closed, Error err )
| Close, Opened (_mmu, Writer, Unix_file_descr fd) ->
| Close, Opened (mmu, Writer, (Truncate_and_file_descr (trc, fd))) ->
let ipc = Persistent.ipc mmu in
Ipc.close ipc ; Ipc.close trc ;
Unix.close fd ; Closed, ()

(* XXX(dinosaure): see ocaml/ocaml#12161 *)
Expand Down
Loading

0 comments on commit 70be87b

Please sign in to comment.