├── .gitignore ├── .travis.yml ├── Dockerfile ├── Makefile ├── Makefile.ocaml ├── README.md ├── bin ├── dune ├── test.ml └── tjr_kv_test.ml ├── dune ├── dune-project ├── misc ├── kv_store_diagram.graphml ├── kv_store_diagram.png └── tjr_kv_config.json ├── src ├── _archive │ └── dummy_dmap_layer.ml ├── _old │ ├── dummy_btree_implementation.ml │ ├── internal_staging.ml │ ├── internal_staging.ml.html │ ├── kv_intf_v2.ml_ │ ├── kv_intf_v3.ml_ │ ├── kv_store_v3.ml_ │ ├── tjr_kv.t.ml │ └── todo.ml ├── _todo │ ├── fun_store.ml │ ├── ss_synchronous_store │ │ ├── ss_test.ml │ │ └── synchronous_store.ml │ └── unthreaded_uncached_backup.ml_ ├── btree_thread.ml ├── dune ├── kv_config_optcomp.ml ├── kv_config_profilers.ml ├── kv_config_runtime.ml ├── kv_intf.ml ├── kv_store_with_lru.ml ├── pcache_thread.ml ├── pvt_pcache_with_blocks_limit.ml ├── root_manager.ml ├── summary.ml ├── summary.t.ml ├── tjr_kv.ml ├── x_lru.ml └── x_lwt_aux.ml_ └── tjr_kv.opam /.gitignore: -------------------------------------------------------------------------------- 1 | /tjr_kv_config.json 2 | /btree.store 3 | /kv.store 4 | /freelist.store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | services: 3 | - docker 4 | 5 | script: 6 | - docker build . 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM ocaml/opam2:4.09 3 | 4 | # some of the following apt packages are likely already installed 5 | RUN sudo apt-get install -y git make 6 | RUN sudo apt-get install -y curl 7 | RUN sudo apt-get install -y gcc 8 | RUN sudo apt-get install -y bzip2 9 | RUN sudo apt-get install -y wget 10 | RUN sudo apt-get install -y unzip m4 11 | RUN sudo apt-get install -y time 12 | RUN sudo apt-get install -y rsync bubblewrap 13 | 14 | RUN opam update 15 | 16 | # install some common packages, so they are cached in future docker builds 17 | RUN opam install dune ocamlfind odoc 18 | RUN opam install core_kernel core 19 | RUN opam install re psq ppx_deriving_yojson extlib alcotest ke bos fmt fileutils 20 | 21 | # drop the RUN prefix from the following lines (and ignore previous lines!) 22 | # to build using local opam install 23 | 24 | RUN opam pin -y -n add tjr_monad https://github.com/tomjridge/tjr_monad.git 25 | RUN opam pin -y -n add tjr_profile https://github.com/tomjridge/tjr_profile.git 26 | RUN opam pin -y -n add tjr_fs_shared https://github.com/tomjridge/tjr_fs_shared.git 27 | RUN opam pin -y -n add tjr_plist https://github.com/tomjridge/tjr_plist.git 28 | RUN opam pin -y -n add isa_btree https://github.com/tomjridge/isa_btree.git 29 | RUN opam pin -y -n add tjr_btree https://github.com/tomjridge/tjr_btree.git#dev 30 | RUN opam pin -y -n add tjr_lru_cache https://github.com/tomjridge/tjr_lru_cache.git 31 | RUN opam pin -y -n add tjr_mem_queue https://github.com/tomjridge/tjr_mem_queue.git 32 | RUN opam pin -y -n add tjr_pcache https://github.com/tomjridge/tjr_pcache.git 33 | RUN opam pin -y -n add tjr_kv https://github.com/tomjridge/tjr_kv.git 34 | RUN opam pin -y -n add tjr_path_resolution https://github.com/tomjridge/path_resolution.git 35 | RUN opam pin -y -n add tjr_net https://github.com/tomjridge/tjr_net.git 36 | RUN opam pin -y -n add tjr_minifs https://github.com/tomjridge/mini-fs.git 37 | 38 | RUN opam install -y tjr_btree 39 | RUN opam install -y tjr_lru_cache 40 | RUN opam install -y tjr_pcache 41 | RUN opam install -y tjr_kv 42 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | $(MAKE) all 3 | 4 | -include Makefile.ocaml 5 | 6 | build:: 7 | $(DUNE) build --only-packages tjr_kv @install 8 | $(DUNE) build bin/tjr_kv_test.exe bin/test.exe 9 | 10 | update_generated_doc:: 11 | cd src && (ocamldoc_pyexpander kv_store_with_lru.ml) 12 | cd src && (ocamldoc_pyexpander summary.t.ml > summary.ml) 13 | 14 | 15 | run_tests: 16 | # $(DUNE) exec bin/test.exe 17 | OCAMLRUNPARAM=b $(DUNE) exec bin/tjr_kv_test.exe 18 | 19 | # for auto-completion of Makefile target 20 | clean:: 21 | rm -f *.store 22 | -------------------------------------------------------------------------------- /Makefile.ocaml: -------------------------------------------------------------------------------- 1 | # -*- makefile -*- 2 | SHELL:=bash 3 | 4 | # This is a generic Makefile.ocaml file, intended to be included in a 5 | # top-level Makefile. NOTE that this file in turn attempts to include 6 | # Makefile.local, which you can use to override defaults if needed. 7 | 8 | # scratch - the Makefile.ocaml will check here for updates to 9 | # Makefile.ocaml itself; if this path doesn't exist on your system 10 | # there is nothing to worry about 11 | scratch?=/tmp/l/github/scratch 12 | 13 | BUILD_DOCS?=#empty for no, otherwise yes 14 | 15 | # after building docs, we copy to this dir 16 | PROMOTE_DOCS?=#empty for no, otherwise yes 17 | PROMOTE_DIR?=/tmp/l/github/ocamldocs/$(notdir $(abspath .)) 18 | 19 | 20 | 21 | # generic makefile follows --------------------------------------------- 22 | 23 | # if set, bash subprocesses will read env from this file 24 | BASH_ENV=bash_env.sh 25 | export BASH_ENV 26 | 27 | # setup bash_env 28 | bash_env.sh: FORCE 29 | opam env > $(BASH_ENV) 30 | 31 | 32 | # just -include Makefile.local in your top-level Makefile, not here 33 | # # allow overriding locally 34 | # -include Makefile.local 35 | 36 | # NOTE minimal dune/opam files might be at /tmp/l/github/notes/minimal_ocaml_proj/ 37 | 38 | # allow overriding from main makefile 39 | DUNE?=dune 40 | 41 | # default: all 42 | 43 | build:: 44 | $(DUNE) build @install 45 | # note: add build:: to have more things built 46 | 47 | install:: 48 | $(DUNE) install --display quiet 2>&1 | tail 49 | 50 | uninstall:: 51 | $(DUNE) uninstall 52 | 53 | clean:: 54 | $(DUNE) clean 55 | 56 | all:: 57 | $(MAKE) Makefile.ocaml 58 | $(MAKE) build 59 | $(MAKE) install 60 | $(if $(BUILD_DOCS), $(MAKE) docs, echo "NOTE: not building docs") 61 | $(if $(PROMOTE_DOCS), $(MAKE) promote_docs, echo "NOTE: not promoting docs") 62 | 63 | clean_all:: 64 | $(MAKE) clean 65 | $(MAKE) all 66 | 67 | SRC:=_build/default/_doc/_html 68 | docs:: FORCE 69 | $(DUNE) build @doc 70 | 71 | promote_docs:: FORCE 72 | rsync -vaz $(SRC)/* $(PROMOTE_DIR) 2>&1 | tail 73 | 74 | 75 | Makefile.ocaml: FORCE 76 | @if test -f $(scratch)/Makefile.ocaml; then diff ./Makefile.ocaml $(scratch)/Makefile.ocaml; fi 77 | # FIXME update Makefile.ocaml itself, from scratch; error if 78 | # different, forcing user to update manually 79 | 80 | # https://www.gnu.org/software/make/manual/html_node/Overriding-Makefiles.html 81 | FORCE: ; 82 | 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tjr_kv, a key-value store for OCaml 2 | 3 | This is a key value store written in OCaml. It uses the B-tree code 4 | from tjr_btree, but adds an in-memory LRU cache (or just "cache") in 5 | front, and a persistent cache (pcache) between the LRU cache and the 6 | B-tree. 7 | 8 | 9 | ![](https://docs.google.com/drawings/d/e/2PACX-1vTIXhyNa7dovQYXuJXBMwPQZU99-x_tRdTIH3SkMUDyPwbL31zExWXauT2hO-eRIUcnGP3RVHiSHrjt/pub?w=557&h=428) 10 | 11 | 12 | 13 | ## Demo 14 | 15 | This is a demo (c. August 2019) of a version of the system with a B-tree placeholder. The demo runs for 2s. At the end, 800k writes have occurred at the LRU interface. 500k of these have been processed via the persistent cache and made it to the B-tree. 16 | 17 | 18 | 19 | 20 | 21 | 22 | ## Building 23 | 24 | The easiest way to build is to look at the Dockerfile and copy the relevant RUN lines (without the RUN prefix). To build with docker just run `docker build .` 25 | 26 | 27 | 28 | ## Quick links 29 | 30 | * For OCamldoc, Travis CI builds etc, see 31 | 32 | 33 | 34 | ## Glossary 35 | 36 | * Block: the "natural" unit of storage for a disk, consisting of a certain number of bytes; typically 4096 bytes in length, in which case block n starts at index n*4096 37 | * B-tree: an on-disk balanced search tree; provides the backend data store; provides quick access to values indexed by keys 38 | * LRU: short for "least-recently-used cache", a popular caching strategy; see 39 | * Message queue: ; used for passing messages between components of a system. For us, the main message queues are q_lru_pc and q_pc_bt 40 | * Persistent cache, or pcache: this is an on-disk log starting from a known root block; it supports a "detach" operation, which collects the tail of the log (and then typically inserts the entries into the B-tree). See . 41 | * Root manager: for on-disk entities, the root manager tracks the root block from which the entity can be reconstructed. For example, each B-tree has a root block, which typically contains pointers to further blocks. The dmap is another example of an on-disk entity with a root block. 42 | 43 | 44 | 45 | ## OCaml naming conventions 46 | 47 | We use the following type variables/ fixed types: 48 | 49 | * k for keys 50 | * v for values 51 | * r for pointers (integers); typically the same as block identifiers 52 | * blk_id for block identifiers (integers) 53 | * t for the generic monad phantom type 54 | 55 | 56 | 57 | ## OCaml code style 58 | 59 | * We have tried to use records for the interfaces (rather than, say, signatures and functors) where possible. This was to enable switching of implementations at runtime. Nowadays this is perhaps better achieved with first-class modules. 60 | 61 | * When listing type parameters, we try to stick to the following order: k, v, r/blk_id, blk, node, leaf, leaf_stream, t 62 | 63 | 64 | 65 | ## Dependencies 66 | 67 | | Description | Link | Comment | 68 | | ------------- | ------------------------------------------ | ------------------------------ | 69 | | tjr_btree | https://github.com/tomjridge/tjr_btree | For the persistent on-disk map | 70 | | tjr_pcache | https://github.com/tomjridge/tjr_pcache | For the persistent cache | 71 | | tjr_lru_cache | https://github.com/tomjridge/tjr_lru_cache | For the LRU in-memory cache | 72 | | tjr_mem_queue | https://github.com/tomjridge/tjr_mem_queue | For inter-thread communication | 73 | 74 | -------------------------------------------------------------------------------- /bin/dune: -------------------------------------------------------------------------------- 1 | (executables 2 | (names tjr_kv_test test) 3 | (preprocess (pps ppx_bin_prot)) 4 | (flags (:standard 5 | -open Tjr_monad -open Tjr_fs_shared 6 | ; -open Tjr_btree_examples 7 | ; -open Tjr_btree_examples.Intf 8 | -open Tjr_pcache -open Tjr_pcache.Pcache_intf 9 | -open Tjr_mem_queue -open Tjr_lru_cache -open Tjr_lru_cache 10 | -open Tjr_kv)) 11 | (libraries tjr_pcache tjr_kv lwt.unix tjr_profile)) 12 | 13 | 14 | ; (flags -noassert) 15 | -------------------------------------------------------------------------------- /bin/test.ml: -------------------------------------------------------------------------------- 1 | (* FIXME resurrect 2 | 3 | (** Test the KV store with an LRU frontend *) 4 | open Kv_intf 5 | open Shared_ctxt 6 | open Tjr_monad.With_lwt 7 | 8 | let file_ops = lwt_file_ops 9 | 10 | open Tjr_kv 11 | (* open Lwt_aux *) 12 | 13 | module KVX = Kv_store_with_lru.Int_int_ex 14 | 15 | let i2k i = i 16 | let i2v i = i 17 | 18 | let Kv_config_runtime.{ 19 | tst_thrd_dly_its=dly_its; tst_thrd_dly; tst_thrd_yld_its; 20 | filename=fn; _ } 21 | = Lazy.force Kv_config_runtime.config 22 | 23 | (** Test thread runs a loop, inserting (i,2*i) at step i; q_lru_pc is 24 | used only to control the rate of inserts *) 25 | let test_thread ~(q_lru_pc:(_,_,_)q_lru_pc) ~lru_ops = 26 | let q_len = q_lru_pc#len in 27 | let maybe_yield n = 28 | n mod tst_thrd_yld_its = 0 |> function 29 | | true -> from_lwt(Tjr_monad.With_lwt.yield ()) 30 | | false -> return () 31 | in 32 | let maybe_sleep n = 33 | (* we allow the q_lru_pc to grow to this size without sleeping *) 34 | let cut_off = 500 in 35 | begin 36 | (n mod dly_its = 0 && q_len () > cut_off) |> function 37 | | true -> 38 | (* this results in somewhat jerky sleeping *) 39 | (* let len_sq = let x = q_len () in x*x in *) 40 | let len = q_len () - cut_off in 41 | let delta = tst_thrd_dly *. float_of_int len in 42 | sleep delta |> from_lwt 43 | | false -> return () 44 | end 45 | in 46 | let rec loop n = 47 | maybe_yield n >>= fun () -> 48 | maybe_sleep n >>= fun () -> 49 | let _ : unit = if n mod 10000 = 0 then Printf.printf "Inserting %#d\n%!" n in 50 | let mode = Persist_later in (* FIXME *) 51 | lru_ops.mt_insert mode (i2k n) (i2k(2*n)) >>= fun () -> 52 | loop (n+1) 53 | in 54 | object 55 | method start_test_thread () : (unit,t)m = loop 0 56 | end 57 | 58 | (** Start pcache, bt and test thread; wait 2s; then print some stats *) 59 | let example = 60 | lwt_file_ops.open_ ~fn ~create:true ~init:true >>= fun fd -> 61 | let blk_dev_ops = (blk_devs#with_ba_buf#from_fd fd)#blk_dev_ops in 62 | KVX.make ~blk_dev_ops ~init0:`Empty >>= fun kv_store -> 63 | let main_thread () = Lwt.( 64 | Lwt_unix.sleep 2.0 >>= fun () -> 65 | Printf.printf "Main thread terminating\n\n%!"; 66 | Printf.printf "Queue sizes: q_lru_pc:%d; q_pc_bt:%d (%s)\n%!" 67 | (kv_store#q_lru_pc#len ()) 68 | (kv_store#q_pc_bt#len ()) 69 | __FILE__ ; 70 | let roots = kv_store#rt_blk in 71 | Printf.printf "B-tree root: %d (%s)\n%!" (roots.bt_rt |> B.to_int) __FILE__; 72 | Printf.printf "min_free blk_id: %d (%s)\n%!" 73 | (kv_store#min_free.min_free_blk_id |> B.to_int) __FILE__; 74 | return ()) 75 | in 76 | (* all threads *) 77 | Lwt.choose [ 78 | to_lwt @@ kv_store#pcache_thread#start_pcache_thread(); 79 | to_lwt @@ kv_store#btree_thread#start_btree_thread(); 80 | to_lwt @@ (test_thread ~q_lru_pc:kv_store#q_lru_pc ~lru_ops:kv_store#lru_ops) 81 | #start_test_thread(); 82 | main_thread () 83 | ] 84 | |> from_lwt 85 | 86 | let _ = Lwt_main.run (to_lwt example) 87 | *) 88 | -------------------------------------------------------------------------------- /bin/tjr_kv_test.ml: -------------------------------------------------------------------------------- 1 | (* FIXME remove the other With_lwt module that is clashing with Tjr_monad.With_lwt *) 2 | 3 | let _ = 4 | let module Test = Kv_store_with_lru.Test() in 5 | Lwt_main.run (Tjr_monad.With_lwt.to_lwt @@ Test.test()) 6 | 7 | (* 8 | module Requires = struct 9 | module Bt_blk_id = Tjr_int.Make_type_isomorphic_to_int() 10 | module Pc_blk_id = Tjr_int.Make_type_isomorphic_to_int() 11 | end 12 | 13 | 14 | module Ukv = Tjr_kv.Synchronous_store.Make(Requires) 15 | 16 | module Test = Ss_test.Test() 17 | 18 | let main () = Test.run_tests ~depth:4 19 | 20 | let _ = main() 21 | *) 22 | -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (env 2 | (dev (flags (:standard -w -27-32-34-39)))) -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.0) 2 | (name tjr_kv) 3 | 4 | ;; for opam 5 | (authors "Tom Ridge ") 6 | (maintainers "Tom Ridge ") 7 | (source (github tomjridge/tjr_kv)) 8 | (homepage https://github.com/tomjridge/tjr_kv) 9 | (documentation https://tomjridge.github.io/ocamldocs/) 10 | (generate_opam_files true) 11 | 12 | 13 | (package 14 | (name tjr_kv) 15 | (synopsis "A Key-Value store for OCaml (part of ImpFS)") 16 | (depends 17 | tjr_btree 18 | tjr_lru_cache 19 | tjr_mem_queue 20 | tjr_pcache 21 | )) 22 | 23 | -------------------------------------------------------------------------------- /misc/kv_store_diagram.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | LRU 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Pcache 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Block device 51 | (uncached) 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | B-tree 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Block device 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | Either B-tree block device should be uncached 89 | or syncing should not interfere with Pcache block device. 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | User API 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | The pcache and following components 114 | are the uncached kv store. 115 | 116 | The kv store includes the frontend LRU cache. 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | async; in another thread 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | sync_key; sync_all 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /misc/kv_store_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomjridge/tjr_kv/43c4c7ffcfcf0d5c825cd8ba74e12688f7966d4a/misc/kv_store_diagram.png -------------------------------------------------------------------------------- /misc/tjr_kv_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_thread_yield_iterations" : 50, 3 | "test_thread_delay_iterations" : 100, 4 | "test_thread_delay" : 1e-06, 5 | "lru_max_size" : 256, 6 | "lru_evict_count" : 128, 7 | "dmap_filename" : "dmap.store", 8 | "dmap_ops_per_block" : 200, 9 | "dmap_blocks_limit" : 10, 10 | "dmap_thread_delay" : 1e-06, 11 | "bt_filename" : "btree.store", 12 | "bt_thread_delay" : 1e-06, 13 | "root_man_filename" : "root_man.store" 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/_archive/dummy_dmap_layer.ml: -------------------------------------------------------------------------------- 1 | (** {2 Archived dummy Pcache and pcache_thread; do not use } *) 2 | 3 | (* 4 | open Tjr_monad.With_lwt 5 | open Kv_intf 6 | open Lwt_aux (* provides various msg queues *) 7 | 8 | open Config 9 | open Kv_profilers 10 | 11 | module S = struct 12 | type k = string 13 | let compare: k -> k -> int = Pervasives.compare 14 | type v = string 15 | end 16 | open S 17 | 18 | module Queues = Lwt_aux.Make_queues(S) 19 | open Queues 20 | 21 | (* NOTE queues are mutable *) 22 | let q_lru_pc_state = q_lru_pc.initial_state 23 | let q_pcache_bt_state = q_pcache_bt.initial_state 24 | 25 | (** {2 Simple freespace impl using an incrementing int ref} *) 26 | 27 | module Alloc = struct 28 | let fv = 29 | let x = ref 0 in 30 | fun () -> (x:=!x+1; !x) 31 | end 32 | open Alloc 33 | 34 | module Pcache' : sig 35 | val pcache_thread : 36 | yield:(unit -> unit Lwt.t) -> 37 | sleep:(float -> unit Lwt.t) -> unit -> ('a, lwt) m 38 | end = struct 39 | 40 | open Pcache_profiler 41 | let [d2b_aa ;d2b_ab ;d2b_ca ;d2b_cb ;pcache_l2d_deq1 ;pcache_l2d_deq2 ;pcache_es] = 42 | ["d2b:aa" ;"d2b:ab" ;"d2b:ca" ;"d2b:cb" ;"pcache:l2d.deq1" ;"pcache:l2d.deq2" ;"pcache_es"] 43 | |> List.map allocate_int 44 | [@@warning "-8"] 45 | 46 | open Pcache_types 47 | 48 | (** Now we fill in the missing components: [pcache_ops, 49 | pcache_blocks_limit, bt_find, bt_detach]. 50 | 51 | For the time being, we would like to use a dummy implementation 52 | of pcache_ops *) 53 | 54 | 55 | let pcache_state : (k,v,'ptr)Pcache_dummy_implementation.Dummy_state.t ref = 56 | Pcache_dummy_implementation.Dummy_state.init_dummy_state ~init_ptr:0 57 | |> ref 58 | 59 | let _ = pcache_state 60 | 61 | let with_state f = 62 | f 63 | ~state:(!pcache_state) 64 | ~set_state:(fun x -> pcache_state:=x; return ()) 65 | 66 | (** NOTE the following enqueues a find event on the msg queue, and 67 | constructs a promise that waits for the result *) 68 | let bt_find = fun k -> 69 | event_ops.ev_create () >>= fun ev -> 70 | let callback = fun v -> event_ops.ev_signal ev v in 71 | mark d2b_aa; 72 | q_pcache_bt.ops.memq_enqueue 73 | ~q:q_pcache_bt_state 74 | ~msg:Msg_pcache_bt.(Find(k,callback)) >>= fun () -> 75 | mark d2b_ab; 76 | event_ops.ev_wait ev 77 | 78 | let bt_handle_detach (detach_info:('k,'v,'ptr)detach_info) = 79 | (* Printf.printf "bt_handle_detach start\n%!"; *) 80 | let kv_op_map = Tjr_pcache.Op_aux.default_kvop_map_ops () in 81 | let kv_ops = detach_info.past_map |> kv_op_map.bindings |> List.map snd in 82 | mark d2b_ca; 83 | q_pcache_bt.ops.memq_enqueue 84 | ~q:q_pcache_bt_state 85 | ~msg:Msg_pcache_bt.(Detach { 86 | ops=kv_ops; 87 | new_pcache_root=detach_info.current_ptr}) >>= fun _ -> 88 | (* Printf.printf "bt_handle_detach end\n%!"; *) 89 | mark d2b_cb; 90 | return () 91 | 92 | let pcache_ops = 93 | (* FIXME we really want an implementation that uses polymap, so we can convert to a map *) 94 | let raw_pcache_ops (* pcache_ops *) : ('op,'map,'ptr,'t)pcache_ops = 95 | Pcache_dummy_implementation.make_pcache_ops 96 | ~monad_ops 97 | ~ops_per_block:pcache_ops_per_block 98 | ~alloc_ptr:(fun () -> return (fv())) 99 | ~with_state:{with_state} 100 | in 101 | let _ : (k,v,'ptr,'t) pcache_ops = raw_pcache_ops in 102 | Pcache_with_blocks_limit.make_ops 103 | ~monad_ops 104 | ~pcache_ops:raw_pcache_ops 105 | ~pcache_blocks_limit 106 | ~bt_find 107 | ~bt_handle_detach 108 | 109 | let pcache_thread ~yield ~sleep () = 110 | let loop_evictees = 111 | let rec loop es = 112 | from_lwt(yield ()) >>= fun () -> 113 | (* Printf.printf "pcache_thread, loop_evictees\n%!"; *) 114 | match es with 115 | | [] -> return () 116 | | (k,e)::es -> 117 | let open Im_intf in 118 | (* let open Mt_intf in *) 119 | match e with 120 | | Insert { value=v; _ } -> 121 | pcache_ops.insert k v >>= fun () -> 122 | loop es 123 | | Delete _ -> 124 | pcache_ops.delete k >>= fun () -> 125 | loop es 126 | | Lower _ -> 127 | Printf.printf "WARNING!!! unexpected evictee: Lower\n%!"; 128 | assert(false) (* should never happen FIXME? *) 129 | (* loop es *) 130 | (* FIXME perhaps define a restricted type *) 131 | in 132 | loop 133 | in 134 | let rec read_and_dispatch () = 135 | (* FIXME do we need to yield if we are simply dequeueing? *) 136 | (* FIXME why is yield coerced to from_lwt? should be monad-agnostic *) 137 | from_lwt(yield ()) >>= fun () -> 138 | (* Printf.printf "pcache_thread read_and_dispatch starts\n%!"; *) 139 | mark pcache_l2d_deq1; 140 | q_lru_pc.ops.memq_dequeue q_lru_pc_state >>= fun msg -> 141 | mark pcache_l2d_deq2; 142 | (* Printf.printf "pcache_thread dequeued: %s\n%!" (Lru'.msg2string msg); *) 143 | (* FIXME the following pause seems to require that the btree 144 | thread makes progress, but of course it cannot since there 145 | are no msgs on the queue *) 146 | from_lwt(sleep pcache_thread_delay) >>= fun () -> (* FIXME *) 147 | match msg with 148 | | Insert (k,v,callback) -> 149 | pcache_ops.insert k v >>= fun () -> 150 | async (fun () -> callback ()) >>= fun () -> 151 | read_and_dispatch () 152 | | Delete (k,callback) -> 153 | pcache_ops.delete k >>= fun () -> 154 | async (fun () -> callback ()) >>= fun () -> 155 | read_and_dispatch () 156 | | Find (k,callback) -> 157 | pcache_ops.find k >>= fun v -> 158 | async (fun () -> callback v) >>= fun () -> 159 | read_and_dispatch () 160 | | Evictees es -> 161 | mark pcache_es; 162 | loop_evictees es >>= fun () -> 163 | read_and_dispatch () 164 | in 165 | read_and_dispatch () 166 | 167 | end 168 | *) 169 | -------------------------------------------------------------------------------- /src/_old/dummy_btree_implementation.ml: -------------------------------------------------------------------------------- 1 | (** An in-mem B-tree with sync, for testing. *) 2 | 3 | open Kv_intf 4 | open Btree_ops 5 | 6 | module Ptr = Int_.Make_type_isomorphic_to_int() 7 | 8 | module Types = struct 9 | 10 | type bt_ptr = Ptr.t 11 | 12 | type ('k,'v) bt_map = ('k,'v,unit) Tjr_map.map 13 | 14 | type ('k,'v) dummy_bt_state = { 15 | ptr: bt_ptr; (* "current" pointer; revealed on next sync *) 16 | map: ('k,'v) bt_map; 17 | synced_states: (bt_ptr * ('k,'v) bt_map) list 18 | } 19 | 20 | end 21 | include Types 22 | 23 | 24 | let empty_btree () : ('k,'v) dummy_bt_state = 25 | (* FIXME pervasives.compare *) 26 | let map = (Tjr_map.make_map_ops Pervasives.compare).empty in 27 | let ptr = Ptr.int2t 0 in 28 | let synced_states = [] in 29 | { ptr; map; synced_states } 30 | 31 | (* NOTE this assumes that ptr is greatest, which it should be given 32 | the implementation *) 33 | let new_ptr state = state.ptr |> Ptr.t2int |> fun x -> x+1 |> Ptr.int2t 34 | 35 | 36 | (** Simple implementation of the B-tree ops, for testing *) 37 | let make_dummy_btree_ops 38 | ~monad_ops ~with_state 39 | : ('k,'v,'blk_id,'t) btree_ops 40 | = 41 | let ( >>= ) = monad_ops.bind in 42 | let return = monad_ops.return in 43 | let with_btree = with_state.with_state in 44 | let count = ref 0 in 45 | (* FIXME pervasives.compare *) 46 | let map_ops = Tjr_map.make_map_ops Pervasives.compare in 47 | let find k = with_btree (fun ~state ~set_state -> 48 | map_ops.find_opt k state.map |> return) 49 | in 50 | let insert k v = with_btree (fun ~state ~set_state -> 51 | count:=!count+1; 52 | (if !count mod (int_of_float 1e5) = 0 then Printf.printf "B-tree, %d inserts\n%!" !count else ()); 53 | map_ops.add k v state.map |> fun map -> 54 | set_state {state with map}) 55 | in 56 | let delete k = with_btree (fun ~state ~set_state -> 57 | map_ops.remove k state.map |> fun map -> 58 | set_state {state with map}) 59 | in 60 | let sync () = with_btree (fun ~state ~set_state -> 61 | let synced_states = (state.ptr,state.map)::state.synced_states in 62 | let ptr = new_ptr state in 63 | set_state {state with synced_states;ptr } >>= fun () -> 64 | return ptr) 65 | in 66 | {find;insert;delete;sync} 67 | 68 | let _ = make_dummy_btree_ops 69 | -------------------------------------------------------------------------------- /src/_old/internal_staging.ml: -------------------------------------------------------------------------------- 1 | (* 2 | (** INTERNAL PLEASE IGNORE This is another attempt to isolate the 3 | various stages of type construction. *) 4 | 5 | open Kv_intf 6 | 7 | module type M = sig 8 | type t 9 | val monad_ops: t monad_ops 10 | end 11 | 12 | 13 | module type B1 = sig 14 | type blk_id 15 | type blk 16 | val blk_ops: blk blk_ops 17 | end 18 | 19 | module type B2 = sig 20 | include M 21 | include B1 22 | (** NOTE typically blk_dev_ops requires eg a file_descr *) 23 | val blk_dev_ops: (blk_id,blk,t)blk_dev_ops 24 | val blk_layer: (blk,(blk_id,blk,t)blk_dev_ops)blk_layer 25 | end 26 | 27 | 28 | module type K1 = sig 29 | type k 30 | val compare_k: k -> k -> int 31 | type v 32 | end 33 | 34 | module type KB1 = sig 35 | include B1 36 | include K1 37 | end 38 | 39 | module type KB2 = sig 40 | include KB1 41 | (* B-tree marshal *) 42 | end 43 | 44 | module type KB3 = sig 45 | (* pcache marshal *) 46 | end 47 | 48 | 49 | (** argument to B-tree make *) 50 | module type BT1 = sig 51 | include M 52 | include K1 53 | include B1 (* only need blk_id *) 54 | type r = blk_id 55 | end 56 | 57 | module I = Isa_btree.Isa_btree_intf 58 | module B = Tjr_btree.Btree_intf 59 | 60 | (** result of B-tree make; NOTE that B.disk_ops contains the dnode 61 | marshalling code, and that this is an argument to most of the 62 | constructor functions in Tjr_btree.Make *) 63 | module type BT2 = sig 64 | include BT1 65 | type leaf 66 | type node 67 | type leaf_stream 68 | type dnode = (node,leaf)I.dnode 69 | val leaf_ops: (k,v,leaf)I.leaf_ops 70 | val node_ops: (k,r,node)I.node_ops 71 | type disk_ops = (r,t,dnode,blk) B.disk_ops 72 | type store_ops = (r,dnode,t) I.store_ops 73 | type pre_btree_ops = (k,v,r,t,leaf,node,leaf_stream) I.pre_btree_ops 74 | val make_something: disk_ops -> unit 75 | end 76 | 77 | 78 | 79 | (** {2 Older version} *) 80 | 81 | 82 | (** These types are common to almost all the components (well, perhaps 83 | the root manager deals with blks rather than kvrt) *) 84 | module type KVRT = sig 85 | type k 86 | type v 87 | type r 88 | type blk_id = r 89 | 90 | type t 91 | 92 | (** Once the [t] monad type is fixed, the mutex and cvar types are usually also fixed *) 93 | type mutex 94 | type cvar 95 | 96 | (** The type of the monadic yield *) 97 | type yield_t 98 | 99 | (** The type of the monadic sleep *) 100 | type sleep_t 101 | end 102 | 103 | (** The following module is for system architecture documentation purposes *) 104 | module Ctxt(Pre:KVRT) = struct 105 | open Memq_intf 106 | include Pre 107 | 108 | module type LRU_DMAP = sig 109 | type msg1 = (k,v,t) Msg_lru_pc.lru_pc_msg 110 | type q1 = (mutex,cvar,msg1) queue 111 | val q_lru_pc_ops : (msg1,q1,t) memq_ops 112 | end 113 | 114 | (* NOTE leaves in the ctxt tree can be mod types rather than mods *) 115 | (** The main types and values in scope at the Lru layer *) 116 | module type LRU = sig 117 | open Syncable_map 118 | type lru_ops = (k,v,r,t) syncable_map_with_pmode 119 | val lru_ops: lru_ops 120 | (* NOTE no lru thread; lru_callback_ops are used instead *) 121 | end 122 | 123 | 124 | module type DMAP_BTREE = sig 125 | type msg2 = (k,v,blk_id,t) Msg_pc_bt.pc_bt_msg 126 | type q2 = (mutex,cvar,msg2) queue 127 | val q_dmag_bt_ops : (msg2,q2,t) memq_ops 128 | end 129 | 130 | (** The main types and values in scope at the detachable map *) 131 | module type DMAP = sig 132 | (** NOTE this thread "never" terminates; it takes msgs from the 133 | lru->pcache queue, and applies them to the log; occasionally the 134 | log is detached and the result is put on the pcache->btree queue 135 | *) 136 | val pcache_thread: yield:yield_t -> sleep:sleep_t -> unit -> ('a,t)m 137 | end 138 | 139 | module type BTREE_ROOTMAN = sig 140 | open Msg_btree_rootman 141 | type msg3 = blk_id msg_btree_rootman 142 | end 143 | 144 | module type BTREE = sig 145 | (** This thread takes items of pcache->btree, and executes against the btree *) 146 | val btree_thread: yield:yield_t -> sleep:sleep_t -> unit -> ('a,t)m 147 | end 148 | 149 | module type ROOTMAN = sig 150 | val rootman_thread: yield:yield_t -> sleep:sleep_t -> unit -> ('a,t)m 151 | end 152 | end 153 | 154 | 155 | (** Documentation of the various state types of the components *) 156 | module States(Pre:KVRT) = struct 157 | include Pre 158 | 159 | module type LRU = sig 160 | type k_map 161 | type t_map 162 | type nonrec lru_state = (k,v,k_map,t_map,t) Mt_state_type.mt_state 163 | end 164 | 165 | module type DMAP = sig 166 | open Pcache_types 167 | open Dcl_types 168 | 169 | (** NOTE typically the internal state is the same for pl and pcl, 170 | and is a buffer and an int *) 171 | type pl_and_pcl_internal_state 172 | 173 | type kvop_map 174 | 175 | (** dcl_state is start_block, current_block etc *) 176 | type nonrec dcl_state = (r,kvop_map) dcl_state 177 | type nonrec detach_info = (k,v,r) detach_info 178 | type nonrec pcache_ops = (k,v,r,t) pcache_ops 179 | 180 | type pcache_state = pl_and_pcl_internal_state * dcl_state 181 | end 182 | 183 | 184 | module type BTREE = sig 185 | (** These fixed types are placeholders *) 186 | type t1 187 | type t2 188 | 189 | (** NOTE: our examples use a file descriptor as a reference to the "block device" *) 190 | type fd 191 | 192 | (** This is the state held in memory while the B-tree executes *) 193 | type btree_state = { 194 | blk_allocator_state:t1; 195 | fstore_state:t2; 196 | btree_root_blk:blk_id; 197 | blk_dev:fd 198 | } 199 | end 200 | 201 | 202 | module type ROOTMAN = sig 203 | type fd 204 | (** This is the state held in memory and presumably mirrored 205 | somewhere on disk (at least, for the roots) eg in block 0 *) 206 | type rootman_state = { 207 | blk_dev:fd; 208 | pcache_root:blk_id; 209 | btree_root:blk_id; 210 | } 211 | end 212 | 213 | (** NOTE The system state is composed of the above, plus the 214 | contents of the queues, plus the contents of the caches (eg the 215 | store cache) *) 216 | end 217 | 218 | 219 | 220 | 221 | *) 222 | -------------------------------------------------------------------------------- /src/_old/internal_staging.ml.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | internal_staging.ml 7 | 8 | 35 | 36 | 131 | 132 | 133 | 134 |
(** INTERNAL PLEASE IGNORE This is another attempt to isolate the
135 |    various stages of type construction. *)
136 | 
137 | open Kv_intf
138 | 
139 | module type M = sig
140 |   type t
141 |   val monad_ops: t monad_ops
142 | end
143 | 
144 | 
145 | module type B1 = sig
146 |   type blk_id
147 |   type blk
148 |   val blk_ops: blk blk_ops
149 | end
150 | 
151 | module type B2 = sig
152 |   include M
153 |   include B1
154 |   (** NOTE typically blk_dev_ops requires eg a file_descr *)
155 |   val blk_dev_ops: (blk_id,blk,t)blk_dev_ops
156 |   val blk_layer: (blk,(blk_id,blk,t)blk_dev_ops)blk_layer
157 | end
158 | 
159 | 
160 | module type K1 = sig
161 |   type k
162 |   val compare_k: k -> k -> int
163 |   type v
164 | end
165 | 
166 | module type KB1 = sig
167 |   include B1
168 |   include K1
169 | end
170 | 
171 | module type KB2 = sig
172 |   include KB1
173 |   (* B-tree marshal *)
174 | end
175 | 
176 | module type KB3 = sig
177 |   (* pcache marshal *)
178 | end
179 | 
180 | 
181 | (** argument to B-tree make *)
182 | module type BT1 = sig
183 |   include M
184 |   include K1
185 |   include B1 (* only need blk_id *)
186 |   type r = blk_id
187 | end
188 | 
189 | module I = Isa_btree.Isa_btree_intf
190 | module B = Tjr_btree.Btree_intf
191 | 
192 | (** result of B-tree make; NOTE that B.disk_ops contains the dnode
193 |    marshalling code, and that this is an argument to most of the
194 |    constructor functions in Tjr_btree.Make *)
195 | module type BT2 = sig
196 |   include BT1
197 |   type leaf
198 |   type node
199 |   type leaf_stream
200 |   type dnode = (node,leaf)I.dnode
201 |   val leaf_ops: (k,v,leaf)I.leaf_ops
202 |   val node_ops: (k,r,node)I.node_ops
203 |   type disk_ops = (r,t,dnode,blk) B.disk_ops
204 |   type store_ops = (r,dnode,t) I.store_ops
205 |   type pre_btree_ops = (k,v,r,t,leaf,node,leaf_stream) I.pre_btree_ops
206 |   val make_something: disk_ops -> unit
207 | end
208 | 
209 | 
210 | 
211 | (** {2 Older version} *)
212 | 
213 | 
214 | (** These types are common to almost all the components (well, perhaps
215 |    the root manager deals with blks rather than kvrt) *)
216 | module type KVRT = sig
217 |   type k
218 |   type v
219 |   type r
220 |   type blk_id = r
221 | 
222 |   type t
223 | 
224 |   (** Once the [t] monad type is fixed, the mutex and cvar types are usually also fixed *)
225 |   type mutex
226 |   type cvar 
227 | 
228 |   (** The type of the monadic yield *)
229 |   type yield_t
230 |     
231 |   (** The type of the monadic sleep *)
232 |   type sleep_t
233 | end  
234 | 
235 | (** The following module is for system architecture documentation purposes *)
236 | module Ctxt(Pre:KVRT) = struct
237 |   open Memq_intf
238 |   include Pre
239 | 
240 |   module type LRU_DMAP = sig
241 |     type msg1 = (k,v,t) Msg_lru_dmap.lru_dmap_msg
242 |     type q1 = (mutex,cvar,msg1) queue
243 |     val q_lru_dmap_ops : (msg1,q1,t) memq_ops
244 |   end
245 |   
246 |   (* NOTE leaves in the ctxt tree can be mod types rather than mods *)
247 |   (** The main types and values in scope at the Lru layer *)
248 |   module type LRU = sig
249 |     open Syncable_map
250 |     type lru_ops = (k,v,r,t) syncable_map_with_pmode
251 |     val lru_ops: lru_ops
252 |     (* NOTE no lru thread; lru_callback_ops are used instead *) 
253 |   end
254 | 
255 | 
256 |   module type DMAP_BTREE = sig
257 |     type msg2 = (k,v,blk_id,t) Msg_dmap_bt.dmap_bt_msg
258 |     type q2 = (mutex,cvar,msg2) queue
259 |     val q_dmag_bt_ops : (msg2,q2,t) memq_ops
260 |   end
261 | 
262 |   (** The main types and values in scope at the detachable map *)
263 |   module type DMAP = sig   
264 |     (** NOTE this thread "never" terminates; it takes msgs from the
265 |         lru->dmap queue, and applies them to the log; occasionally the
266 |         log is detached and the result is put on the dmap->btree queue
267 |     *)
268 |     val dmap_thread: yield:yield_t -> sleep:sleep_t -> unit -> ('a,t)m
269 |   end
270 | 
271 |   module type BTREE_ROOTMAN = sig
272 |     open Msg_btree_rootman
273 |     type msg3 = blk_id msg_btree_rootman
274 |   end
275 | 
276 |   module type BTREE = sig
277 |     (** This thread takes items of dmap->btree, and executes against the btree *)
278 |     val btree_thread: yield:yield_t -> sleep:sleep_t -> unit -> ('a,t)m        
279 |   end
280 | 
281 |   module type ROOTMAN = sig
282 |     val rootman_thread: yield:yield_t -> sleep:sleep_t -> unit -> ('a,t)m
283 |   end
284 | end
285 | 
286 | 
287 | (** Documentation of the various state types of the components *)
288 | module States(Pre:KVRT) = struct
289 |   include Pre
290 | 
291 |   module type LRU = sig
292 |     type k_map
293 |     type t_map
294 |     type nonrec lru_state = (k,v,k_map,t_map,t) Mt_state_type.mt_state
295 |   end
296 | 
297 |   module With_blk_id(Blk_id:sig type blk_id = r end) = struct
298 |     open Blk_id
299 | 
300 |     module type DMAP = sig
301 |       open Dmap_types
302 |       open Dcl_types
303 | 
304 |       (** NOTE typically the internal state is the same for pl and pcl,
305 |           and is a buffer and an int *)
306 |       type pl_and_pcl_internal_state 
307 | 
308 |       type kvop_map
309 | 
310 |       (** dcl_state is start_block, current_block etc *)
311 |       type nonrec dcl_state = (r,kvop_map) dcl_state
312 |       type nonrec detach_info = (k,v,r) detach_info
313 |       type nonrec dmap_ops = (k,v,r,t) dmap_ops
314 | 
315 |       type dmap_state = pl_and_pcl_internal_state * dcl_state
316 |     end
317 | 
318 | 
319 |     module type BTREE = sig
320 |       (** These fixed types are placeholders *)
321 |       type t1
322 |       type t2
323 | 
324 |       (** NOTE: our examples use a file descriptor as a reference to the "block device" *)
325 |       type fd
326 | 
327 |       (** This is the state held in memory while the B-tree executes *)
328 |       type btree_state = {
329 |         blk_allocator_state:t1;
330 |         fstore_state:t2;
331 |         btree_root_blk:blk_id;
332 |         blk_dev:fd
333 |       }
334 |     end
335 | 
336 | 
337 |     module type ROOTMAN = sig
338 |       type fd
339 |       (** This is the state held in memory and presumably mirrored
340 |           somewhere on disk (at least, for the roots) eg in block 0 *)
341 |       type rootman_state = {
342 |         blk_dev:fd;
343 |         dmap_root:blk_id;
344 |         btree_root:blk_id;
345 |       }        
346 |     end
347 | 
348 |     (** NOTE The system state is composed of the above, plus the
349 |        contents of the queues, plus the contents of the caches (eg the
350 |        store cache) *)
351 |   end
352 | end
353 | 
354 | 
355 | 
356 | 357 | 358 | 359 | -------------------------------------------------------------------------------- /src/_old/kv_intf_v2.ml_: -------------------------------------------------------------------------------- 1 | (** Interfaces based on classes *) 2 | 3 | open Kv_intf 4 | open Std_types 5 | 6 | 7 | 8 | (** {2 Pcache} *) 9 | 10 | -------------------------------------------------------------------------------- /src/_old/kv_intf_v3.ml_: -------------------------------------------------------------------------------- 1 | (** Interfaces based on a single "store" object with mutable refs. 2 | 3 | 4 | NOTE the class types are not rendered well with odoc. 5 | 6 | {[ 7 | ]} 8 | 9 | *) 10 | 11 | [@@@ warning "-33"] 12 | open Tjr_monad.With_lwt 13 | open Std_types 14 | 15 | module Util = struct 16 | class virtual ['a] mrshlr = object 17 | method virtual to_blk: 'a -> blk 18 | method virtual of_blk: blk -> 'a 19 | end 20 | 21 | class virtual ['a] set_once = object 22 | method virtual set: 'a -> unit 23 | method virtual get: unit -> 'a 24 | method virtual is_set: bool 25 | end 26 | 27 | class virtual ['a] mutable_ = object 28 | method virtual set: 'a -> unit 29 | method virtual get: unit -> 'a 30 | method virtual is_set: bool 31 | end 32 | 33 | 34 | (* 35 | (** This is a container for a type 'a that can be stored in a single 36 | block. The initialized method checks that all the state parts of 37 | a component are initialized; use with assert *) 38 | class virtual ['a] on_disk_block = object 39 | (* val blk_dev_ops : (blk_id,blk,t)blk_dev_ops *) 40 | val virtual blk_id : blk_id 41 | val virtual contents : 'a mutable_ 42 | val virtual marshaller : 'a mrshlr 43 | method virtual sync_to_disk : unit -> (unit,t)m 44 | method virtual sync_from_disk : unit -> (unit,t)m 45 | (* method virtual is_initialized : bool *) 46 | end 47 | *) 48 | 49 | end 50 | open Util 51 | 52 | module Pvt_class_types = struct 53 | 54 | class virtual ['fl] freelist = object 55 | (* inherit ['fl] on_disk_block *) 56 | method virtual blk_allocator: (r,t)blk_allocator_ops 57 | method virtual sync_to_disk: unit -> (unit,t)m 58 | end 59 | 60 | class virtual ['fl] freelist_factory = object 61 | method virtual make_from_disk: 62 | blk_dev_ops:(blk_id,blk,t)blk_dev_ops -> 63 | blk_id:blk_id -> 64 | 'fl freelist 65 | end 66 | 67 | type t1 = { 68 | mutable bt_rt:blk_id; 69 | mutable pc_hd:blk_id; 70 | mutable pc_tl:blk_id 71 | }[@@deriving bin_io] 72 | 73 | class virtual root_man = object 74 | val virtual roots: t1 75 | method virtual sync_to_disk: unit -> (unit,t)m 76 | end 77 | 78 | class virtual btree_cache = object 79 | (* method virtual blk_dev_ops : (blk_id,blk,t)blk_dev_ops set_once *) 80 | 81 | (** Flush the write back cache, after merging a prefix of the pcache *) 82 | method virtual flush_cache : unit -> (unit,t)m 83 | end 84 | 85 | (* we expect that, once setup, the threads run forever; other 86 | applications may need to shutdown threads instead. *) 87 | class virtual ['k,'v] btree = object 88 | val mutable virtual blk_id : blk_id (* root of the btree *) 89 | method virtual thread : < start_thread: unit -> (unit,t)m > 90 | method virtual btree_ops : unit -> ('k,'v,blk_id,t) Kv_intf.Btree_ops.btree_ops 91 | (* method virtual blk_dev_ops : (blk_id,blk,t)blk_dev_ops set_once *) 92 | (* method virtual get_cache : btree_cache *) 93 | (* method virtual is_initialized : bool *) 94 | end 95 | (** The B-tree needs to track the root blk_id only; to create a 96 | B-tree we need to write an empty leaf to disk *) 97 | 98 | class virtual ['k,'v] btree_factory = object 99 | method virtual read_from_disk: blk_id -> ('k,'v)btree 100 | method virtual make_empty: blk_id -> ('k,'v)btree 101 | end 102 | 103 | class virtual ['kvop_map] pcache = object 104 | val mutable virtual pcache_state : (r,'kvop_map) Tjr_pcache.Pcache_state.pcache_state 105 | method virtual thread : < start_thread: unit -> (unit,t)m > 106 | method virtual sync_to_disk : unit -> (unit,t)m 107 | method virtual sync_from_disk : hd:blk_id -> tl:blk_id -> (unit,t)m 108 | (* method is_initialized : bool *) 109 | (* method blk_dev_ops : (blk_id,blk,t)blk_dev_ops set_once *) 110 | end 111 | 112 | class virtual ['kvop_map] pcache_factory = object 113 | 114 | end 115 | 116 | 117 | class virtual ['k,'v,'mt_state] lru = object 118 | val mutable virtual lru_state: 'mt_state 119 | method virtual lru_ops: unit -> ('k,'v,t)mt_ops 120 | end 121 | 122 | (** A collection of all the components *) 123 | class virtual ['k,'v,'fl,'kvop_map,'mt_state] kv_store = object 124 | method virtual blk_dev_ops : (blk_id,blk,t)blk_dev_ops set_once 125 | method virtual root_man : root_man 126 | method virtual freelist : 'fl freelist 127 | method virtual btree_cache : btree_cache 128 | method virtual btree : ('k,'v) btree 129 | method virtual q_pc_bt : ('k,'v) Kv_intf_v2.q_pc_bt 130 | method virtual pcache : 'kvop_map pcache 131 | method virtual q_lru_pc : ('k,'v) Kv_intf_v2.q_lru_pc 132 | method virtual lru : ('k,'v,'mt_state) lru 133 | 134 | (** Checks all components are initialized *) 135 | method virtual is_initialized : bool 136 | end 137 | 138 | end 139 | 140 | module Classes = struct 141 | 142 | module P = Pvt_class_types 143 | 144 | (** {2 Generic syncable object} *) 145 | 146 | (* Convert a type with binprot to a marshaller *) 147 | module Make_mrshlr(S:sig type tt[@@deriving bin_io] end) = struct 148 | open S 149 | 150 | let to_blk (x:tt) = 151 | let buf = buf_create () in 152 | let _ : int = bin_write_tt buf ~pos:0 x in 153 | buf 154 | 155 | let of_blk blk = 156 | bin_read_tt blk ~pos_ref:(ref 0) 157 | 158 | let mrshlr : tt P.mrshlr = object 159 | method to_blk = to_blk 160 | method of_blk = of_blk 161 | end 162 | 163 | end 164 | 165 | class ['a] mutable_ = object 166 | val mutable the_ref = (Obj.magic () : 'a) 167 | val mutable is_set : bool = false 168 | method set x = the_ref <- x 169 | method get = 170 | assert(is_set); 171 | the_ref 172 | method is_set = is_set 173 | end 174 | 175 | class ['a] set_once = object (self:'self) 176 | inherit ['a] mutable_ 177 | method! set x = 178 | assert(not(is_set)); 179 | the_ref <- x 180 | end 181 | 182 | class ['a] on_disk_block = object (self:'self) 183 | val blk_dev_ops = (new set_once:(blk_id,blk,t)blk_dev_ops set_once) 184 | val blk_id = (new set_once:blk_id set_once) 185 | val contents = (new mutable_ : 'a mutable_) 186 | val marshaller = (new set_once:'a P.mrshlr set_once) 187 | method sync_to_disk () = 188 | blk_dev_ops#get.write ~blk_id:(blk_id#get) ~blk:(marshaller#get#to_blk contents#get) 189 | method sync_from_disk () = 190 | blk_dev_ops#get.read ~blk_id:(blk_id#get) >>= fun blk -> 191 | contents#set (marshaller#get#of_blk blk); return () 192 | method is_initialized = blk_dev_ops#is_set && blk_id#is_set && contents#is_set 193 | end 194 | 195 | (* FIXME how to check that this class definition satisfies the class type definition? *) 196 | let f (x:'a on_disk_block) : 'a P.on_disk_block = x 197 | 198 | 199 | (* 200 | (** {2 Freelist} *) 201 | 202 | module Freelist = struct 203 | 204 | type x = { mutable min_free_blk_id:blk_id }[@@deriving bin_io] 205 | 206 | class freelist = object 207 | inherit [x] syncable 208 | 209 | val pvt_blk_allocator : (r,t)blk_allocator_ops set_once = new set_once 210 | 211 | method get_blk_allocator () = 212 | match pvt_blk_allocator#is_set () with 213 | | true -> pvt_blk_allocator#get 214 | | false -> 215 | rt#get |> fun rb -> 216 | let blk_alloc () = 217 | let min = rb.min_free_blk_id in 218 | rb.min_free_blk_id <- B.inc rb.min_free_blk_id; 219 | return min 220 | in 221 | let blk_free blk_id = return () in 222 | { blk_alloc; blk_free } 223 | 224 | initializer 225 | let module M = Make_mrshlr(struct type tt = x[@@deriving bin_io] end) in 226 | marshaller#set M.mrshlr 227 | end 228 | end 229 | class freelist = Freelist.freelist 230 | 231 | 232 | 233 | (** {2 Root manager, for B-tree and pcache roots} *) 234 | 235 | module Root_man = struct 236 | 237 | type x = { 238 | mutable bt_rt:blk_id; 239 | mutable pc_hd:blk_id; 240 | mutable pc_tl:blk_id 241 | }[@@deriving bin_io] 242 | 243 | class root_man = object 244 | val sync_ops : x syncable = new syncable 245 | method get_sync_ops = sync_ops 246 | 247 | initializer 248 | let module M = Make_mrshlr(struct type tt = x[@@deriving bin_io] end) in 249 | sync_ops#set_marshaller M.mrshlr 250 | end 251 | 252 | end 253 | class root_man = Root_man.root_man 254 | 255 | 256 | (** {2 B-tree} *) 257 | 258 | module Btree = struct 259 | 260 | class btree = object (self) 261 | method f = self#get_blk_dev_ops |> fun (x:int) -> x 262 | 263 | end 264 | 265 | 266 | (** {2 The kv store, as a collection of components} *) 267 | 268 | module Kv_store = struct 269 | open Kv_intf_v2 270 | class ['k,'v] kv_store = object 271 | val root_man = new root_man 272 | val btree = () 273 | val q_pc_bt : ('k,'v) q_pc_bt = failwith "" 274 | val pc = () 275 | val q_lru_pc : ('k,'v)q_lru_pc = failwith "" 276 | method set_blk_dev_ops x = 277 | root_man#get_sync_ops#set_blk_dev_ops x; 278 | (* btree#set_blk_dev_ops x; *) 279 | () 280 | 281 | end 282 | end 283 | 284 | *) 285 | 286 | end 287 | 288 | include Classes 289 | 290 | 291 | (* 292 | 293 | 294 | class type ['k, 'v, 'kvop_map, 'fl] store = object 295 | 296 | method set_blk_dev_ops: std_blk_dev_ops -> unit 297 | 298 | method set_rt_blk_id : blk_id -> unit 299 | 300 | (* This doesn't sync to disk *) 301 | method set_rt_blk: rt_blk -> unit 302 | 303 | method sync_rt_blk_from_disk: unit -> (unit,t)m 304 | 305 | method sync_rt_blk_to_disk: unit -> (unit,t)m 306 | (* method write_rt_blk: rt_blk -> (unit,t)m *) 307 | 308 | 309 | (* this also contains the blk allocator *) 310 | method set_freelist: 'fl freelist -> unit 311 | 312 | 313 | 314 | (* this starts the btree thread *) 315 | method boot_btree: unit -> (unit,t)m 316 | 317 | method get_btree: unit -> ('k,'v,r,t) Kv_intf.Btree_ops.btree_ops 318 | 319 | method get_btree_cache: unit (* FIXME *) 320 | 321 | 322 | method boot_pcache: unit -> (unit,t)m 323 | 324 | method get_pcache: unit -> (r,'kvop_map)Pcache_state.pcache_state 325 | 326 | 327 | (* method boot_lru: unit -> (unit,t)m *) 328 | 329 | method get_lru: unit -> ('k,'v,t)mt_ops 330 | (* and various kinds of marshallers? empty_leaf as blk? *) 331 | end 332 | 333 | 334 | *) 335 | -------------------------------------------------------------------------------- /src/_old/kv_store_v3.ml_: -------------------------------------------------------------------------------- 1 | (** Version using {!Kv_intf_v3} interfaces *) 2 | 3 | (* NOTE this code is mostly copied from kv_store_with_lru *) 4 | [@@@warning "-33"] 5 | 6 | open Tjr_monad.With_lwt 7 | open Lwt_aux (* provides various msg queues *) 8 | open Std_types 9 | open Kv_intf 10 | open Kv_intf_v3 11 | 12 | 13 | module type S = Kv_store_with_lru.S 14 | 15 | (* 16 | module Make(S:S) = struct 17 | open S 18 | 19 | 20 | (** {2 Message queues} *) 21 | 22 | let q_lru_pc : (k,v) Kv_intf_v2.q_lru_pc = Tjr_mem_queue.With_lwt.make_as_object () 23 | let q_pc_bt : (k,v) Kv_intf_v2.q_pc_bt = Tjr_mem_queue.With_lwt.make_as_object () 24 | 25 | 26 | (** {2 Blk_dev_ops ref} *) 27 | 28 | let blk_dev_ops_ref: (blk_id,blk,t) blk_dev_ops option ref = ref None 29 | 30 | let set_blk_dev_ops blk_dev_ops = blk_dev_ops_ref := Some blk_dev_ops 31 | 32 | let get_blk_dev_ops () = 33 | assert(Option.is_some !blk_dev_ops_ref); 34 | Option.get !blk_dev_ops_ref 35 | 36 | 37 | module Make_root_blk_ops(S:sig type rb[@@deriving bin_io] end) = struct 38 | include S 39 | 40 | let write_rb ~blk_id rb = 41 | let blk_dev_ops = get_blk_dev_ops () in 42 | (* FIXME have a single buf_create, specialized to std_type and buf_sz *) 43 | let buf = buf_create () in 44 | bin_write_rb buf ~pos:0 rb |> fun _ -> 45 | blk_dev_ops.write ~blk_id ~blk:buf 46 | 47 | let read_rb: blk_id:blk_id -> (rb,t)m = fun ~blk_id -> 48 | let blk_dev_ops = get_blk_dev_ops () in 49 | blk_dev_ops.read ~blk_id >>= fun blk -> 50 | bin_read_rb blk ~pos_ref:(ref 0) |> fun rb -> 51 | return rb 52 | end 53 | 54 | module X = Make_root_blk_ops(struct type rb = rt_blk[@@deriving bin_io] end) 55 | 56 | 57 | 58 | (** {2 The root block, where roots (apart from freelist) are stored} *) 59 | 60 | let r0 = ref None 61 | 62 | let set_rt blk_id = r0:=Some blk_id 63 | 64 | let get_rt () = 65 | assert(Option.is_some !r0); 66 | Option.get !r0 67 | 68 | 69 | let rb_ref = ref None 70 | 71 | let set_rt_blk rb = rb_ref:=Some rb 72 | 73 | let get_rb () = 74 | assert(Option.is_some !rb_ref); 75 | Option.get !rb_ref 76 | 77 | let sync_rt_blk_from_disk () = 78 | get_rt () |> fun blk_id -> 79 | X.read_rb ~blk_id >>= fun rb -> 80 | rb_ref:=Some rb; return () 81 | 82 | let sync_rt_blk_to_disk () = 83 | get_rt () |> fun blk_id -> 84 | X.write_rb ~blk_id (get_rb()) 85 | 86 | 87 | 88 | (** {2 The freelist} *) 89 | 90 | 91 | type freelist_rt_blk = { 92 | min_free:blk_id; 93 | } [@@deriving bin_io] 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | (** {2 Root manager} *) 104 | 105 | let roots : rt_blk option ref = ref None 106 | let root_man : (rt_blk,t) Root_man_ops.root_man Lazy.t = lazy ( 107 | assert(Option.is_some !roots); 108 | Root_manager.make_root_man ~monad_ops ~blk_ops ~blk_dev_ops:(Lazy.force blk_dev_ops)) 109 | 110 | 111 | (** {2 Blk allocator} *) 112 | 113 | let blk_alloc : (r,t) blk_allocator_ops Lazy.t = lazy ( 114 | let blk_alloc () = 115 | assert(Option.is_some !roots); 116 | !roots |> Option.get |> fun x -> 117 | roots:=Some({x with min_free=B.inc x.min_free}); 118 | return x.min_free 119 | in 120 | let blk_free blk_id = return () in 121 | {blk_alloc;blk_free}) 122 | 123 | 124 | 125 | (** {2 B-tree/btree ops/bt thread} *) 126 | 127 | module Btree_ = Tjr_btree_examples.Make_1.Make(struct 128 | include Std_types 129 | include S 130 | let cs = Tjr_btree_examples.Make_1.make_constants ~k_size ~v_size 131 | end) 132 | 133 | let with_bt_rt = 134 | let with_state f = 135 | assert(Option.is_some !roots); 136 | f ~state:( (!roots |> Option.get).bt_rt ) 137 | ~set_state:(fun bt_rt -> 138 | !roots |> Option.get |> fun x -> 139 | {x with bt_rt} |> fun x -> 140 | roots:=Some x; return ()) 141 | in 142 | { with_state } 143 | 144 | let btree_thread = lazy Btree_thread.( 145 | let blk_dev_ops = Lazy.force blk_dev_ops in 146 | let blk_alloc = Lazy.force blk_alloc in 147 | let x = Btree_.make ~blk_dev_ops ~blk_alloc ~root_ops:with_bt_rt in 148 | let module S = (val x) in (* FIXME this is supposed to be uncached *) 149 | make_btree_thread ~q_pc_bt ~map_ops:S.map_ops_with_ls) 150 | 151 | 152 | (** {2 Pcache and pcache_thread } *) 153 | 154 | let marshalling_config : (k,v,r) Pcache_intf.marshalling_config = 155 | (module S) 156 | 157 | module Pcache_ = Tjr_pcache.Make.Make(struct 158 | include Std_types 159 | include S 160 | let marshalling_config = marshalling_config 161 | end) 162 | 163 | (** NOTE don't set this directly; use set_pcache_state *) 164 | let pcache_state : Pcache_.pcache_state option ref = ref None 165 | 166 | let set_pcache_state = fun s -> 167 | pcache_state := Some s; 168 | assert(Option.is_some !roots); 169 | (* also update roots *) 170 | !roots|>Option.get|>fun x -> 171 | { x with pc_hd=s.root_ptr; pc_tl=s.current_ptr } |> fun x -> 172 | roots:=Some x; 173 | () 174 | 175 | let with_pcache = 176 | let with_state f = 177 | assert(Option.is_some !pcache_state); 178 | f ~state:( !pcache_state |> Option.get ) 179 | ~set_state:(fun s -> 180 | set_pcache_state s; 181 | return ()) 182 | in 183 | { with_state } 184 | 185 | let pcache_thread = lazy Pcache_.( 186 | let pcache_ops = 187 | make_pcache_ops_with_blk_dev 188 | ~blk_dev_ops:(Lazy.force blk_dev_ops) 189 | ~blk_alloc:(Lazy.force blk_alloc).blk_alloc 190 | ~with_pcache 191 | in 192 | let config = Lazy.force runtime_config in 193 | Pcache_thread.make_pcache_thread 194 | ~kvop_map_ops 195 | ~pcache_blocks_limit:config.pcache_blocks_limit 196 | ~pcache_ops 197 | ~q_lru_pc 198 | ~q_pc_bt) 199 | 200 | 201 | (** {2 LRU cache} *) 202 | 203 | module Lru_ = Tjr_lru_cache.Make.Make(struct 204 | type k = S.k 205 | let compare = S.k_cmp 206 | type v = S.v 207 | type t = lwt 208 | let monad_ops = monad_ops 209 | let async = async 210 | let event_ops = event_ops 211 | end) 212 | type mt_state = Lru_.mt_state 213 | 214 | let lru : (k,v,_) Lru.lru = 215 | let open Lru in 216 | let args : (_,_,_) args = 217 | object 218 | method make_multithreaded_lru=Lru_.make_multithreaded_lru 219 | method q_lru_pc ()=q_lru_pc 220 | end 221 | in 222 | make_lru args 223 | 224 | let config = Lazy.force runtime_config 225 | 226 | let _ : unit = lru#set_initial_state 227 | (Lru_.init_state ~max_size:config.lru_max_size ~evict_count:config.lru_evict_count) 228 | 229 | let lru_ops = lru#get_lru_ops () 230 | 231 | 232 | let store : (k,v,_) store = 233 | let module X = Kv_store_with_lru.Make(S) in 234 | let set_blk_dev_ops blk_dev_ops = X.blk_dev_ops:= 235 | 236 | failwith "" 237 | 238 | end 239 | *) 240 | -------------------------------------------------------------------------------- /src/_old/tjr_kv.t.ml: -------------------------------------------------------------------------------- 1 | (** A key-value store (example instance) 2 | 3 | {2 Architecture} 4 | 5 | {%html: 6 | 7 | 8 | 9 | 10 | 11 | %} 12 | 13 | Simplified interface: 14 | 15 | {%html: 16 | 17 |
module type KV = sig
 18 |   type ('a, 't) m
 19 | 
 20 |   type k
 21 | 
 22 |   type v
 23 | 
 24 |   type blk_id
 25 | 
 26 |   type ('k, 'v) kvop = Insert of 'k * 'v | Delete of 'k
 27 | 
 28 |   (* NOTE this is the LRU entry type *)
 29 |   type dirty = bool
 30 | 
 31 |   type 'v entry =
 32 |     | Insert of { value : 'v; dirty : dirty }
 33 |     | Delete of { dirty : dirty }
 34 |     | Lower of 'v option
 35 | 
 36 |   type ('k, 'v, 'blk_id, 't) btree_ops = {
 37 |     find : 'k -> ('v option, 't) m;
 38 |     insert : 'k -> 'v -> (unit, 't) m;
 39 |     delete : 'k -> (unit, 't) m;
 40 |     sync : unit -> ('blk_id, 't) m;
 41 |   }
 42 | 
 43 |   type ('k, 'v, 'blk_id, 't) pc_bt_msg =
 44 |     | Find of 'k * ('v option -> (unit, 't) m)
 45 |     | Detach of { ops : ('k, 'v) kvop list; new_pcache_root : 'blk_id }
 46 | 
 47 |   type ('k, 'v, 't) lru_pc_msg =
 48 |     | Insert of 'k * 'v * (unit -> (unit, 't) m)
 49 |     | Delete of 'k * (unit -> (unit, 't) m)
 50 |     | Find of 'k * ('v option -> (unit, 't) m)
 51 |     | Evictees of ('k * 'v entry) list
 52 |         (** NOTE this is the same as LRU msg type *)
 53 | 
 54 |   (* NOTE the interface provided by the key-value store is the same as
 55 |      that of the LRU component, which we reproduce here *)
 56 |   type persist_mode = Persist_later | Persist_now
 57 | 
 58 |   type ('k, 'v, 't) mt_ops = {
 59 |     mt_find : 'k -> ('v option, 't) m;
 60 |     mt_insert : persist_mode -> 'k -> 'v -> (unit, 't) m;
 61 |     mt_delete : persist_mode -> 'k -> (unit, 't) m;
 62 |     mt_sync_key : 'k -> (unit, 't) m;
 63 |     mt_sync_all_keys : unit -> (unit, 't) m;
 64 |   }
 65 | end
 66 | 
67 | 68 | %} 69 | 70 | *) 71 | 72 | (** See {!Kv_store_with_lru} for more details *) 73 | 74 | 75 | (** {2 Main interfaces} *) 76 | 77 | module Kv_intf = Kv_intf 78 | 79 | 80 | (** {2 Configuration and profilers} *) 81 | 82 | module Kv_config_optcomp = Kv_config_optcomp 83 | module Kv_config_runtime = Kv_config_runtime 84 | module Kv_config_profilers = Kv_config_profilers 85 | 86 | 87 | (* {2 Lwt aux} *) 88 | (* module Lwt_aux = Lwt_aux *) 89 | 90 | 91 | (** {2 Root manager} *) 92 | 93 | module Root_manager = Root_manager 94 | 95 | 96 | (** {2 Btree thread} *) 97 | 98 | module Btree_thread = Btree_thread 99 | 100 | 101 | 102 | (** {2 Pcache thread} *) 103 | 104 | module Pcache_thread = Pcache_thread 105 | 106 | 107 | (** {2 Lru} *) 108 | 109 | module Lru = Lru 110 | 111 | 112 | (** {2 The key-value store} *) 113 | 114 | module Kv_store_with_lru = Kv_store_with_lru 115 | 116 | 117 | 118 | (** {2 Further notes} *) 119 | 120 | (** 121 | 122 | {3 Combining B-tree and pcache roots in a single block} 123 | 124 | One option when syncing the btree+pcache combination would be to write 125 | the pcache roots to disk, and then (in another block) write the 126 | B-tree root. This is fine, but if a crash occurs inbetween, we have 127 | to recover (which isn't difficult, but still adds complexity). As 128 | an alternative, we can write the btree and the pcache roots into 129 | the same block atomically. This means that we don't have to worry 130 | about recovering from a crash (this approach is crash safe by 131 | design). 132 | 133 | *) 134 | -------------------------------------------------------------------------------- /src/_old/todo.ml: -------------------------------------------------------------------------------- 1 | (* 2 | (** A B-tree, with imperative init, no cache, and access to q_pc_bt *) 3 | class type ['k,'v,'ls] btree = object 4 | (* val bt_rt: blk_id ref *) 5 | method set_blk_dev_ops: std_blk_dev_ops -> unit 6 | method set_blk_allocator: std_blk_allocator_ops -> unit 7 | method set_with_bt_rt: (r,t) with_state -> unit 8 | method set_q_pc_bt: ('k,'v)q_pc_bt -> unit 9 | method check_initialized: unit -> unit 10 | method get_q_pc_bt: unit -> ('k,'v)q_pc_bt 11 | method get_map_ops: unit -> ('k,'v,r,'ls,t)map_ops_with_ls 12 | (* method get_ls_ops : unit -> ('k,'v,'ls,'t) ls_ops *) 13 | end 14 | 15 | let make_btree_1 ~make_btree : ('k,'v,'ls)btree = 16 | let is_some,is_none = Option.(is_some,is_none) in 17 | let blk_dev_ops = ref None in 18 | let blk_allocator = ref None in 19 | let with_bt_rt = ref None in 20 | let q_pc_bt = ref None in 21 | let check_initialized () = 22 | assert(is_some !blk_dev_ops); 23 | assert(is_some !blk_allocator); 24 | assert(is_some !with_bt_rt); 25 | assert(is_some !q_pc_bt); 26 | () 27 | in 28 | object 29 | method set_blk_dev_ops x = blk_dev_ops:=Some x 30 | method set_blk_allocator x = blk_allocator:=Some x 31 | method set_with_bt_rt x = with_bt_rt:=Some x 32 | method set_q_pc_bt x = q_pc_bt:=Some x 33 | method check_initialized = check_initialized 34 | method get_q_pc_bt () = 35 | assert(is_some !q_pc_bt); 36 | Option.get !q_pc_bt 37 | method get_map_ops () : ('k,'v,r,'ls,t) map_ops_with_ls = 38 | check_initialized(); 39 | make_btree ~blk_dev_ops:(Option.get !blk_dev_ops) ~blk_alloc:(Option.get !blk_allocator) ~root_ops:(Option.get !with_bt_rt) 40 | end 41 | 42 | let _ = make_btree_1 43 | *) 44 | -------------------------------------------------------------------------------- /src/_todo/fun_store.ml: -------------------------------------------------------------------------------- 1 | (* 2 | 3 | (** Functional store, with a reference for initialization purposes *) 4 | 5 | (* test store -------------------------------------------------------- *) 6 | 7 | (* FIXME this is a common pattern when initializing a store; perhaps 8 | add to Tjr_store *) 9 | let set,get = Tjr_store.(set,get) 10 | 11 | let test_store = ref (Tjr_store.empty_fstore ()) 12 | 13 | let mk_ref' v = 14 | !test_store |> fun t -> 15 | Tjr_store.mk_ref v t |> fun (r,t) -> 16 | test_store:=t; 17 | r 18 | 19 | *) 20 | -------------------------------------------------------------------------------- /src/_todo/ss_synchronous_store/ss_test.ml: -------------------------------------------------------------------------------- 1 | (* 2 | (* testing -------------------------------------------------------- *) 3 | 4 | (* NOTE we need pc_blk_id from synchronous_store.Make FIXME *) 5 | module Test() : sig val run_tests: depth:int -> unit end = struct 6 | 7 | open Tjr_monad 8 | open Tjr_monad.Types 9 | open Tjr_monad.State_passing 10 | module Spi = Tjr_monad.State_passing 11 | open Tjr_pcache 12 | 13 | (* 14 | 15 | We need 16 | - monad; use state-passing 17 | - btree_ops; these are just map ops 18 | - pcache_ops; these are from tjr_pcache; the pcache state needs to be 19 | part of our global state 20 | - XXX ukv_mref_ops; this is just the state 21 | - detach_map_ops; just a map supporting ('k, ('k, 'v) op, 'map) 22 | Tjr_map.map_ops 23 | - bt_sync: perhaps we also record valid states as part of the global 24 | state? the bt_sync operation could return a btree_root (iso to int); 25 | FIXME do we want to identify btree_root as a different type? or 26 | maybe work with 'a blkid? 27 | - sync_ukv_roots: also record this as part of our global state 28 | 29 | 30 | What do we want to test? 31 | 32 | - That the ukv abstracts to a simple map 33 | - take B-tree state (as a map) and append pcache state (as a map) 34 | - the abstract state should be the same before and after a 35 | sync_ukv_root action (which occurs when a roll-up occurs) 36 | - That the state is always well formed: 37 | - (concurrency ... needs to be tested with multiple insert threads 38 | and arb interleaving FIXME) 39 | - ?anything else? 40 | 41 | *) 42 | 43 | (* FIXME this is duplicated in btree_model; DRY *) 44 | 45 | module K = Tjr_int.Make_type_isomorphic_to_int() 46 | type key = K.t 47 | 48 | module V = Tjr_int.Make_type_isomorphic_to_int() 49 | type value = V.t 50 | 51 | module K_map = Tjr_map.Make( 52 | struct type t = key let compare: t -> t -> int = Pervasives.compare end) 53 | 54 | type btree_repr = value K_map.Map_.t 55 | 56 | type dcl_state = (key,value,pc_blk_id) Dcl_spec2.state 57 | 58 | (* need a map from k to (k,v)op *) 59 | 60 | (* if we make the map types the same type, we can union the maps 61 | more easily *) 62 | type k_vop_map = (key,value)Ins_del_op_type.op K_map.Map_.t 63 | 64 | type 'test state = { 65 | test:'test; 66 | btree_state: btree_repr; 67 | pcache_state: dcl_state; (* contains ptr_ref *) 68 | (* ukv_state: ukv_state; *) 69 | free_bt_blk_id: int; 70 | synced_btrees: (bt_blk_id* btree_repr)list; (* assoc list *) 71 | synced_root_pairs: root_pair list; 72 | } 73 | 74 | let init_test_state ~test = { 75 | test; 76 | btree_state=K_map.Map_.empty; 77 | pcache_state=Dcl_spec2.{kvs=[];ptr_ref=Pc_blk_id.int2t 0}; 78 | (* FIXME pcache root is duplicated *) 79 | (* ukv_state={ in_roll_up=false; pcache_root=Pc_blk_id.int2t 0; btree_root=Bt_blk_id.int2t 0 }; *) 80 | free_bt_blk_id=1; 81 | synced_btrees=[]; 82 | synced_root_pairs=[] 83 | } 84 | 85 | 86 | let monad_ops: 'test state state_passing monad_ops = 87 | Spi.monad_ops () 88 | 89 | let ( >>= ) = monad_ops.bind 90 | let return = monad_ops.return 91 | 92 | 93 | (* implement btree ops with a simple map; FIXME maybe put in tjr_btree.test *) 94 | let btree_ops : (key,value,'t) Tjr_btree.Map_ops.map_ops = 95 | (* let ops = K_map.map_ops in *) 96 | let find k = with_world (fun s -> 97 | (try Some (K_map.Map_.find k s.btree_state) with _ -> None),s) 98 | in 99 | let insert k v = with_world (fun s -> 100 | (),{s with btree_state=K_map.Map_.add k v s.btree_state}) 101 | in 102 | let delete k = with_world (fun s -> 103 | (),{s with btree_state=K_map.Map_.remove k s.btree_state}) 104 | in 105 | (* FIXME could do better *) 106 | let insert_many k v kvs = insert k v >>= fun () -> return kvs in 107 | {find;insert;delete;insert_many} 108 | 109 | 110 | let map_union s1 s2 = 111 | K_map.Map_.union (fun k a1 a2 -> Some a2) s1 s2 112 | 113 | (* 114 | (* merge bt and pc; for pc, we have ins and delete actions *) 115 | let map_merge bt pc = 116 | let f key v1 v2 = 117 | match v2 with 118 | | None -> v1 119 | | Some(Delete k') -> 120 | assert(key=k'); 121 | None 122 | | Some(Insert (k,v)) -> 123 | assert(key=k); 124 | Some(v) 125 | in 126 | K_map.Map_.merge f bt pc 127 | *) 128 | 129 | let ops_to_map ~init ~ops = 130 | Tjr_list.with_each_elt 131 | ~list:ops 132 | ~step:(fun ~state elt -> 133 | match elt with 134 | | Delete k -> K_map.Map_.remove k state 135 | | Insert(k,v) -> K_map.Map_.add k v state) 136 | ~init 137 | 138 | let _ = ops_to_map 139 | 140 | let abstract_state s = 141 | s.pcache_state.Dcl_spec2.kvs |> fun ops -> 142 | let init = s.btree_state in 143 | ops_to_map ~init ~ops 144 | 145 | 146 | type abstract_state = value K_map.Map_.t 147 | 148 | 149 | let mref = Mref.{ 150 | get=(fun () -> with_world (fun s -> s.pcache_state,s)); 151 | set=fun pcache_state -> with_world (fun s -> (),{s with pcache_state}) 152 | } 153 | 154 | (* we don't construct these; instead we use an abstract model 155 | FIXME the abstract model should be in plog *) 156 | let pcache_ops : (key, value, (key * (key, value) op) list, pc_blk_id, 'test state state_passing) dcl_ops = 157 | Dcl_spec2.make_spec_ops 158 | ~monad_ops 159 | ~ops_per_block:2 160 | ~new_ptr:"FIXME" 161 | ~with_spec:"FIXME" 162 | 163 | 164 | let _ = pcache_ops 165 | 166 | let pcache_blocks_limit = 2 167 | 168 | (* let ukv_mref_ops : (ukv_state, 'test state state_passing) Mref_plus.mref = 169 | * let get () = with_world (fun s -> s.ukv_state,s) in 170 | * let set ukv_state = with_world (fun s -> (),{s with ukv_state}) in 171 | * let with_ref f = with_world (fun s -> 172 | * let (b,ukv_state) = f s.ukv_state in 173 | * b,{s with ukv_state}) 174 | * in 175 | * Mref_plus.{ get; set; with_ref } *) 176 | 177 | 178 | (* when we sync a btree we simply increment the bt_blk_id and 179 | store the current btree state in the list *) 180 | let bt_sync () : (bt_blk_id,'t) m = 181 | with_world (fun s -> 182 | let n = s.free_bt_blk_id in 183 | let nn = Bt_blk_id.int2t n in 184 | nn,{ s with free_bt_blk_id=n+1; 185 | synced_btrees=(nn,s.btree_state)::s.synced_btrees }) 186 | 187 | (* what do we want to do here? when we detach the pc, the abstract 188 | model increases ptr_ref; for each ptr ref, there is therefore a 189 | corresponding map from that ptr, and a map upto that ptr; when 190 | we update the bt and sync, it contains the map upto the ptr, 191 | and so does the pc; but then we swing the bt and pc root 192 | pointers with this operation; let's avoid tracking the 193 | correspondence between ptrs and kvops; instead, just require 194 | that after a sync the abstract map is what it should be FIXME 195 | we are not really testing the behaviour whereby pc and bt roots 196 | sync to disk at arbitrary times *) 197 | let sync_ukv_roots ~btree_root ~pcache_root = return () 198 | 199 | (* FIXME why are we revealing the map impl type here? pcache_ops also shares 'map type *) 200 | let kvop_map_bindings kvops = kvops |> List.map snd 201 | 202 | let execute_btree_rollup = 203 | execute_btree_rollup 204 | ~monad_ops 205 | ~bt_insert:btree_ops.insert 206 | ~bt_delete:btree_ops.delete 207 | ~bt_sync 208 | ~kvop_map_bindings 209 | ~sync_new_roots:(fun root_pair -> 210 | with_world (fun s -> 211 | ((),{s with synced_root_pairs=root_pair::s.synced_root_pairs}))) 212 | 213 | let ukv_ops : (key,value,'t) ukv_ops = 214 | make_ukv_ops 215 | ~monad_ops 216 | ~pcache_ops 217 | ~pcache_blocks_limit 218 | ~bt_find:btree_ops.find 219 | ~execute_btree_rollup 220 | 221 | let _ = pcache_ops 222 | 223 | let insert k v s = 224 | ukv_ops.insert k v |> fun m -> 225 | Tjr_monad.State_passing.run ~init_state:s m |> fun (_,s') -> 226 | s' 227 | 228 | let delete k s = 229 | ukv_ops.delete k |> fun m -> 230 | Tjr_monad.State_passing.run ~init_state:s m |> fun (_,s') -> 231 | s' 232 | 233 | 234 | (* OK; now we can run some tests by exhaustively enumerating states *) 235 | (* FIXME reuse btree testing code *) 236 | 237 | open Tjr_exhaustive_testing 238 | 239 | let ops = 240 | Tjr_list.from_to 1 10 |> 241 | List.map (fun k -> 242 | let v = 2*k in 243 | let (k,v) = (K.int2t k, V.int2t v) in 244 | [Insert(k,v); Delete k]) |> 245 | List.concat 246 | 247 | 248 | (* we do not have sets of states (since we can't really compare 249 | states for equality very easily); instead we just apply all 250 | operations upto a set depth *) 251 | 252 | 253 | (* the test state is a list of operations (most recent first) *) 254 | 255 | type op' = I of int * int | D of int [@@deriving yojson] 256 | 257 | type ops' = op' list [@@deriving yojson] 258 | 259 | let op2op' = function 260 | | Insert(k,v) -> I(K.t2int k,V.t2int v) 261 | | Delete k -> D(K.t2int k) 262 | 263 | 264 | type iis = (int * int) list [@@deriving yojson] 265 | 266 | 267 | let run_tests ~depth = 268 | 269 | let step_test op s = {s with test=op::s.test} in 270 | 271 | let step s op = 272 | if List.length s.test > depth then [] else 273 | begin 274 | (* Printf.printf "stepping...\n%!"; *) 275 | match op with 276 | | Insert(k,v) -> [insert k v s|>step_test op] 277 | | Delete k -> [delete k s|>step_test op] 278 | end 279 | in 280 | 281 | let check_state s = () in 282 | 283 | let check_step s op s' = 284 | let t = abstract_state s in 285 | match op with 286 | | Insert(k,v) -> 287 | let correct_t' = K_map.Map_.add k v t in 288 | let abstract_s' = abstract_state s' in 289 | Pcache_debug.log_lazy (fun () -> 290 | let bindings map = 291 | K_map.Map_.bindings map |> 292 | List.map (fun (k,v) -> K.t2int k,V.t2int v) |> 293 | iis_to_yojson |> Yojson.Safe.pretty_to_string 294 | in 295 | let expected = bindings correct_t' in 296 | let actual = bindings abstract_s' in 297 | Printf.sprintf " 298 | Checking step... 299 | Op: %s 300 | Previous ops: %s 301 | Expected: %s 302 | Actual: %s 303 | " 304 | (op|>op2op'|>op'_to_yojson|>Yojson.Safe.pretty_to_string) 305 | (s.test|>List.map op2op'|>ops'_to_yojson|>Yojson.Safe.pretty_to_string) 306 | expected 307 | actual 308 | ); 309 | (* NOTE we can't just expect pervasive equality to work on maps *) 310 | assert(K_map.Map_.equal (=) correct_t' abstract_s'); 311 | () 312 | | Delete k -> 313 | let correct_t' = K_map.Map_.remove k t in 314 | let abstract_s' = abstract_state s' in 315 | Pcache_debug.log_lazy (fun () -> 316 | let bindings map = 317 | K_map.Map_.bindings map |> 318 | List.map (fun (k,v) -> K.t2int k,V.t2int v) |> 319 | iis_to_yojson |> Yojson.Safe.pretty_to_string 320 | in 321 | let expected = bindings correct_t' in 322 | let actual = bindings abstract_s' in 323 | Printf.sprintf " 324 | Checking step... 325 | Op: %s 326 | Previous ops: %s 327 | Expected: %s 328 | Actual: %s 329 | " 330 | (op|>op2op'|>op'_to_yojson|>Yojson.Safe.pretty_to_string) 331 | (s.test|>List.map op2op'|>ops'_to_yojson|>Yojson.Safe.pretty_to_string) 332 | expected 333 | actual 334 | ); 335 | (* NOTE we can't just expect pervasive equality to work on maps *) 336 | assert(K_map.Map_.equal (=) correct_t' abstract_s'); 337 | () 338 | in 339 | 340 | let test_ops = { step; check_state; check_step } in 341 | 342 | 343 | let init_states = [init_test_state ~test:[]] in 344 | 345 | Printf.printf "%s: tests starting...\n%!" __FILE__; 346 | 347 | (* we also need to maintain a set of states; in this case, we 348 | can't really check equality of two states; so instead we need 349 | to impose a max bound on the number of states considered, which 350 | can be done by bounding the depth of operations *) 351 | assert(() = Tjr_exhaustive_testing.test_till_no_successor_states ~test_ops ~ops ~init_states); 352 | Printf.printf "%s: ...tests finished\n%!" __FILE__ 353 | 354 | 355 | end (* Test *) 356 | *) 357 | -------------------------------------------------------------------------------- /src/_todo/ss_synchronous_store/synchronous_store.ml: -------------------------------------------------------------------------------- 1 | (* 2 | 3 | (** An uncached kv store *) 4 | 5 | (** 6 | 7 | NOTE see diagram in /docs/ 8 | 9 | At a high level, this is a k -> v map. 10 | 11 | This is uncached: there is no cache in front of the store. The pcache 12 | works on a raw uncached block device. The B-tree syncs the block 13 | device after every batch operation. 14 | 15 | We expect to use this with an LRU in front. 16 | 17 | At intervals, when the pcache becomes long, some initial prefix of the 18 | pcache is rolled into the B-tree. The root of the pcache is adjusted 19 | (the old blocks can be reclaimed), and the root is written to disk 20 | (but not necessarily synced). On crash, if the old root is used there 21 | is no problem - we just replay these modifications over the B-tree. We 22 | require that if the new pcache root hits disk, the B-tree is also on 23 | disk. One approach is to async (flush btree; flush pcache root). 24 | 25 | *) 26 | 27 | open Tjr_monad.Types 28 | (* open Tjr_pcache.Types *) 29 | open Tjr_pcache.Ins_del_op_type 30 | 31 | module type REQUIRES = sig 32 | module Bt_blk_id:Tjr_int.TYPE_ISOMORPHIC_TO_INT 33 | module Pc_blk_id:Tjr_int.TYPE_ISOMORPHIC_TO_INT (* NOTE only needed for testing; otherwise abstract *) 34 | end 35 | 36 | module Make(Requires : REQUIRES) = struct 37 | 38 | open Requires 39 | 40 | open Tjr_btree.Map_ops 41 | 42 | open Tjr_pcache.Dcl_types 43 | open Tjr_pcache.Pcache_types 44 | 45 | 46 | 47 | type bt_blk_id = Bt_blk_id.t 48 | type pc_blk_id = Pc_blk_id.t 49 | 50 | 51 | (** We execute a roll up in another thread, to avoid blocking the 52 | pcache. The roll-up thread will typically finish with a new pair 53 | of roots. *) 54 | type root_pair = { 55 | pcache_root: pc_blk_id; 56 | btree_root: bt_blk_id; 57 | } 58 | 59 | 60 | (** This should execute in another dedicated rollup thread, so that 61 | the pcache thread is not blocked. We can't just instantiate this 62 | function here. In the pcache thread we need to join a msg to the 63 | end of a msg queue, signalling the rollup thread to perform a 64 | rollup. In this case, the rollup thread likely already knows 65 | operations such as [bt_insert] etc. which it can use to 66 | instantiate this function. 67 | 68 | NOTE that we return a unit so that from the pcache we can call 69 | this function without waiting for the rollup to occur. So the new 70 | roots have to be handled somewhere else (not in the pcache 71 | thread). *) 72 | let execute_btree_rollup 73 | ~monad_ops 74 | ~(bt_insert:'k -> 'v -> (unit,'t) m) 75 | ~bt_delete 76 | ~bt_sync (* to sync the B-tree to get the new B-tree root *) 77 | ~(kvop_map_bindings:'op_map -> ('k,'v) op list) 78 | ~sync_new_roots (* not clear if we should just return the new roots rather than explicitly passing in a sync op *) 79 | = 80 | let ( >>= ) = monad_ops.bind in 81 | let return = monad_ops.return in 82 | let f detach_result : (unit,'t) m = 83 | begin 84 | (* map consists of all the entries we need to roll up *) 85 | detach_result.abs_past |> kvop_map_bindings |> fun ops -> 86 | let rec loop ops = 87 | match ops with 88 | | [] -> return (`Finished(detach_result.start_block,detach_result.current_block)) (* FIXME just return detach_result *) 89 | | v::ops -> 90 | match v with 91 | | Insert (k,v) -> bt_insert k v >>= fun () -> loop ops 92 | | Delete k -> bt_delete k >>= fun () -> loop ops 93 | in 94 | loop ops 95 | end 96 | >>= 97 | begin 98 | function `Finished(old_root,(*new*)pcache_root) -> 99 | (* sync the btree *) 100 | bt_sync () >>= fun btree_root -> 101 | (* now we need to flush the new roots to disk *) 102 | sync_new_roots {pcache_root; btree_root} >>= fun () -> 103 | return () 104 | end 105 | in 106 | f 107 | 108 | 109 | (* FIXME rename ukv *) 110 | type ('k,'v,'t) ukv_ops = ('k,'v,'t) Tjr_btree.Map_ops.map_ops 111 | 112 | (* we perform a "roll up" operation, merging the pcache into the 113 | B-tree, when the number of pcache blocks reaches 114 | pcache_blocks_limit *) 115 | 116 | (** Construct the UKV. Parameters: 117 | - [monad_ops] 118 | - [pcache_ops]: dcl_ops from tjr_pcache 119 | - [pcache_blocks_limit]: how many blocks in the pcache before attempting a roll-up; if the length of pcache is [>=] this limit, we attempt a roll-up; NOTE that this limit should be >= 2 (if we roll up with 1 block, then in fact nothing gets rolled up because we roll up "upto" the current block; not a problem but probably pointless for testing) 120 | - [bt_find]: called if key not in pcache map FIXME do we need a write-through cache here? or just rely on the front-end LRU? FIXME note that even if a rollup is taking place, we can use the old B-tree root for the [bt_find] operation. 121 | - [execute_btree_rollup]: called to detach the rollup into another thread; typically this operation puts a msg on a message queue which is then received and acted upon by the dedicated rollup thread 122 | *) 123 | let make_ukv_ops 124 | ~monad_ops 125 | ~pcache_ops 126 | ~pcache_blocks_limit 127 | ~bt_find 128 | ~execute_btree_rollup 129 | : ('k,'v,'t) ukv_ops 130 | = 131 | (* let open Mref_plus in *) 132 | let ( >>= ) = monad_ops.bind in 133 | let return = monad_ops.return in 134 | let pc = pcache_ops in 135 | let find k = 136 | pc.find k >>= fun op -> 137 | match op with 138 | | None -> bt_find k 139 | | Some op -> 140 | match op with 141 | | Insert(k,v) -> return (Some v) 142 | | Delete k -> return None 143 | in 144 | let maybe_roll_up () = 145 | pc.block_list_length () >>= fun n -> 146 | match n >= pcache_blocks_limit with 147 | | false -> return `No_roll_up_needed 148 | | true -> 149 | pc.detach () >>= fun detach_result -> 150 | execute_btree_rollup detach_result >>= fun () -> 151 | return `Ok 152 | in 153 | let insert k v = 154 | pc.add (Insert(k,v)) >>= fun () -> 155 | maybe_roll_up () >>= fun _ -> 156 | return () 157 | in 158 | let delete k = 159 | pc.add (Delete k) >>= fun () -> 160 | maybe_roll_up () >>= fun _ -> 161 | return () 162 | in 163 | let insert_many k v kvs = 164 | (* FIXME we should do something smarter here *) 165 | insert k v >>= fun () -> return kvs 166 | in 167 | { find; insert; delete; insert_many } 168 | 169 | 170 | 171 | end (* Make *) 172 | *) 173 | -------------------------------------------------------------------------------- /src/_todo/unthreaded_uncached_backup.ml_: -------------------------------------------------------------------------------- 1 | (** An uncached kv store *) 2 | 3 | (** 4 | 5 | NOTE see diagram in /docs/ 6 | 7 | At a high level, this is a k -> v map. 8 | 9 | This is uncached: there is no cache in front of the store. The pcache 10 | works on a raw uncached block device. The B-tree syncs the block 11 | device after every batch operation. 12 | 13 | We expect to use this with an LRU in front. 14 | 15 | At intervals, when the pcache becomes long, some initial prefix of the 16 | pcache is rolled into the B-tree. The root of the pcache is adjusted 17 | (the old blocks can be reclaimed), and the root is written to disk 18 | (but not necessarily synced). On crash, if the old root is used there 19 | is no problem - we just replay these modifications over the B-tree. We 20 | require that if the new pcache root hits disk, the B-tree is also on 21 | disk. One approach is to async (flush btree; flush pcache root). 22 | 23 | *) 24 | 25 | open Tjr_monad 26 | open Tjr_monad.Types 27 | open Tjr_pcache 28 | 29 | module type REQUIRES = sig 30 | module Bt_blk_id:Tjr_int.TYPE_ISOMORPHIC_TO_INT 31 | module Pc_blk_id:Tjr_int.TYPE_ISOMORPHIC_TO_INT (* NOTE only needed for testing; otherwise abstract *) 32 | end 33 | 34 | module Make(Requires : REQUIRES) = struct 35 | 36 | open Requires 37 | 38 | open Tjr_btree.Map_ops 39 | 40 | open Persistent_log 41 | 42 | 43 | type bt_blk_id = Bt_blk_id.t 44 | type pc_blk_id = Pc_blk_id.t 45 | 46 | (** The ukv state. Fields: 47 | - [in_roll_up]: a flag covering the critical section when we are executing a roll up 48 | - [pcache_root]: the root of the pcache 49 | - [btree_root]: the root of the B-tree 50 | *) 51 | type ukv_state = { 52 | in_roll_up: bool; 53 | pcache_root: pc_blk_id; 54 | btree_root: bt_blk_id; 55 | } 56 | 57 | 58 | (* FIXME don't open this - can get confusing if we are with mref or 59 | mrefplus *) 60 | (* open Tjr_monad.Mref_plus *) 61 | 62 | type 't ukv_state_ops = (ukv_state,'t) Tjr_monad.Mref_plus.mref 63 | 64 | 65 | type ('k,'v,'t) ukv_ops = ('k,'v,'t) Tjr_btree.Map_ops.map_ops 66 | 67 | (* we perform a "roll up" operation, merging the pcache into the 68 | B-tree, when the number of pcache blocks reaches 69 | pcache_blocks_limit *) 70 | 71 | (* NOTE when we detach, we should not alter the pcache root, but 72 | later after the btree changes are synced, we can sync the new 73 | pcache root; in memory we also store both roots (as the ukv 74 | state) in an mref *) 75 | (** Construct the UKV. Parameters: 76 | - [monad_ops] 77 | - [btree_ops] 78 | - [pcache_ops] 79 | - [pcache_blocks_limit]: how many blocks in the pcache before attempting a roll-up; if the length of pcache is [>=] this limit, we attempt a roll-up; NOTE that this limit should be >= 2 (if we roll up with 1 block, then in fact nothing gets rolled up because we roll up "upto" the current block; not a problem but probably pointless for testing) 80 | - [ukv_mref_ops]: a reference to the ukv state; the pcache and btree roots get updated (also [in_roll_up] is updated) 81 | - [detach_map_ops]: used to get the bindings for the blocks that have been detached from the pcache 82 | - [bt_sync]: at the end of the roll-up, we sync the B-tree itself to disk 83 | - [sync_ukv_roots]: called just before leaving the critical section, to record new roots for B-tree and pcache 84 | *) 85 | let make_ukv_ops 86 | ~monad_ops ~btree_ops ~pcache_ops ~pcache_blocks_limit 87 | ~ukv_mref_ops ~kvop_map_bindings 88 | ~bt_sync (* to sync the B-tree to get the new B-tree root *) 89 | ~sync_ukv_roots (* to write the ukv roots to disk somewhere *) 90 | 91 | : ('k,'v,'t) ukv_ops 92 | = 93 | let open Mref_plus in 94 | let ( >>= ) = monad_ops.bind in 95 | let return = monad_ops.return in 96 | dest_map_ops btree_ops @@ fun ~find ~insert ~delete ~insert_many -> 97 | (* rename just so we don't get confused *) 98 | let (bt_find,bt_insert,bt_delete,bt_insert_many) = (find,insert,delete,insert_many) in 99 | let (*Persistent_log.*){find; add; detach; get_block_list_length} = pcache_ops in 100 | let (pc_find,pc_add,pc_detach,pc_get_block_list_length) = (find,add,detach,get_block_list_length) in 101 | let find k = 102 | pc_find k >>= fun op -> 103 | match op with 104 | | None -> bt_find k 105 | | Some op -> 106 | match op with 107 | | Insert(k,v) -> return (Some v) 108 | | Delete k -> return None 109 | in 110 | let maybe_roll_up () = 111 | ukv_mref_ops.get () >>= function { in_roll_up; _ } -> 112 | match in_roll_up with 113 | | true -> 114 | return `Already_in_roll_up 115 | | false -> 116 | pc_get_block_list_length () >>= fun n -> 117 | match n >= pcache_blocks_limit with 118 | | false -> return `No_roll_up_needed 119 | | true -> 120 | (* we need to roll-up the pcache into the B-tree *) 121 | (* first set the flag; if already set, just skip the roll 122 | up since someone else is doing it *) 123 | ukv_mref_ops.with_ref (fun s -> 124 | match s.in_roll_up with 125 | | true -> `Already_in_roll_up,s 126 | | false -> `Ok,{s with in_roll_up=true}) >>= 127 | begin 128 | function 129 | | `Already_in_roll_up -> 130 | (* of course, we checked in_roll_up above, and it was 131 | false; but a concurrent thread may have set it in the 132 | meantime *) 133 | return `Already_in_roll_up 134 | | `Ok -> 135 | (* the flag has been set; we need to roll up the 136 | cache; NOTE that if we have a single block in the 137 | pcache, and attempt to roll-up, then in fact 138 | nothing will be rolled up; FIXME this is presumably 139 | an error, so we require pclimit >= 2 FIXME we also 140 | need some sort of backpressure/prioritization on 141 | this roll-up thread in case the cache is running 142 | too far ahead of the B-tree *) 143 | pc_detach () >>= fun (old_root,(map:'map),new_root,_(*new_map*)) -> 144 | (* map consists of all the entries we need to roll up *) 145 | map |> kvop_map_bindings |> fun ops -> 146 | let rec loop ops = 147 | match ops with 148 | | [] -> return (`Finished(old_root,new_root)) 149 | | v::ops -> 150 | match v with 151 | | Insert (k,v) -> bt_insert k v >>= fun () -> loop ops 152 | | Delete k -> bt_delete k >>= fun () -> loop ops 153 | in 154 | loop ops 155 | end 156 | >>= 157 | begin 158 | function 159 | | `Already_in_roll_up -> return `Already_in_roll_up 160 | | `Finished(old_root,(*new*)pcache_root) -> 161 | (* sync the btree *) 162 | (* NOTE this should be done in the critical section, before resetting the flag *) 163 | bt_sync () >>= fun btree_root -> 164 | (* now we need to reset the flag, and the roots *) 165 | sync_ukv_roots ~btree_root ~pcache_root >>= fun () -> 166 | ukv_mref_ops.with_ref (fun s -> 167 | assert(s.in_roll_up); 168 | (),{ in_roll_up=false; btree_root; pcache_root }) >>= fun () -> 169 | return `Ok 170 | end 171 | in 172 | let insert k v = 173 | pc_add (Insert(k,v)) >>= fun () -> 174 | maybe_roll_up () >>= fun _ -> 175 | return () 176 | in 177 | let delete k = 178 | pc_add (Delete k) >>= fun () -> 179 | maybe_roll_up () >>= fun _ -> 180 | return () 181 | in 182 | let insert_many k v kvs = 183 | (* FIXME we should do something smarter here *) 184 | insert k v >>= fun () -> return kvs 185 | in 186 | { find; insert; delete; insert_many } 187 | 188 | 189 | 190 | (* testing -------------------------------------------------------- *) 191 | 192 | module Test() : sig val run_tests: depth:int -> unit end = struct 193 | 194 | open Tjr_monad 195 | open Monad 196 | open State_passing_instance 197 | module Spi = State_passing_instance 198 | 199 | (* 200 | We need 201 | - monad; use state-passing 202 | - btree_ops; these are just map ops 203 | - pcache_ops; these are from persistent_log; the pcache state needs to be part of our global state 204 | - ukv_mref_ops; this is just the state 205 | - detach_map_ops; just a map supporting ('k, ('k, 'v) op, 'map) Tjr_map.map_ops 206 | - bt_sync: perhaps we also record valid states as part of the global state? the bt_sync operation could return a btree_root (iso to int); FIXME do we want to identify btree_root as a different type? or maybe work with 'a blkid? 207 | - sync_ukv_roots: also record this as part of our global state 208 | 209 | 210 | What do we want to test? 211 | 212 | - That the ukv abstracts to a simple map 213 | - take B-tree state (as a map) and append pcache state (as a map) 214 | - the abstract state should be the same before and after a sync_ukv_root action (which occurs when a roll-up occurs) 215 | - That the state is always well formed: 216 | - (concurrency ... needs to be tested with multiple insert threads and arb interleaving FIXME) 217 | - ?anything else? 218 | 219 | *) 220 | 221 | 222 | (* FIXME remove "abstract" and just use model; remove model_ops *) 223 | module Pc_model = Persistent_log.Abstract_model_ops(Pc_blk_id) 224 | 225 | 226 | module K = Tjr_int.Make_type_isomorphic_to_int() 227 | type key = K.t 228 | 229 | module V = Tjr_int.Make_type_isomorphic_to_int() 230 | type value = V.t 231 | 232 | module K_map = Tjr_map.Make(struct type t = key let compare: t -> t -> int = Pervasives.compare end) 233 | 234 | type btree_repr = value K_map.Map_.t 235 | 236 | type pc_model_state = (key,value) Pc_model.state 237 | 238 | (* need a map from k to (k,v)op *) 239 | 240 | (* if we make the map types the same type, we can union the maps more easily *) 241 | type k_vop_map = (key,value)Persistent_log.op K_map.Map_.t 242 | 243 | type 'test state = { 244 | test:'test; 245 | (* pc_model_state: pc_model_state; *) 246 | btree_state: btree_repr; 247 | pcache_state: pc_model_state; (* contains ptr_ref *) 248 | ukv_state: ukv_state; 249 | free_bt_blk_id: int; 250 | synced_btrees: (bt_blk_id* btree_repr)list; (* assoc list *) 251 | synced_ukv_roots: (bt_blk_id*pc_blk_id) list; 252 | } 253 | 254 | let init_test_state ~test = { 255 | test; 256 | btree_state=K_map.Map_.empty; 257 | pcache_state=Pc_model.{kvs=[];ptr_ref=Pc_blk_id.int2t 0}; 258 | (* FIXME pcache root is duplicated *) 259 | ukv_state={ in_roll_up=false; pcache_root=Pc_blk_id.int2t 0; btree_root=Bt_blk_id.int2t 0 }; 260 | free_bt_blk_id=1; 261 | synced_btrees=[]; 262 | synced_ukv_roots=[] 263 | } 264 | 265 | 266 | let monad_ops: 'test state state_passing monad_ops = 267 | Spi.monad_ops () 268 | 269 | let ( >>= ) = monad_ops.bind 270 | let return = monad_ops.return 271 | 272 | 273 | (* implement btree ops with a simple map; FIXME maybe put in tjr_btree.test *) 274 | let btree_ops : (key,value,'t) Tjr_btree.Map_ops.map_ops = 275 | (* let ops = K_map.map_ops in *) 276 | let find k = with_world (fun s -> 277 | (try Some (K_map.Map_.find k s.btree_state) with _ -> None),s) 278 | in 279 | let insert k v = with_world (fun s -> 280 | (),{s with btree_state=K_map.Map_.add k v s.btree_state}) 281 | in 282 | let delete k = with_world (fun s -> 283 | (),{s with btree_state=K_map.Map_.remove k s.btree_state}) 284 | in 285 | (* FIXME could do better *) 286 | let insert_many k v kvs = insert k v >>= fun () -> return kvs in 287 | {find;insert;delete;insert_many} 288 | 289 | 290 | let map_union s1 s2 = 291 | K_map.Map_.union (fun k a1 a2 -> Some a2) s1 s2 292 | 293 | (* 294 | (* merge bt and pc; for pc, we have ins and delete actions *) 295 | let map_merge bt pc = 296 | let f key v1 v2 = 297 | match v2 with 298 | | None -> v1 299 | | Some(Delete k') -> 300 | assert(key=k'); 301 | None 302 | | Some(Insert (k,v)) -> 303 | assert(key=k); 304 | Some(v) 305 | in 306 | K_map.Map_.merge f bt pc 307 | *) 308 | 309 | let ops_to_map ~init ~ops = 310 | Tjr_list.with_each_elt 311 | ~list:ops 312 | ~step:(fun ~state elt -> 313 | match elt with 314 | | Delete k -> K_map.Map_.remove k state 315 | | Insert(k,v) -> K_map.Map_.add k v state) 316 | ~init 317 | 318 | let _ = ops_to_map 319 | 320 | let abstract_state s = 321 | s.pcache_state.Pc_model.kvs |> List.map snd |> fun ops -> 322 | let init = s.btree_state in 323 | ops_to_map ~init ~ops 324 | 325 | 326 | type abstract_state = value K_map.Map_.t 327 | 328 | 329 | let mref = Mref.{ 330 | get=(fun () -> with_world (fun s -> s.pcache_state,s)); 331 | set=fun pcache_state -> with_world (fun s -> (),{s with pcache_state}) 332 | } 333 | 334 | (* we don't construct these; instead we use an abstract model 335 | FIXME the abstract model should be in plog *) 336 | let pcache_ops : (key, value, (key * (key, value) op) list, pc_blk_id, 'test state state_passing) plog_ops = 337 | Pc_model.abstract_model_ops 338 | ~monad_ops 339 | ~ops_per_block:2 340 | ~mref 341 | 342 | let _ = pcache_ops 343 | 344 | let pcache_blocks_limit = 2 345 | 346 | let ukv_mref_ops : (ukv_state, 'test state state_passing) Mref_plus.mref = 347 | let get () = with_world (fun s -> s.ukv_state,s) in 348 | let set ukv_state = with_world (fun s -> (),{s with ukv_state}) in 349 | let with_ref f = with_world (fun s -> 350 | let (b,ukv_state) = f s.ukv_state in 351 | b,{s with ukv_state}) 352 | in 353 | Mref_plus.{ get; set; with_ref } 354 | 355 | 356 | (* when we sync a btree we simply increment the bt_blk_id and 357 | store the current btree state in the list *) 358 | let bt_sync () : (bt_blk_id,'t) m = 359 | with_world (fun s -> 360 | let n = s.free_bt_blk_id in 361 | let nn = Bt_blk_id.int2t n in 362 | nn,{ s with free_bt_blk_id=n+1; 363 | synced_btrees=(nn,s.btree_state)::s.synced_btrees }) 364 | 365 | (* what do we want to do here? when we detach the pc, the abstract 366 | model increases ptr_ref; for each ptr ref, there is therefore a 367 | corresponding map from that ptr, and a map upto that ptr; when 368 | we update the bt and sync, it contains the map upto the ptr, 369 | and so does the pc; but then we swing the bt and pc root 370 | pointers with this operation; let's avoid tracking the 371 | correspondence between ptrs and kvops; instead, just require 372 | that after a sync the abstract map is what it should be FIXME 373 | we are not really testing the behaviour whereby pc and bt roots 374 | sync to disk at arbitrary times *) 375 | let sync_ukv_roots ~btree_root ~pcache_root = return () 376 | 377 | (* FIXME why are we revealing the map impl type here? pcache_ops also shares 'map type *) 378 | let kvop_map_bindings kvops = kvops |> List.map snd 379 | 380 | let ukv_ops : (key,value,'t) ukv_ops = 381 | make_ukv_ops 382 | ~monad_ops ~btree_ops ~pcache_ops ~pcache_blocks_limit 383 | ~ukv_mref_ops ~kvop_map_bindings 384 | ~bt_sync (* to sync the B-tree to get the new B-tree root *) 385 | ~sync_ukv_roots (* to write the ukv roots to disk somewhere *) 386 | 387 | let _ = pcache_ops 388 | 389 | let insert k v s = 390 | ukv_ops.insert k v |> fun m -> 391 | Tjr_monad.State_passing_instance.run ~init_state:s m |> fun (_,s') -> 392 | s' 393 | 394 | let delete k s = 395 | ukv_ops.delete k |> fun m -> 396 | Tjr_monad.State_passing_instance.run ~init_state:s m |> fun (_,s') -> 397 | s' 398 | 399 | 400 | (* OK; now we can run some tests by exhaustively enumerating states *) 401 | (* FIXME reuse btree testing code *) 402 | 403 | open Tjr_exhaustive_testing 404 | 405 | let ops = 406 | Tjr_list.from_to 1 10 |> 407 | List.map (fun k -> 408 | let v = 2*k in 409 | let (k,v) = (K.int2t k, V.int2t v) in 410 | [Insert(k,v); Delete k]) |> 411 | List.concat 412 | 413 | 414 | (* we do not have sets of states (since we can't really compare 415 | states for equality very easily); instead we just apply all 416 | operations upto a set depth *) 417 | 418 | 419 | (* the test state is a list of operations (most recent first) *) 420 | 421 | type op' = I of int * int | D of int [@@deriving yojson] 422 | 423 | type ops' = op' list [@@deriving yojson] 424 | 425 | let op2op' = function 426 | | Insert(k,v) -> I(K.t2int k,V.t2int v) 427 | | Delete k -> D(K.t2int k) 428 | 429 | 430 | type iis = (int * int) list [@@deriving yojson] 431 | 432 | 433 | let run_tests ~depth = 434 | 435 | let step_test op s = {s with test=op::s.test} in 436 | 437 | let step s op = 438 | if List.length s.test > depth then [] else 439 | begin 440 | (* Printf.printf "stepping...\n%!"; *) 441 | match op with 442 | | Insert(k,v) -> [insert k v s|>step_test op] 443 | | Delete k -> [delete k s|>step_test op] 444 | end 445 | in 446 | 447 | let check_state s = () in 448 | 449 | let check_step s op s' = 450 | let t = abstract_state s in 451 | match op with 452 | | Insert(k,v) -> 453 | let correct_t' = K_map.Map_.add k v t in 454 | let abstract_s' = abstract_state s' in 455 | Pcache_debug.log_lazy (fun () -> 456 | let bindings map = 457 | K_map.Map_.bindings map |> 458 | List.map (fun (k,v) -> K.t2int k,V.t2int v) |> 459 | iis_to_yojson |> Yojson.Safe.pretty_to_string 460 | in 461 | let expected = bindings correct_t' in 462 | let actual = bindings abstract_s' in 463 | Printf.sprintf " 464 | Checking step... 465 | Op: %s 466 | Previous ops: %s 467 | Expected: %s 468 | Actual: %s 469 | " 470 | (op|>op2op'|>op'_to_yojson|>Yojson.Safe.pretty_to_string) 471 | (s.test|>List.map op2op'|>ops'_to_yojson|>Yojson.Safe.pretty_to_string) 472 | expected 473 | actual 474 | ); 475 | (* NOTE we can't just expect pervasive equality to work on maps *) 476 | assert(K_map.Map_.equal (=) correct_t' abstract_s'); 477 | () 478 | | Delete k -> 479 | let correct_t' = K_map.Map_.remove k t in 480 | let abstract_s' = abstract_state s' in 481 | Pcache_debug.log_lazy (fun () -> 482 | let bindings map = 483 | K_map.Map_.bindings map |> 484 | List.map (fun (k,v) -> K.t2int k,V.t2int v) |> 485 | iis_to_yojson |> Yojson.Safe.pretty_to_string 486 | in 487 | let expected = bindings correct_t' in 488 | let actual = bindings abstract_s' in 489 | Printf.sprintf " 490 | Checking step... 491 | Op: %s 492 | Previous ops: %s 493 | Expected: %s 494 | Actual: %s 495 | " 496 | (op|>op2op'|>op'_to_yojson|>Yojson.Safe.pretty_to_string) 497 | (s.test|>List.map op2op'|>ops'_to_yojson|>Yojson.Safe.pretty_to_string) 498 | expected 499 | actual 500 | ); 501 | (* NOTE we can't just expect pervasive equality to work on maps *) 502 | assert(K_map.Map_.equal (=) correct_t' abstract_s'); 503 | () 504 | in 505 | 506 | let test_ops = { step; check_state; check_step } in 507 | 508 | 509 | let init_states = [init_test_state ~test:[]] in 510 | 511 | Printf.printf "%s: tests starting...\n%!" __FILE__; 512 | 513 | (* we also need to maintain a set of states; in this case, we 514 | can't really check equality of two states; so instead we need 515 | to impose a max bound on the number of states considered, which 516 | can be done by bounding the depth of operations *) 517 | assert(() = Tjr_exhaustive_testing.test_till_no_successor_states ~test_ops ~ops ~init_states); 518 | Printf.printf "%s: ...tests finished\n%!" __FILE__ 519 | 520 | 521 | end (* Test *) 522 | end (* Make *) 523 | -------------------------------------------------------------------------------- /src/btree_thread.ml: -------------------------------------------------------------------------------- 1 | (** The B-tree worker thread; don't open *) 2 | 3 | (* open Shared_ctxt *) 4 | open Kv_intf 5 | open Kv_config_profilers 6 | open Root_manager 7 | 8 | module type S = sig 9 | type t 10 | val monad_ops : t monad_ops 11 | val async : t async 12 | type r = Shared_ctxt.r (* the queues are specialized to this unfortunately FIXME *) 13 | end 14 | 15 | module Make(S:S) = struct 16 | module S=S 17 | open S 18 | 19 | let ( >>= ) = monad_ops.bind 20 | let return = monad_ops.return 21 | 22 | let make_btree_thread (type ls) 23 | ~(write_origin:Origin.t -> (unit,t)m) (* where we write the roots *) 24 | ~(q_pc_bt:(_,_,_)q_pc_bt) 25 | ~(map_ops_bt:('k,'v,r,ls,t)map_ops_with_ls) 26 | ~(sync_bt:(unit -> (r,t)m)) 27 | : < start_btree_thread: unit -> (unit,t)m > 28 | = 29 | let open (struct 30 | open Kvop 31 | 32 | let [d2b_ea;d2b_eb] = 33 | ["d2b_ea";"d2b_eb"] 34 | |> List.map intern 35 | [@@warning "-8"] 36 | let mark = bt_profiler.mark 37 | 38 | open Msg_pc_bt 39 | 40 | type btree_op_count = { 41 | mutable find: int; 42 | mutable detach: int; 43 | mutable detach_ops: int; 44 | } 45 | let btree_op_count = { find=0; detach=0; detach_ops=0} 46 | 47 | let _ : unit = Stdlib.at_exit (fun () -> 48 | Printf.printf "B-tree op count: find=%#d detach=%#d detach_ops=%#d (%s)\n" btree_op_count.find btree_op_count.detach btree_op_count.detach_ops __FILE__) 49 | 50 | let Map_ops_with_ls.{ find; insert; delete; _ } = map_ops_bt 51 | 52 | let rec loop (ops:('k,'v)kvop list) = 53 | (* from_lwt(yield()) >>= fun () -> *) 54 | (* FIXME may want to yield occasionally *) 55 | match ops with 56 | | [] -> return () 57 | | op::ops -> 58 | btree_op_count.detach_ops <- btree_op_count.detach_ops + 1; 59 | (* FIXME more efficient if we dealt with multiple ops eg 60 | insert_many *) 61 | (* NOTE the following do not have callbacks, because they come 62 | from a flush from the pcache (even if the LRU user 63 | requested sync... the sync write is to the pcache) *) 64 | match op with 65 | | Insert(k,v) -> 66 | insert ~k ~v >>= fun () -> 67 | loop ops 68 | | Delete k -> 69 | delete ~k >>= fun () -> 70 | loop ops 71 | 72 | let rec read_and_dispatch () = 73 | (* from_lwt(yield()) >>= fun () -> *) 74 | mark d2b_ea; 75 | (* FIXME are we worried about the cost of these dequeues? most 76 | of the time they will pause *) 77 | q_pc_bt#dequeue () >>= fun msg -> 78 | mark (-1*d2b_ea); 79 | (* from_lwt(sleep bt_thread_delay) >>= fun () -> (\* FIXME *\) *) 80 | (* Printf.printf "btree_thread dequeued: %s\n%!" "-"; *) 81 | match msg with 82 | | Find(k,callback) -> 83 | btree_op_count.find <- btree_op_count.find + 1; 84 | find ~k >>= fun v -> 85 | async(fun () -> callback v) >>= fun () -> 86 | read_and_dispatch () 87 | | Detach { ops; new_pcache_hd_tl } -> 88 | btree_op_count.detach <- btree_op_count.detach + 1; 89 | loop ops >>= fun () -> 90 | (* FIXME what to do with the new root? maybe nothing for the 91 | time being? *) 92 | (* FIXME what about root pair? *) 93 | sync_bt () >>= fun btree_root -> 94 | (* Printf.printf 95 | "New root pair: pcache_root=%d, bt_root=%d\n%!" 96 | (B.to_int new_pcache_root) 97 | (ptr |> B.to_int);*) 98 | let r = new_pcache_hd_tl in 99 | let pcache_origin = Pl_origin.{hd=r;tl=r;blk_len=1} in 100 | write_origin { pcache_origin; btree_root } >>= fun () -> 101 | read_and_dispatch () 102 | end) 103 | in 104 | object 105 | method start_btree_thread () = async read_and_dispatch 106 | end 107 | 108 | let _ = make_btree_thread 109 | end 110 | 111 | 112 | (** Lwt example *) 113 | module Example = Make(struct include Shared_ctxt end) 114 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name tjr_kv) 3 | (public_name tjr_kv) 4 | (flags (:standard -open Tjr_monad -open Tjr_profile -open Tjr_fs_shared 5 | -open Tjr_btree -open Tjr_btree.Btree_intf 6 | -open Tjr_plist 7 | -open Tjr_pcache ; -open Tjr_pcache_example 8 | -open Tjr_mem_queue -open Tjr_lru_cache )) 9 | (preprocess (pps ppx_deriving_yojson ppx_jane )) 10 | (preprocessor_deps (file "kv_config_optcomp.ml")) 11 | (libraries tjr_btree ; no tjr_btree_examples 12 | tjr_pcache ; tjr_pcache_examples 13 | tjr_lru_cache tjr_mem_queue ppx_jane)) 14 | 15 | (include_subdirs unqualified) 16 | 17 | -------------------------------------------------------------------------------- /src/kv_config_optcomp.ml: -------------------------------------------------------------------------------- 1 | [%%define PROFILING_ENABLED false] 2 | 3 | -------------------------------------------------------------------------------- /src/kv_config_profilers.ml: -------------------------------------------------------------------------------- 1 | [%%import "kv_config_optcomp.ml"] 2 | 3 | module Internal : sig 4 | val lru_profiler : int profiler 5 | val pcache_profiler: int profiler 6 | val bt_profiler : int profiler 7 | end = struct 8 | [%%if PROFILING_ENABLED] 9 | let lru_profiler = make_profiler ~print_header:"kv lru profiler" () 10 | let pcache_profiler = make_profiler ~print_header:"kv pcache profiler" () 11 | let bt_profiler = make_profiler ~print_header:"kv bt profiler" () 12 | [%%else] 13 | let d = dummy_profiler 14 | let lru_profiler = d 15 | let pcache_profiler = d 16 | let bt_profiler = d 17 | [%%endif] 18 | end 19 | 20 | include Internal 21 | 22 | -------------------------------------------------------------------------------- /src/kv_config_runtime.ml: -------------------------------------------------------------------------------- 1 | module Config_type = struct 2 | type config = { 3 | tst_thrd_yld_its : int; (** iters between yields *) 4 | 5 | tst_thrd_dly_its : int; (** iters before maybe sleeping *) 6 | 7 | tst_thrd_dly : float; (** secs to delay, proportionality cst *) 8 | lru_max_size : int; 9 | lru_evict_count : int; 10 | filename : string; (** store filename *) 11 | pcache_blocks_limit : int; (** max blocks before detaching *) 12 | } [@@deriving yojson] 13 | end 14 | include Config_type 15 | 16 | module S = struct 17 | include Config_type 18 | let default_config = Some { 19 | tst_thrd_yld_its = 1000; 20 | tst_thrd_dly_its = 200; 21 | tst_thrd_dly = (1e-6); 22 | lru_max_size = 2000; 23 | lru_evict_count = 1500; 24 | filename = "kv.store"; 25 | pcache_blocks_limit = 100; 26 | } 27 | let filename="kv_config.json" 28 | end 29 | 30 | include Tjr_config.Make(S) 31 | 32 | [%%import "kv_config_optcomp.ml"] 33 | [%%if PROFILING_ENABLED] 34 | let _ : unit = Printf.printf "Profiling enabled (kv/%s)\n" __FILE__ 35 | let profiling_enabled = true 36 | [%%else] 37 | let profiling_enabled = false 38 | [%%endif] 39 | -------------------------------------------------------------------------------- /src/kv_intf.ml: -------------------------------------------------------------------------------- 1 | (** Main types *) 2 | 3 | module Btree_ops = struct 4 | (* FIXME also include "batch" op *) 5 | 6 | (** The operations supported by the B-tree. The B-tree should be 7 | uncached. *) 8 | (* $(PIPE2SH("""sed -n '/type[ ].*btree_ops = /,/}/p' >GEN.btree_ops.ml_""")) *) 9 | type ('k,'v,'blk_id,'t) btree_ops = { 10 | find : 'k -> ('v option,'t)m; 11 | insert : 'k -> 'v -> (unit,'t)m; 12 | delete : 'k -> (unit,'t)m; 13 | get_root : unit -> ('blk_id,'t)m; 14 | } 15 | type ('k,'v,'blk_id,'t) t = ('k,'v,'blk_id,'t) btree_ops 16 | end 17 | 18 | 19 | module Msg_pc_bt = struct 20 | (** The type of messages sent from the pcache to the B-tree. 21 | 22 | This is a callback-oriented interface, with operations [find] and 23 | [detach] (to handle a list of operations... assumed to come from a 24 | detach operation via a map ie no duplicate keys) 25 | 26 | *) 27 | open Kvop 28 | (* open Blk_id_as_int *) 29 | 30 | (* $(PIPE2SH("""sed -n '/type[ ].*pc_bt_msg = /,/}/p' >GEN.pc_bt_msg.ml_""")) *) 31 | type ('k,'v,'blk_id,'t) pc_bt_msg = 32 | | Find of 'k * ('v option -> (unit,'t) m) 33 | | Detach of { 34 | ops: ('k,'v) kvop list; 35 | new_pcache_hd_tl: 'blk_id (* the pointer to the pcache of length 1 *) 36 | } 37 | end 38 | 39 | module Msg_lru_pc = struct 40 | open Lru_msg (* otherwise error about kinds, which is really about 41 | the constructors not being recognized *) 42 | 43 | (* $(PIPE2SH("""sed -n '/type[ ].*lru_pc_msg = /,/Evictees/p' >GEN.lru_pc_msg.ml_""")) *) 44 | type ('k,'v,'t) lru_pc_msg = ('k,'v,'t) lru_msg 45 | = 46 | | Insert of 'k*'v*(unit -> (unit,'t)m) 47 | | Delete of 'k*(unit -> (unit,'t)m) 48 | | Find of 'k * ('v option -> (unit,'t)m) 49 | | Evictees of ('k,'v)kvop list 50 | | Sync of (unit -> (unit,'t)m) 51 | 52 | (** Debug for int,int *) 53 | let msg2string = 54 | function 55 | | Insert(k,v,_) -> Printf.sprintf "Insert(%d,%d)" k v 56 | | Delete(k,_) -> Printf.sprintf "Delete(%d)" k 57 | | Find(k,_) -> Printf.sprintf "Find(%d)" k 58 | | Evictees es -> Printf.sprintf "Evictees(len=%d)" (List.length es) 59 | | Sync _ -> "Sync" 60 | 61 | end 62 | 63 | 64 | (** {2 Messages} *) 65 | 66 | open Blk_id_as_int 67 | 68 | type ('k,'v,'t) pc_bt_msg = ('k,'v,blk_id,'t) Msg_pc_bt.pc_bt_msg 69 | 70 | class type ['k,'v,'t] q_pc_bt = object 71 | method enqueue: ('k,'v,'t) pc_bt_msg -> (unit,'t)m 72 | method dequeue: unit -> (('k,'v,'t) pc_bt_msg,'t)m 73 | method len: unit -> int 74 | end 75 | 76 | type ('k,'v,'t) lru_pc_msg = ('k,'v,'t) Msg_lru_pc.lru_pc_msg 77 | 78 | class type ['k,'v,'t] q_lru_pc = object 79 | method enqueue: ('k,'v,'t) lru_pc_msg -> (unit,'t)m 80 | method dequeue: unit -> (('k,'v,'t) lru_pc_msg,'t)m 81 | method len: unit -> int 82 | end 83 | 84 | 85 | 86 | 87 | 88 | (** {2 Factory} *) 89 | 90 | (* FIXME for general use -- where there are many kv stores -- we want 91 | to incorporate a usedlist, and store the usedlist origin with the 92 | other origins *) 93 | 94 | 95 | (* $(PIPE2SH("""sed -n '/type[ ].*kv_store[ ]/,/^>/p' >GEN.kv_store.ml_""")) *) 96 | type ('k,'v,'blk_id,'t,'kvop_map) kv_store = < 97 | btree_thread : < start_btree_thread : unit -> (unit, 't)m >; 98 | lru_ops : ('k, 'v, 't) mt_ops; 99 | pcache_thread : < start_pcache_thread : unit -> (unit, 't)m >; 100 | pcache_ops : ('k,'v,'blk_id,'kvop_map,'t)pcache_ops; 101 | q_lru_pc : ('k, 'v, 't) q_lru_pc; 102 | q_pc_bt : ('k, 'v, 't) q_pc_bt; 103 | origin : 'blk_id; 104 | (* write_origin : unit -> (unit,'t)m; *) 105 | > 106 | (** NOTE the two threads have to be started before various operations 107 | can complete; the lru_ops are the operations exposed to the user *) 108 | (* root_man : (rt_blk, t) root_man; *) 109 | (* rt_blk : rt_blk *) 110 | (* min_free : min_free; *) 111 | (* blk_alloc : (r, t) blk_allocator_ops; *) 112 | 113 | 114 | type ('k,'v,'blk_id,'blk,'t,'params,'kvop_map) kv_factory = < 115 | (* pcache_factory : ('k,'v,'blk_id,'blk,'kvop_map,'t) pcache_factory; *) 116 | 117 | (* FIXME add origin writing *) 118 | 119 | with_: 120 | blk_dev_ops : ('blk_id,'blk,'t)blk_dev_ops -> 121 | barrier : (unit -> (unit,'t)m) -> 122 | sync : (unit -> (unit,'t)m) -> 123 | freelist_ops : ('blk_id,'t)freelist_ops_af -> 124 | params : 'params -> 125 | < 126 | create: unit -> ( ('k,'v,'blk_id,'t,'kvop_map)kv_store,'t)m; 127 | (** Create an empty kv store, initializing blks etc *) 128 | 129 | restore: blk_id -> ( ('k,'v,'blk_id,'t,'kvop_map)kv_store,'t)m; 130 | (** Restore from disk *) 131 | > 132 | > 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/kv_store_with_lru.ml: -------------------------------------------------------------------------------- 1 | (** A KV store with an LRU cache frontend. *) 2 | 3 | 4 | (** {2 Architecture} 5 | 6 | {%html: 7 | 8 | 9 | 10 | %} 11 | 12 | 13 | We construct the following... 14 | 15 | 16 | {3 Queues: q_lru_pc and q_pc_bt} 17 | 18 | - q_lru_pc, a msg queue from the lru to the pcache 19 | - q_pc_bt, a msg queue from the pcache to the B-tree 20 | 21 | 22 | {3 Blk allocator} 23 | 24 | - provides blk alloc/free 25 | 26 | 27 | {3 LRU} 28 | 29 | - lru_ops, concurrent-safe map operations 30 | 31 | 32 | {3 Pcache } 33 | 34 | - pcache_thread, which takes msgs from q_pc_bt and executes against the pcache; also performs detach occasionally and enqueues messages to q_pc_bt 35 | 36 | 37 | {3 B-tree} 38 | 39 | - btree_thread, listening to q_pc_bt and executing operations against the B-tree 40 | 41 | 42 | {3 Root manager} 43 | 44 | - root_man, which is responsible for persisting the roots for the B-tree and the pcache 45 | 46 | *) 47 | 48 | open Kv_intf 49 | 50 | let runtime_config = Kv_config_runtime.config 51 | 52 | type params = < 53 | lru_params : < evict_count : int; max_size : int;>; 54 | pcache_blocks_limit : int; 55 | > 56 | 57 | module type S = sig 58 | type t = lwt 59 | val monad_ops : t monad_ops 60 | type mutex 61 | type cvar 62 | val mutex_ops : (mutex,cvar,t) mutex_ops 63 | val async : t async 64 | val yield : unit -> (unit,t)m 65 | val event_ops : t event_ops 66 | 67 | type k 68 | type v 69 | type buf = Shared_ctxt.buf 70 | type blk = Shared_ctxt.blk 71 | type blk_id = Shared_ctxt.r 72 | type r = Shared_ctxt.r 73 | 74 | type kvop_map 75 | val pcache_factory : (k, v, r, blk, buf, kvop_map, t) pcache_factory 76 | 77 | type leaf 78 | type node 79 | type dnode 80 | type ls 81 | type wbc 82 | val btree_factory : (k,v,r,t,leaf,node,dnode,ls,blk,wbc) btree_factory 83 | 84 | type lru 85 | val lru_factory : (k,v,lru,t) lru_factory 86 | 87 | val root_manager : (blk_id,blk,t) Root_manager.root_manager 88 | 89 | end 90 | 91 | module Make(S:S) = struct 92 | module S=S 93 | open S 94 | 95 | let kvop_map_ops = pcache_factory#kvop_map_ops 96 | 97 | let ( >>= ) = monad_ops.bind 98 | 99 | let return = monad_ops.return 100 | 101 | module Btree_thread = Btree_thread.Make(S) 102 | 103 | module Pcache_thread = Pcache_thread.Make(S) 104 | 105 | module With_(A:sig 106 | val blk_dev_ops : (blk_id,blk,t) blk_dev_ops 107 | val barrier : unit -> (unit,t)m 108 | val sync : unit -> (unit,t)m 109 | val freelist_ops : (blk_id,t) freelist_ops_af 110 | val params : params 111 | end) 112 | = struct 113 | open A 114 | 115 | let root_man = root_manager#with_ ~blk_dev_ops 116 | 117 | let blk_alloc = freelist_ops 118 | 119 | let pc_with = pcache_factory#with_ ~blk_dev_ops ~barrier ~freelist_ops 120 | 121 | (* 122 | - (b_origin): pcache origin and btree root 123 | - (b_pcache): empty pcache 124 | - (b_empty_btree): empty btree 125 | *) 126 | let lru_params = params#lru_params 127 | let pcache_blocks_limit = params#pcache_blocks_limit 128 | 129 | let create () : (_ kv_store,_)m = 130 | 131 | (* queues *) 132 | let q_lru_pc : (k,v,_) q_lru_pc = Tjr_mem_queue.With_lwt.make_as_object () in 133 | let q_pc_bt : (k,v,_) q_pc_bt = Tjr_mem_queue.With_lwt.make_as_object () in 134 | 135 | (* btree first *) 136 | freelist_ops.blk_alloc () >>= fun b_empty_btree -> 137 | btree_factory#write_empty_leaf ~blk_dev_ops ~blk_id:b_empty_btree >>= fun () -> 138 | btree_factory#uncached 139 | ~blk_dev_ops ~blk_alloc ~btree_root:(`A b_empty_btree) |> fun btree_o -> 140 | 141 | (* then pcache *) 142 | pc_with#create () >>= fun pcache_ops -> 143 | pcache_ops.get_origin () >>= fun pcache_origin -> 144 | 145 | (* then the origin *) 146 | let open Root_manager in 147 | freelist_ops.blk_alloc () >>= fun b_origin -> 148 | let write_origin origin = root_man#write_origin ~blk_id:b_origin ~origin in 149 | let origin = { pcache_origin; btree_root=b_empty_btree } in 150 | write_origin origin >>= fun () -> 151 | 152 | (* then the lru *) 153 | let to_lower msg = q_lru_pc#enqueue msg in 154 | let lru_ref = ref @@ lru_factory#empty lru_params in 155 | (* FIXME the lru has to be locked *) 156 | with_locked_ref ~monad_ops ~mutex_ops lru_ref >>= fun with_state -> 157 | let lru_ops = lru_factory#make_ops ~with_state ~to_lower in 158 | 159 | (* then the btree thread and the pcache thread *) 160 | let btree_thread = Btree_thread.make_btree_thread 161 | ~write_origin 162 | ~q_pc_bt 163 | ~map_ops_bt:btree_o#map_ops_with_ls 164 | ~sync_bt:(fun () -> 165 | (* NOTE we need to sync the blk_dev *) 166 | sync () >>= fun () -> 167 | btree_o#get_btree_root ()) 168 | in 169 | 170 | let pcache_thread = 171 | Pcache_thread.make_pcache_thread 172 | ~kvop_map_ops 173 | ~pcache_blocks_limit 174 | ~pcache_ops 175 | ~q_lru_pc 176 | ~q_pc_bt 177 | in 178 | let _ = pcache_thread in 179 | 180 | let obj : _ kv_store = object 181 | method btree_thread=btree_thread 182 | method pcache_thread=pcache_thread 183 | method pcache_ops=pcache_ops (* raw! not with blks_limit etc *) 184 | method lru_ops=lru_ops 185 | method q_lru_pc=q_lru_pc 186 | method q_pc_bt=q_pc_bt 187 | method origin=b_origin 188 | end 189 | in 190 | return obj 191 | 192 | 193 | let restore b_origin : (_ kv_store,_)m = 194 | (* queues *) 195 | let q_lru_pc : (k,v,_) q_lru_pc = Tjr_mem_queue.With_lwt.make_as_object () in 196 | let q_pc_bt : (k,v,_) q_pc_bt = Tjr_mem_queue.With_lwt.make_as_object () in 197 | 198 | (* origin first *) 199 | root_man#read_origin b_origin >>= fun origin -> 200 | let open Root_manager in 201 | let write_origin origin = root_man#write_origin ~blk_id:b_origin ~origin in 202 | 203 | (* btree *) 204 | btree_factory#uncached 205 | ~blk_dev_ops ~blk_alloc ~btree_root:(`A origin.btree_root) |> fun btree_o -> 206 | 207 | (* then pcache *) 208 | pc_with#restore ~hd:origin.pcache_origin.hd >>= fun pcache_ops -> 209 | 210 | (* then lru *) 211 | let to_lower msg = q_lru_pc#enqueue msg in 212 | let lru_ref = ref @@ lru_factory#empty lru_params in 213 | (* FIXME the lru has to be locked *) 214 | with_locked_ref ~monad_ops ~mutex_ops lru_ref >>= fun with_state -> 215 | let lru_ops = lru_factory#make_ops ~with_state ~to_lower in 216 | 217 | 218 | 219 | (* then the btree thread and the pcache thread *) 220 | let btree_thread = Btree_thread.make_btree_thread 221 | ~write_origin 222 | ~q_pc_bt 223 | ~map_ops_bt:btree_o#map_ops_with_ls 224 | ~sync_bt:(fun () -> 225 | (* NOTE we need to sync the blk_dev *) 226 | sync () >>= fun () -> 227 | btree_o#get_btree_root ()) 228 | in 229 | 230 | let pcache_thread = 231 | Pcache_thread.make_pcache_thread 232 | ~kvop_map_ops 233 | ~pcache_blocks_limit 234 | ~pcache_ops 235 | ~q_lru_pc 236 | ~q_pc_bt 237 | in 238 | let _ = pcache_thread in 239 | 240 | let obj : _ kv_store = object 241 | method btree_thread=btree_thread 242 | method pcache_thread=pcache_thread 243 | method pcache_ops=pcache_ops (* raw, not with limit *) 244 | method lru_ops=lru_ops 245 | method q_lru_pc=q_lru_pc 246 | method q_pc_bt=q_pc_bt 247 | method origin=b_origin 248 | end 249 | in 250 | return obj 251 | 252 | let obj = object 253 | method create=create 254 | method restore=restore 255 | end 256 | 257 | end (* With_ *) 258 | 259 | 260 | let with_ 261 | ~blk_dev_ops 262 | ~barrier 263 | ~sync 264 | ~freelist_ops 265 | ~params 266 | = 267 | let open (struct 268 | module A = With_(struct 269 | let blk_dev_ops=blk_dev_ops 270 | let barrier=barrier 271 | let sync=sync 272 | let freelist_ops=freelist_ops 273 | let params=params 274 | end) 275 | end) 276 | in 277 | A.obj 278 | 279 | let kv_factory : _ kv_factory = object 280 | method with_=with_ 281 | end 282 | 283 | end (* Make *) 284 | 285 | 286 | module Examples = struct 287 | module Int_int = struct 288 | module S1 = Shared_ctxt 289 | module S2 = struct 290 | include S1 291 | type k=int 292 | type v=int 293 | 294 | type kvop_map = Tjr_pcache.Make.Examples.Int_int.kvop_map 295 | let pcache_factory = Tjr_pcache.pcache_examples#for_int_int 296 | 297 | type node = Tjr_btree.Make_6.Examples.Int_int.node 298 | type leaf = Tjr_btree.Make_6.Examples.Int_int.leaf 299 | type dnode = (node, leaf) Isa_btree.dnode 300 | type ls = Tjr_btree.Make_6.Examples.Int_int.ls 301 | type blk = Shared_ctxt.blk 302 | type wbc = Tjr_btree.Make_6.Examples.Int_int.wbc 303 | let btree_factory = Tjr_btree.btree_examples#int_int_factory 304 | 305 | type lru = Tjr_lru_cache.Lru_examples.Int_int.lru 306 | let lru_factory = Tjr_lru_cache.Lru_examples.Int_int.lru_factory 307 | 308 | let root_manager = Root_manager.root_managers#for_lwt_buf 309 | end 310 | 311 | module M = Make(S2) 312 | let kv_factory = M.kv_factory 313 | end 314 | end 315 | 316 | 317 | module Test() = struct 318 | (* open Tjr_monad.With_lwt *) 319 | open Shared_ctxt 320 | 321 | let kv_factory = Examples.Int_int.kv_factory 322 | 323 | let pcache_factory = Tjr_pcache.pcache_examples#for_int_int 324 | let plist_factory = pcache_factory#simple_plist_factory#plist_factory 325 | 326 | let params = 327 | object 328 | method lru_params=object method evict_count=10 method max_size=20 end 329 | method pcache_blocks_limit=2 330 | end 331 | let count = 1000 332 | 333 | (* $(CONFIG("kv_store_with_lru.ml: dont_log")) *) 334 | let dont_log = true 335 | 336 | let test () = 337 | blk_devs#lwt_open_file ~fn:"kv.store" ~create:true ~trunc:true >>= fun bd -> 338 | let blk_dev_ops=bd#blk_dev_ops in 339 | let root_man = Root_manager.root_managers#for_lwt_buf#with_ ~blk_dev_ops in 340 | (* FIXME we should just have create and restore taking a fn *) 341 | shared_freelist#with_ ~fn:"freelist.store" |> fun fl -> 342 | fl#create ~min_free:(B.of_int 0) >>= fun fl -> 343 | let freelist_ops = fl#freelist_ops in 344 | let _ = freelist_ops in 345 | let barrier = (fun () -> return ()) in 346 | let sync = barrier in 347 | kv_factory#with_ 348 | ~blk_dev_ops 349 | ~barrier 350 | ~sync 351 | (* $(FIXME("""standardize on blk_alloc and blk_free; alter shared_freelist """)) *) 352 | ~freelist_ops:{blk_alloc=freelist_ops.Shared_freelist.alloc;blk_free=freelist_ops.free} 353 | ~params 354 | |> fun kv -> 355 | kv#create () >>= fun kv_store -> 356 | Printf.printf "%s: created from disk\n%!" __FILE__; 357 | let lru_ops = kv_store#lru_ops in 358 | let { mt_find; mt_insert; mt_delete; mt_sync_all_keys; _ } = lru_ops in 359 | (* NOTE kv_store.origin is the blk to restore from *) 360 | (* $(FIXME("""start threads automatically rather than requiring explicit start""")) *) 361 | Printf.printf "%s: starting btree thread\n%!" __FILE__; 362 | kv_store#btree_thread#start_btree_thread () >>= fun () -> 363 | Printf.printf "%s: started btree thread\n%!" __FILE__; 364 | kv_store#pcache_thread#start_pcache_thread () >>= fun () -> 365 | Printf.printf "%s: started pcache thread\n%!" __FILE__; 366 | (* perform some operations *) 367 | Printf.printf "%s: performing operations\n%!" __FILE__; 368 | 0 |> iter_k (fun ~k n -> 369 | match n >= count with 370 | | true -> return () 371 | | false -> 372 | assert(dont_log || (Printf.printf "%s: inserting %d\n%!" __FILE__ n; true)); 373 | (* FIXME mt_insert should take an optional persist mode *) 374 | mt_insert n (2*n) >>= fun () -> 375 | k (n+1)) >>= fun () -> 376 | (* FIXME need some mechanism to free up all resources for KV store 377 | (GC may not be effective?) *) 378 | Printf.printf "%s: syncing keys\n%!" __FILE__; 379 | mt_sync_all_keys () >>= fun () -> 380 | (* NOTE this should also result in a sync on the pcache *) 381 | 382 | (* root_man#write_origin ~blk_id:(B.of_int 0) ~origin:Root_manager.{} *) 383 | Printf.printf "%s: closing\n%!" __FILE__; 384 | fl#close () >>= fun () -> 385 | (* Printf.printf "%s: pausing\n%!" __FILE__; *) 386 | (* NOTE this shouldn't be necessary if the sync_all_keys blocks till complete *) 387 | (* Tjr_monad.With_lwt.(from_lwt @@ sleep 2.0) >>= fun () -> *) 388 | (* FIXME expose pcache ops and btree ops in kv_store *) 389 | (* bd#close () >>= fun () -> *) 390 | let blk_id = kv_store#origin in 391 | 392 | 393 | (* print out the contents of the pcache, for debugging *) 394 | root_man#read_origin blk_id >>= fun origin -> 395 | Printf.printf "%s: read origin %d %d\n%!" __FILE__ (origin.pcache_origin.hd|>B.to_int) (origin.btree_root|>B.to_int); 396 | let plf = plist_factory#with_blk_dev_ops ~blk_dev_ops ~barrier in 397 | 398 | plf#init#read_from_hd origin.pcache_origin.hd >>= fun xs -> 399 | assert(dont_log || ( 400 | xs |> List.map (fun (xs,nxt) -> xs) |> List.concat |> 401 | List.map Kvop.ii_op2s |> String.concat "," |> fun s -> 402 | Printf.printf "\n%s: plist is %s\n\n%!" __FILE__ s; true)); 403 | 404 | kv#restore blk_id >>= fun kv2 -> 405 | kv2#btree_thread#start_btree_thread () >>= fun () -> 406 | kv2#pcache_thread#start_pcache_thread () >>= fun () -> 407 | Printf.printf "%s: restored from disk\n%!" __FILE__; 408 | (* FIXME check the contents of the restored store *) 409 | 0 |> iter_k (fun ~k n -> 410 | match n >= count with 411 | | true -> return () 412 | | false -> 413 | assert(dont_log || ( 414 | Printf.printf "%s: finding %d\n%!" __FILE__ n; true)); 415 | (* FIXME mt_insert should take an optional persist mode *) 416 | kv2#lru_ops.mt_find n >>= fun v -> 417 | assert( 418 | let b1 = v<>None in 419 | let b2 = b1 && (dest_Some v = 2*n) in 420 | match b1&&b2 with 421 | | true -> true 422 | | false -> 423 | Printf.printf "%s: find did not return the correct result: %d %b %b\n%!" 424 | __FILE__ n b1 b2; 425 | false); 426 | k (n+1)) >>= fun () -> 427 | (* $(FIXME("""before we close, we must ensure that all the 428 | threads have become quiescent (see BUG 2020-08-04_10:48:30m 429 | where something attempts to access the blk_dev after close); 430 | this sleep seems to allow time for everything to quiesce, but 431 | it would be better if we had some explicit way to do this eg 432 | via a "sync_origin" from lry that traverses the entire suite of 433 | components and finally writes the origin""")) *) 434 | Tjr_monad.With_lwt.(from_lwt @@ sleep 0.5) >>= fun () -> 435 | (* NOTE we should also sync the freelist if this wasn't just test code *) 436 | bd#close () >>= fun () -> 437 | return () 438 | 439 | end 440 | -------------------------------------------------------------------------------- /src/pcache_thread.ml: -------------------------------------------------------------------------------- 1 | (** Pcache worker thread, which takes an existing pcache_ops, wraps it 2 | using blocks limit, and interfaces with the B-tree *) 3 | 4 | (* NOTE specific to lwt; necessary? *) 5 | (* open Tjr_monad.With_lwt *) 6 | 7 | (* open Shared_ctxt *) 8 | open Kv_intf 9 | open Kv_config_profilers 10 | 11 | (* $(CONFIG("pcache_thread.ml: dont_log")) *) 12 | let dont_log = true 13 | 14 | 15 | module P = Pvt_pcache_with_blocks_limit 16 | 17 | module type S = sig 18 | type t 19 | val monad_ops : t monad_ops 20 | val event_ops : t event_ops 21 | val yield : unit -> (unit,t)m 22 | val async : t async 23 | end 24 | 25 | module Make(S:S) = struct 26 | module S = S 27 | open S 28 | 29 | let ( >>= ) = monad_ops.bind 30 | 31 | let return = monad_ops.return 32 | 33 | let make_pcache_thread (type k v ls kvop_map blk_id) 34 | ~(kvop_map_ops:(k,(k,v)kvop,kvop_map)Tjr_map.map_ops) 35 | ~pcache_blocks_limit 36 | ~(pcache_ops: _ pcache_ops) 37 | ~(q_lru_pc: _ q_lru_pc) 38 | ~(q_pc_bt: _ q_pc_bt) 39 | : < start_pcache_thread: unit -> (unit,t)m > 40 | = 41 | let open (struct 42 | 43 | type nonrec pcache_ops = (k,v,blk_id,kvop_map,lwt) pcache_ops 44 | type nonrec pcache_state = (blk_id,kvop_map) Pcache_intf.pcache_state 45 | 46 | let [mk1;mk2;mk3;mk4] = 47 | let file = "pct" in 48 | ["1" ;"2" ;"3" ;"4"] 49 | |> List.map (fun s -> file^"_"^s) 50 | |> List.map intern 51 | [@@warning "-8"] 52 | 53 | let mark = pcache_profiler.mark 54 | 55 | (** Now we fill in the missing components: [bt_find, 56 | bt_handle_detach].*) 57 | 58 | (** NOTE this enqueues a find event on the msg queue, and 59 | constructs a promise that waits for the result *) 60 | let bt_find = fun k -> 61 | event_ops.ev_create () >>= fun ev -> 62 | let callback = fun v -> event_ops.ev_signal ev v in 63 | mark mk1; 64 | q_pc_bt#enqueue (Find(k,callback)) >>= fun () -> 65 | mark (-1*mk1); 66 | event_ops.ev_wait ev 67 | 68 | let bt_handle_detach (detach_info:('k,'v,'blk_id,'kvop_map)Detach_info.t) = 69 | assert(dont_log || ( 70 | Printf.printf "%s: bt_handle_detach start\n%!" __FILE__; true)); 71 | let kv_ops = detach_info.past_map |> kvop_map_ops.bindings |> List.map snd in 72 | mark mk2; 73 | q_pc_bt#enqueue 74 | Msg_pc_bt.(Detach { 75 | ops=kv_ops; 76 | new_pcache_hd_tl=detach_info.current_ptr}) >>= fun _ -> 77 | mark (-1*mk2); 78 | return () 79 | 80 | let _ = bt_handle_detach 81 | 82 | let pcache_op_count = ref 0 83 | 84 | (* debug/info *) 85 | let _ : unit = Stdlib.at_exit (fun () -> 86 | Printf.printf "pcache op count: %#d (%s)\n" (!pcache_op_count) __FILE__) 87 | 88 | let raw_pcache_ops = pcache_ops 89 | 90 | let map_ops_fidi = 91 | P.make_ops 92 | ~monad_ops 93 | ~pcache_ops:raw_pcache_ops 94 | ~pcache_blocks_limit 95 | ~bt_find 96 | ~bt_handle_detach 97 | 98 | let pcache_thread () = 99 | let loop_evictees = 100 | let rec loop es = 101 | yield () >>= fun () -> 102 | (* Printf.printf "pcache_thread, loop_evictees\n%!"; *) 103 | match es with 104 | | [] -> return () 105 | | kvop::es -> 106 | Kvop.(match kvop with 107 | | Insert (k,v) -> 108 | map_ops_fidi.insert k v >>= fun () -> 109 | loop es 110 | | Delete k -> 111 | map_ops_fidi.delete k >>= fun () -> 112 | loop es) 113 | in 114 | loop 115 | in 116 | let rec read_and_dispatch () = 117 | (* FIXME do we need to yield if we are simply dequeueing? *) 118 | (* FIXME why is yield coerced to from_lwt? should be monad-agnostic *) 119 | yield () >>= fun () -> 120 | (* Printf.printf "pcache_thread read_and_dispatch starts\n%!"; *) 121 | mark mk3; 122 | q_lru_pc#dequeue () >>= fun msg -> 123 | mark (-1*mk3); 124 | (* Printf.printf "pcache_thread dequeued: %s\n%!" (Lru'.msg2string msg); *) 125 | (* FIXME the following pause seems to require that the btree 126 | thread makes progress, but of course it cannot since there 127 | are no msgs on the queue *) 128 | (* from_lwt(sleep pcache_thread_delay) >>= fun () -> (\* FIXME *\) *) 129 | match msg with 130 | | Insert (k,v,callback) -> 131 | incr pcache_op_count; 132 | map_ops_fidi.insert k v >>= fun () -> 133 | async (fun () -> callback ()) >>= fun () -> 134 | read_and_dispatch () 135 | | Delete (k,callback) -> 136 | incr pcache_op_count; 137 | map_ops_fidi.delete k >>= fun () -> 138 | async (fun () -> callback ()) >>= fun () -> 139 | read_and_dispatch () 140 | | Find (k,callback) -> 141 | incr pcache_op_count; 142 | map_ops_fidi.find k >>= fun v -> 143 | async (fun () -> callback v) >>= fun () -> 144 | read_and_dispatch () 145 | | Evictees es -> 146 | pcache_op_count:=!pcache_op_count + List.length es; 147 | mark mk4; 148 | loop_evictees es >>= fun () -> 149 | read_and_dispatch () 150 | | Sync callback -> 151 | assert(dont_log || ( 152 | Printf.printf "%s: pcache received sync\n%!" __FILE__; true)); 153 | pcache_op_count:=!pcache_op_count + 1; 154 | pcache_ops.pcache_sync () >>= fun () -> 155 | callback () 156 | 157 | in 158 | read_and_dispatch () 159 | 160 | (** NOTE currently pcache doesn't sleep at all *) 161 | 162 | let start_pcache_thread () : (unit,t)m = async (fun () -> pcache_thread ()) 163 | end) 164 | in 165 | object 166 | (* method pcache_ops=pcache_ops *) 167 | method start_pcache_thread=start_pcache_thread 168 | end 169 | 170 | let _ = make_pcache_thread 171 | 172 | end 173 | -------------------------------------------------------------------------------- /src/pvt_pcache_with_blocks_limit.ml: -------------------------------------------------------------------------------- 1 | (** This is a pcache wrapper which automatically detaches after 2 | a certain number of blocks; used by pcache_thread *) 3 | 4 | (* $(CONFIG("pvt_pcache_with_blocks_limit.ml: dont_log")) *) 5 | let dont_log = true 6 | 7 | (** fidi = find insert delete insertmany *) 8 | type ('k,'v,'t) map_fidi_ops = { 9 | find : 'k -> ('v option,'t) m; 10 | insert : 'k -> 'v -> (unit,'t) m; 11 | delete : 'k -> (unit,'t)m; 12 | insert_many : 'k -> 'v -> ('k*'v) list -> (('k*'v)list,'t) m 13 | } 14 | 15 | 16 | type ('k,'v,'t) pcache_with_lim_ops = ('k,'v,'t) map_fidi_ops 17 | 18 | (** NOTE bt_find and bt_handle_detach are named for the particular 19 | application we envisage: a persistent cache which hands over to a 20 | btree 21 | 22 | Construct the PCACHE, which uses the pcache_ops and wraps it in a routine which occasionally executes a B-tree roll-up. 23 | 24 | Parameters: 25 | 26 | - [monad_ops] 27 | 28 | - [pcache_ops]: pcache from tjr_pcache, with pcache interface 29 | 30 | - [pcache_blocks_limit]: how many blocks in the pcache before 31 | attempting a roll-up; if the length of pcache is [>=] this limit, we 32 | attempt a roll-up; NOTE that this limit should be >= 2 (if we roll 33 | up with 1 block, then in fact nothing gets rolled up because we roll 34 | up "upto" the current block; not a problem but probably pointless 35 | for testing) 36 | 37 | - [bt_find]: called if key not in pcache map FIXME do we need a 38 | write-through cache here? or just rely on the front-end LRU? FIXME 39 | note that even if a rollup is taking place, we can use the old 40 | B-tree root for the [bt_find] operation. 41 | 42 | - [bt_handle_detach]: called to detach the rollup into another thread; 43 | typically this operation puts a msg on a message queue which is then 44 | received and acted upon by the dedicated rollup thread 45 | 46 | *) 47 | let make_ops 48 | ~monad_ops 49 | ~(pcache_ops:('k,'v,'ptr,'kvop_map,'t) pcache_ops) 50 | ~pcache_blocks_limit 51 | ~bt_find 52 | ~(bt_handle_detach:('k,'v,'ptr,'kvop_map) Detach_info.t -> (unit,'t)m) 53 | = 54 | (* let open Mref_plus in *) 55 | let ( >>= ) = monad_ops.bind in 56 | let return = monad_ops.return in 57 | let pc = pcache_ops in (* persistent cache; another name for pcache *) 58 | let find k = 59 | pc.find k >>= fun v -> 60 | match v with 61 | | None -> bt_find k 62 | | Some v -> return (Some v) 63 | in 64 | let maybe_roll_up () = 65 | pc.blk_len () >>= fun n -> 66 | match n >= pcache_blocks_limit with 67 | | false -> return `No_roll_up_needed 68 | | true -> 69 | assert(dont_log || ( 70 | Printf.printf "%s: pcache_thread, maybe_roll_up\n%!" __FILE__; true)); 71 | pc.detach () >>= fun detach_result -> 72 | bt_handle_detach detach_result >>= fun () -> 73 | return `Ok 74 | in 75 | let insert k v = 76 | pc.insert k v >>= fun () -> 77 | maybe_roll_up () >>= fun _ -> 78 | return () 79 | in 80 | let delete k = 81 | pc.delete k >>= fun () -> 82 | maybe_roll_up () >>= fun _ -> 83 | return () 84 | in 85 | let insert_many k v kvs = 86 | (* FIXME we should do something smarter here *) 87 | insert k v >>= fun () -> return kvs 88 | in 89 | {find;insert;delete;insert_many} 90 | 91 | let _ = make_ops 92 | -------------------------------------------------------------------------------- /src/root_manager.ml: -------------------------------------------------------------------------------- 1 | open Shared_ctxt 2 | 3 | type 'r kv_origin = { pcache_origin: 'r Pl_origin.t; btree_root: 'r }[@@deriving bin_io] 4 | 5 | module Origin = struct 6 | type t = Shared_ctxt.r kv_origin[@@deriving bin_io] 7 | let max_sz = 9*2 8 | end 9 | 10 | let bp_mshlr : _ bp_mshlr = (module Origin) 11 | 12 | let ba_mshlr = bp_mshlrs#ba_mshlr ~mshlr:bp_mshlr ~buf_sz:(Blk_sz.to_int blk_sz) 13 | 14 | module M = (val ba_mshlr) 15 | 16 | (* FIXME this should probably have origin parameterized on blk_id, 17 | like everything else; but at the moment we have only 1 use case *) 18 | type ('blk_id,'blk,'t) root_manager = < 19 | with_: 20 | blk_dev_ops:('blk_id,'blk,'t) blk_dev_ops -> 21 | < 22 | read_origin: 'blk_id -> (Origin.t,'t)m; 23 | write_origin: blk_id:'blk_id -> origin:Origin.t -> (unit,'t)m; 24 | > 25 | > 26 | 27 | (** Example root managers *) 28 | let root_managers = 29 | let for_lwt_buf : ('blk_id,_,_) root_manager = 30 | let with_ ~blk_dev_ops = 31 | let read_origin blk_id = 32 | blk_dev_ops.read ~blk_id >>= fun blk -> 33 | blk |> blk_ops.blk_to_buf |> (fun buf -> buf.ba_buf) |> M.unmarshal |> return 34 | in 35 | let write_origin ~blk_id ~origin = 36 | origin |> M.marshal |> fun ba_buf -> 37 | let blk = {ba_buf;is_valid=true} |> blk_ops.buf_to_blk in 38 | blk_dev_ops.write ~blk_id ~blk 39 | in 40 | object 41 | method read_origin=read_origin 42 | method write_origin=write_origin 43 | end 44 | in 45 | object 46 | method with_=with_ 47 | end 48 | in 49 | object 50 | method for_lwt_buf=for_lwt_buf 51 | end 52 | 53 | 54 | let _ = root_managers 55 | -------------------------------------------------------------------------------- /src/summary.ml: -------------------------------------------------------------------------------- 1 | (** Summary *) 2 | 3 | (** 4 | 5 | {2 Architecture} 6 | 7 | {%html: 8 | 9 | 10 | 11 | 12 | 13 | %} 14 | See {!Kv_store_with_lru} for more details 15 | 16 | 17 | {2 Main interfaces} 18 | 19 | {[ 20 | module type S = sig 21 | type k[@@deriving bin_io] 22 | type v[@@deriving bin_io] 23 | val k_cmp: k -> k -> int 24 | type r = Shared_ctxt.r[@@deriving bin_io] 25 | 26 | val k_size: int 27 | val v_size: int 28 | val r_size: int 29 | end 30 | module type S' = sig 31 | (* NOTE specialized to shared_ctxt *) 32 | type k 33 | type v 34 | val k_cmp: k -> k -> int 35 | val k_mshlr: k bp_mshlr 36 | val v_mshlr: v bp_mshlr 37 | end 38 | 39 | type ('k,'v) kv_store = < 40 | blk_alloc : (r, t) blk_allocator_ops; 41 | btree_thread : < start_btree_thread : unit -> (unit, t)m >; 42 | lru_ops : ('k, 'v, t) mt_ops; 43 | min_free : min_free; 44 | pcache_thread : < start_pcache_thread : unit -> (unit, t)m >; 45 | q_lru_pc : ('k, 'v, t) q_lru_pc; 46 | q_pc_bt : ('k, 'v, t) q_pc_bt; 47 | root_man : (rt_blk, t) root_man; 48 | rt_blk : rt_blk 49 | > 50 | 51 | ]} 52 | 53 | *) 54 | -------------------------------------------------------------------------------- /src/summary.t.ml: -------------------------------------------------------------------------------- 1 | (** Summary *) 2 | 3 | (** 4 | 5 | {2 Architecture} 6 | 7 | {%html: 8 | 9 | 10 | 11 | 12 | 13 | %} 14 | See {!Kv_store_with_lru} for more details 15 | 16 | 17 | {2 Main interfaces} 18 | 19 | {[ 20 | $(INCLUDE("GEN.*.ml_")) 21 | ]} 22 | 23 | *) 24 | -------------------------------------------------------------------------------- /src/tjr_kv.ml: -------------------------------------------------------------------------------- 1 | (** tjr_kv: A key-value store (example instance) *) 2 | 3 | include Summary 4 | 5 | 6 | module Kv_intf = Kv_intf 7 | 8 | 9 | (** {2 Configuration and profilers} *) 10 | 11 | module Kv_config_optcomp = Kv_config_optcomp 12 | module Kv_config_runtime = Kv_config_runtime 13 | module Kv_config_profilers = Kv_config_profilers 14 | 15 | 16 | (* {2 Lwt aux} *) 17 | (* module Lwt_aux = Lwt_aux *) 18 | 19 | 20 | (** {2 Root manager} *) 21 | 22 | module Root_manager = Root_manager 23 | 24 | 25 | (** {2 Btree thread} *) 26 | 27 | module Btree_thread = Btree_thread 28 | 29 | 30 | 31 | (** {2 Pcache thread} *) 32 | 33 | module Pcache_thread = Pcache_thread 34 | 35 | 36 | (** {2 Lru} *) 37 | 38 | module Lru = Lru 39 | 40 | 41 | (** {2 The key-value store} *) 42 | 43 | module Kv_store_with_lru = Kv_store_with_lru 44 | 45 | 46 | 47 | (** {2 Further notes} *) 48 | 49 | (** 50 | 51 | {3 Combining B-tree and pcache roots in a single block} 52 | 53 | One option when syncing the btree+pcache combination would be to write 54 | the pcache roots to disk, and then (in another block) write the 55 | B-tree root. This is fine, but if a crash occurs inbetween, we have 56 | to recover (which isn't difficult, but still adds complexity). As 57 | an alternative, we can write the btree and the pcache roots into 58 | the same block atomically. This means that we don't have to worry 59 | about recovering from a crash (this approach is crash safe by 60 | design). 61 | 62 | *) 63 | -------------------------------------------------------------------------------- /src/x_lru.ml: -------------------------------------------------------------------------------- 1 | (* NOTE this wasn't really doing much FIXME remove 2 | 3 | (** Construct the LRU ops by joining the to the q_lru_pc *) 4 | 5 | open Kv_intf 6 | open Kv_config_profilers 7 | 8 | module type S = sig 9 | type t 10 | val monad_ops : t monad_ops 11 | type k 12 | type v 13 | type lru 14 | val lru_factory : (k,v,lru,t) lru_factory 15 | end 16 | 17 | module Make(S:S) = struct 18 | module S=S 19 | open S 20 | 21 | let ( >>= ) = monad_ops.bind 22 | let return = monad_ops.return 23 | 24 | let l2d_aa = intern "l2d:aa" 25 | let mark = lru_profiler.mark 26 | 27 | (* NOTE with_lru must be locked *) 28 | let make ~with_lru ~q_lru_pc = 29 | let open (struct 30 | 31 | (* let lru_lock = mutex_ops.create_mutex() *) 32 | 33 | (* let _ = lru_lock *) 34 | 35 | let enqueue msg = 36 | return () >>= fun () -> 37 | mark l2d_aa; 38 | q_lru_pc#enqueue msg >>= fun r -> 39 | mark (-1 * l2d_aa); 40 | return r 41 | 42 | let to_lower = enqueue (* NOTE used in lru_callback_ops below FIXME 43 | perhaps rename this type *) 44 | 45 | let lru_ops = 46 | lru_factory#make_ops ~with_state:with_lru ~to_lower 47 | 48 | end) 49 | in 50 | lru_ops 51 | end 52 | 53 | 54 | (* 55 | type mutex 56 | type cvar 57 | val mutex_ops : (mutex,cvar,t) mutex_ops 58 | *) 59 | 60 | 61 | (* 62 | |> fun lru_ops -> 63 | return @@ object 64 | method lru_ops=lru_ops 65 | method lru_ref=lru_ref 66 | end 67 | let lru_ref = ref @@ lru_factory#empty 68 | ~max_size:lru_params#max_size 69 | ~evict_count:lru_params#evict_count 70 | 71 | 72 | let to_return = 73 | with_locked_ref ~monad_ops ~mutex_ops lru_ref >>= fun with_state -> 74 | lru_factory#make_ops ~with_state:with_state ~to_lower |> fun lru_ops -> 75 | return @@ object 76 | method lru_ops=lru_ops 77 | method lru_ref=lru_ref 78 | end 79 | *) 80 | *) 81 | -------------------------------------------------------------------------------- /src/x_lwt_aux.ml_: -------------------------------------------------------------------------------- 1 | (** Lwt support FIXME move to shared? or tjr_monad? *) 2 | 3 | open Tjr_monad.With_lwt 4 | 5 | (* open Tjr_mem_queue.Memq_intf *) 6 | 7 | include struct 8 | 9 | let monad_ops = lwt_monad_ops 10 | 11 | let ( >>= ) = monad_ops.bind 12 | let return = monad_ops.return 13 | 14 | let event_ops = lwt_event_ops 15 | 16 | end 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | (* 25 | 26 | (** {2 In-memory message queue for Lwt} *) 27 | 28 | (** Type for lwt_queue_ops *) 29 | module Lwt_queue = struct 30 | type 'msg lwt_queue_ops = ('msg, (Lwt_mutex.t, unit Lwt_condition.t, 'msg) queue, lwt) memq_ops 31 | end 32 | open Lwt_queue 33 | 34 | (** Construct queue ops *) 35 | let queue_ops () = make_memq_ops ~monad_ops ~mutex_ops 36 | 37 | let _ : unit -> ('a, (Lwt_mutex.t, unit Lwt_condition.t, 'a) queue, lwt) memq_ops = queue_ops 38 | 39 | 40 | (* FIXME maybe avoid the unit arg *) 41 | (** Construct empty queue *) 42 | let empty_queue () = { 43 | q = Queue.create(); 44 | mutex=create_mutex(); 45 | cvar=create_cvar() 46 | } 47 | 48 | (** {2 Construct queues given k,v,blk_id} *) 49 | 50 | module type S = sig 51 | type k 52 | type v 53 | type blk_id 54 | end 55 | 56 | 57 | (** This functor constructs [q_lru_pc] and [q_pc_bt], parameterized by k and v *) 58 | module Make_queues(S:S) : sig 59 | (** Just an abbrev *) 60 | type 'a queue' = (Lwt_mutex.t, unit Lwt_condition.t, 'a) queue 61 | type lru_pc_msg' = (S.k, S.v, lwt) Msg_type.msg 62 | val q_lru_pc : 63 | (lru_pc_msg' queue', lru_pc_msg' lwt_queue_ops) 64 | initial_state_and_ops 65 | type pc_bt_msg' = (S.k, S.v, S.blk_id, lwt) Msg_pc_bt.pc_bt_msg 66 | val q_pc_bt : 67 | (pc_bt_msg' queue', pc_bt_msg' lwt_queue_ops) 68 | initial_state_and_ops 69 | end 70 | = struct 71 | open S 72 | 73 | type 'a queue' = (Lwt_mutex.t, unit Lwt_condition.t, 'a) queue 74 | 75 | (** {2 q_lru_pc} *) 76 | 77 | type lru_pc_msg' = (k,v,lwt) Msg_lru_pc.lru_pc_msg 78 | module Internal2 = struct 79 | let q_lru_pc : 80 | (Lwt_mutex.t,unit Lwt_condition.t, lru_pc_msg') queue 81 | = 82 | (empty_queue ()) 83 | let q_lru_pc_ops : lru_pc_msg' lwt_queue_ops = queue_ops () 84 | end 85 | let q_lru_pc = {initial_state=Internal2.q_lru_pc;ops=Internal2.q_lru_pc_ops} 86 | 87 | (** {2 q_pc_bt } *) 88 | 89 | type pc_bt_msg' = (k,v,S.blk_id,lwt) Msg_pc_bt.pc_bt_msg 90 | module Internal3 = struct 91 | let q_pc_bt : 92 | (Lwt_mutex.t,unit Lwt_condition.t, pc_bt_msg') queue 93 | = 94 | (empty_queue ()) 95 | let q_pc_bt_ops : pc_bt_msg' lwt_queue_ops = queue_ops () 96 | end 97 | let q_pc_bt = {initial_state=Internal3.q_pc_bt; ops=Internal3.q_pc_bt_ops} 98 | end 99 | *) 100 | 101 | let yield () = Lwt.pause () 102 | 103 | let sleep f = Lwt_unix.sleep f 104 | -------------------------------------------------------------------------------- /tjr_kv.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "A Key-Value store for OCaml (part of ImpFS)" 4 | maintainer: ["Tom Ridge "] 5 | authors: ["Tom Ridge "] 6 | homepage: "https://github.com/tomjridge/tjr_kv" 7 | doc: "https://tomjridge.github.io/ocamldocs/" 8 | bug-reports: "https://github.com/tomjridge/tjr_kv/issues" 9 | depends: [ 10 | "dune" {>= "2.0"} 11 | "tjr_btree" 12 | "tjr_lru_cache" 13 | "tjr_mem_queue" 14 | "tjr_pcache" 15 | ] 16 | build: [ 17 | ["dune" "subst"] {pinned} 18 | [ 19 | "dune" 20 | "build" 21 | "-p" 22 | name 23 | "-j" 24 | jobs 25 | "@install" 26 | "@runtest" {with-test} 27 | "@doc" {with-doc} 28 | ] 29 | ] 30 | dev-repo: "git+https://github.com/tomjridge/tjr_kv.git" 31 | --------------------------------------------------------------------------------