Skip to content

Commit 16c4572

Browse files
author
icristescu
committed
Add a split function to create new chunk file
1 parent ae36632 commit 16c4572

14 files changed

+421
-60
lines changed

CHANGES.md

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
`finished` callback (#2089, @Ngoguey42)
88
- Add `Gc.oldest_live_commit` which returns the key of the commit on which the
99
latest gc was called on. (#2110, @icristescu)
10+
- Add `split` to create a new suffix chunk. Subsequent writes will append to
11+
this chunk until `split` is called again. (#2118, @icristescu)
1012

1113
### Changed
1214

src/irmin-pack/unix/chunked_suffix.ml

+101-12
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ module Make (Io : Io.S) (Errs : Io_errors.S with module Io = Io) = struct
3232
| `Inconsistent_store
3333
| `Read_out_of_bounds ]
3434

35+
type add_new_error =
36+
[ open_error
37+
| Io.close_error
38+
| `Pending_flush
39+
| `File_exists of string
40+
| `Multiple_empty_chunks ]
41+
3542
(** A simple container for chunks. *)
3643
module Inventory : sig
3744
type t
@@ -56,13 +63,24 @@ module Make (Io : Io.S) (Errs : Io_errors.S with module Io = Io) = struct
5663
(t, [> open_error ]) result
5764

5865
val close : t -> (unit, [> Io.close_error | `Pending_flush ]) result
66+
67+
val add_new_appendable :
68+
open_chunk:
69+
(chunk_idx:int ->
70+
is_legacy:bool ->
71+
is_appendable:bool ->
72+
(Ao.t, add_new_error) result) ->
73+
t ->
74+
(unit, [> add_new_error ]) result
75+
76+
val length : t -> int63
5977
end = struct
60-
type t = chunk Array.t
78+
type t = { mutable chunks : chunk Array.t }
6179

6280
exception OpenInventoryError of open_error
6381

64-
let v = Array.init
65-
let appendable t = Array.get t (Array.length t - 1)
82+
let v num create = { chunks = Array.init num create }
83+
let appendable t = Array.get t.chunks (Array.length t.chunks - 1)
6684

6785
let find ~off t =
6886
let open Int63.Syntax in
@@ -72,23 +90,28 @@ module Make (Io : Io.S) (Errs : Io_errors.S with module Io = Io) = struct
7290
let poff = suffix_off_to_chunk_poff c in
7391
Int63.zero <= poff && poff < end_poff
7492
in
75-
match Array.find_opt find t with
93+
match Array.find_opt find t.chunks with
7694
| None -> raise (Errors.Pack_error `Read_out_of_bounds)
7795
| Some c -> (c, suffix_off_to_chunk_poff c)
7896

97+
let end_offset_of_chunk start_offset ao =
98+
let chunk_len = Ao.end_poff ao in
99+
Int63.Syntax.(start_offset + chunk_len)
100+
101+
let is_legacy chunk_idx = chunk_idx = 0
102+
79103
let open_ ~start_idx ~chunk_num ~open_chunk =
80104
let off_acc = ref Int63.zero in
81105
let create_chunk i =
82106
let suffix_off = !off_acc in
83107
let is_appendable = i = chunk_num - 1 in
84108
let chunk_idx = start_idx + i in
85-
let is_legacy = chunk_idx = 0 in
109+
let is_legacy = is_legacy chunk_idx in
86110
let open_result = open_chunk ~chunk_idx ~is_legacy ~is_appendable in
87111
match open_result with
88112
| Error err -> raise (OpenInventoryError err)
89113
| Ok ao ->
90-
let chunk_len = Ao.end_poff ao in
91-
(off_acc := Int63.Syntax.(suffix_off + chunk_len));
114+
off_acc := end_offset_of_chunk suffix_off ao;
92115
{ idx = chunk_idx; suffix_off; ao }
93116
in
94117
try Ok (v chunk_num create_chunk)
@@ -98,17 +121,58 @@ module Make (Io : Io.S) (Errs : Io_errors.S with module Io = Io) = struct
98121
let close t =
99122
(* Close immutable chunks, ignoring errors. *)
100123
let _ =
101-
Array.sub t 0 (Array.length t - 1)
124+
Array.sub t.chunks 0 (Array.length t.chunks - 1)
102125
|> Array.iter @@ fun chunk ->
103126
let _ = Ao.close chunk.ao in
104127
()
105128
in
106129
(* Close appendable chunk and keep error since this
107130
is the one that can have a pending flush. *)
108131
(appendable t).ao |> Ao.close
132+
133+
let wrap_error result =
134+
Result.map_error
135+
(fun err -> (err : add_new_error :> [> add_new_error ]))
136+
result
137+
138+
let reopen_last_chunk ~open_chunk t =
139+
(* Close the previous appendable chunk and reopen as non-appendable. *)
140+
let open Result_syntax in
141+
let ({ idx; ao; suffix_off } as last_chunk) = appendable t in
142+
let is_legacy = is_legacy idx in
143+
(* Compute the suffix_off for the following chunk. *)
144+
let length = end_offset_of_chunk suffix_off ao in
145+
let* () = Ao.close ao in
146+
let* ao =
147+
open_chunk ~chunk_idx:idx ~is_legacy ~is_appendable:false |> wrap_error
148+
in
149+
let pos = Array.length t.chunks - 1 in
150+
t.chunks.(pos) <- { last_chunk with ao };
151+
Ok length
152+
153+
let create_appendable_chunk ~open_chunk t suffix_off =
154+
let open Result_syntax in
155+
let next_id = succ (appendable t).idx in
156+
let* ao =
157+
open_chunk ~chunk_idx:next_id ~is_legacy:false ~is_appendable:true
158+
in
159+
Ok { idx = next_id; suffix_off; ao }
160+
161+
let add_new_appendable ~open_chunk t =
162+
let open Result_syntax in
163+
let* next_suffix_off = reopen_last_chunk ~open_chunk t in
164+
let* chunk =
165+
create_appendable_chunk ~open_chunk t next_suffix_off |> wrap_error
166+
in
167+
t.chunks <- Array.append t.chunks [| chunk |];
168+
Ok ()
169+
170+
let length t =
171+
let open Int63.Syntax in
172+
Array.fold_left (fun sum c -> sum + Ao.end_poff c.ao) Int63.zero t.chunks
109173
end
110174

111-
type t = { inventory : Inventory.t }
175+
type t = { inventory : Inventory.t; root : string; dead_header_size : int }
112176

113177
let chunk_path = Layout.V4.suffix_chunk
114178

@@ -122,7 +186,7 @@ module Make (Io : Io.S) (Errs : Io_errors.S with module Io = Io) = struct
122186
in
123187
let chunk = { idx = chunk_idx; suffix_off = Int63.zero; ao } in
124188
let inventory = Inventory.v 1 (Fun.const chunk) in
125-
{ inventory }
189+
{ inventory; root; dead_header_size = 0 }
126190

127191
(** A module to adjust values when mapping from chunks to append-only files *)
128192
module Ao_shim = struct
@@ -156,7 +220,7 @@ module Make (Io : Io.S) (Errs : Io_errors.S with module Io = Io) = struct
156220
| false -> Ao.open_ro ~path ~end_poff ~dead_header_size
157221
in
158222
let+ inventory = Inventory.open_ ~start_idx ~chunk_num ~open_chunk in
159-
{ inventory }
223+
{ inventory; root; dead_header_size }
160224

161225
let open_ro ~root ~end_poff ~dead_header_size ~start_idx ~chunk_num =
162226
let open Result_syntax in
@@ -168,16 +232,41 @@ module Make (Io : Io.S) (Errs : Io_errors.S with module Io = Io) = struct
168232
Ao.open_ro ~path ~end_poff ~dead_header_size
169233
in
170234
let+ inventory = Inventory.open_ ~start_idx ~chunk_num ~open_chunk in
171-
{ inventory }
235+
{ inventory; root; dead_header_size }
172236

173237
let appendable_ao t = (Inventory.appendable t.inventory).ao
174238
let end_poff t = appendable_ao t |> Ao.end_poff
239+
let length t = Inventory.length t.inventory
175240

176241
let read_exn t ~off ~len buf =
177242
let chunk, poff = Inventory.find ~off t.inventory in
178243
Ao.read_exn chunk.ao ~off:poff ~len buf
179244

180245
let append_exn t s = Ao.append_exn (appendable_ao t) s
246+
247+
let add_chunk ~auto_flush_threshold ~auto_flush_procedure t =
248+
let open Result_syntax in
249+
let* () =
250+
let end_poff = end_poff t in
251+
if Int63.(compare end_poff zero = 0) then Error `Multiple_empty_chunks
252+
else Ok ()
253+
in
254+
let root = t.root in
255+
let dead_header_size = t.dead_header_size in
256+
let open_chunk ~chunk_idx ~is_legacy ~is_appendable =
257+
let path = chunk_path ~root ~chunk_idx in
258+
let* { dead_header_size; end_poff } =
259+
Ao_shim.v ~path ~end_poff:Int63.zero ~dead_header_size ~is_legacy
260+
~is_appendable
261+
in
262+
match is_appendable with
263+
| true ->
264+
Ao.create_rw ~path ~overwrite:true ~auto_flush_threshold
265+
~auto_flush_procedure
266+
| false -> Ao.open_ro ~path ~end_poff ~dead_header_size
267+
in
268+
Inventory.add_new_appendable ~open_chunk t.inventory
269+
181270
let close t = Inventory.close t.inventory
182271
let empty_buffer t = appendable_ao t |> Ao.empty_buffer
183272
let flush t = appendable_ao t |> Ao.flush

src/irmin-pack/unix/chunked_suffix_intf.ml

+14
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ module type S = sig
3939
| `Inconsistent_store
4040
| `Read_out_of_bounds ]
4141

42+
type add_new_error =
43+
[ open_error
44+
| Io.close_error
45+
| `Pending_flush
46+
| `File_exists of string
47+
| `Multiple_empty_chunks ]
48+
4249
val create_rw :
4350
root:string ->
4451
start_idx:int ->
@@ -65,6 +72,12 @@ module type S = sig
6572
chunk_num:int ->
6673
(t, [> open_error ]) result
6774

75+
val add_chunk :
76+
auto_flush_threshold:int ->
77+
auto_flush_procedure:Ao.auto_flush_procedure ->
78+
t ->
79+
(unit, [> add_new_error ]) result
80+
6881
val close : t -> (unit, [> Io.close_error | `Pending_flush ]) result
6982
val empty_buffer : t -> bool
7083
val flush : t -> (unit, [> Io.write_error ]) result
@@ -77,6 +90,7 @@ module type S = sig
7790
Possible new names: [consistency_poff], [persisted_poff].
7891
*)
7992
val end_poff : t -> int63
93+
val length : t -> int63
8094
val read_exn : t -> off:int63 -> len:int -> bytes -> unit
8195
val append_exn : t -> string -> unit
8296

