")
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 |
174 | end
175 |
176 | module type KB3 = sig
177 |
178 | end
179 |
180 |
181 | (** argument to B-tree make *)
182 | module type BT1 = sig
183 | include M
184 | include K1
185 | include B1
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 |
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 |
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 |
--------------------------------------------------------------------------------