src/irmin-pack/unix/dispatcher.ml

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ module Make (Fm : File_manager.S with module Io = Io.Unix) :
7070
offsets are translated into real ones (i.e. in prefix or suffix offsets). *)
7171
let end_offset t =
7272
let open Int63.Syntax in
73-
Suffix.end_poff (Fm.suffix t.fm) + suffix_start_offset t
73+
Suffix.length (Fm.suffix t.fm) + suffix_start_offset t
7474

7575
module Suffix_arithmetic = struct
7676
(* Adjust the read in suffix, as the global offset [off] is
@@ -319,7 +319,7 @@ module Make (Fm : File_manager.S with module Io = Io.Unix) :
319319
List.rev !preffix_chunks
320320
| None -> []
321321
in
322-
let suffix_end_poff = Fm.Suffix.end_poff (Fm.suffix t.fm) in
322+
let suffix_end_poff = Fm.Suffix.length (Fm.suffix t.fm) in
323323
let suffix_start_offset = suffix_start_offset t in
324324
let get_entry_accessor rem_len location poff =
325325
let accessor =

src/irmin-pack/unix/errors.ml

+3-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ type base_error =
6969
| `Invalid_prefix_read of string
7070
| `Invalid_mapping_read of string
7171
| `Invalid_read_of_gced_object of string
72-
| `Inconsistent_store ]
72+
| `Inconsistent_store
73+
| `Split_forbidden_during_batch
74+
| `Multiple_empty_chunks ]
7375
[@@deriving irmin ~pp]
7476
(** [base_error] is the type of most errors that can occur in a [result], except
7577
for errors that have associated exceptions (see below) and backend-specific

src/irmin-pack/unix/ext.ml

+13
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,18 @@ module Maker (Config : Conf.S) = struct
227227
let fsync t = File_manager.fsync t.fm |> Errs.raise_if_error
228228
let reload t = File_manager.reload t.fm |> Errs.raise_if_error
229229

230+
let split t =
231+
let open Result_syntax in
232+
let readonly = Irmin_pack.Conf.readonly t.config in
233+
let* () = if readonly then Error `Ro_not_allowed else Ok () in
234+
let* () =
235+
if t.during_batch then Error `Split_forbidden_during_batch
236+
else Ok ()
237+
in
238+
File_manager.split t.fm
239+
240+
let split_exn repo = split repo |> Errs.raise_if_error
241+
230242
module Gc = struct
231243
let cancel t =
232244
match t.running_gc with
@@ -525,6 +537,7 @@ module Maker (Config : Conf.S) = struct
525537
let reload = X.Repo.reload
526538
let flush = X.Repo.flush
527539
let fsync = X.Repo.fsync
540+
let split = X.Repo.split_exn
528541

529542
module Gc = struct
530543
type msg = [ `Msg of string ]

0 commit comments

Comments
 (0)