Skip to content

Commit 7e15f2d

Browse files
committed
Shared cache
1 parent c4186ac commit 7e15f2d

File tree

11 files changed

+300
-183
lines changed

11 files changed

+300
-183
lines changed

lib/btrfs_store.ml

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -173,39 +173,56 @@ let get_cache t name =
173173
Hashtbl.add t.caches name c;
174174
c
175175

176-
let cache ~user t name : (string * (unit -> unit Lwt.t)) Lwt.t =
176+
let cache ?(shared=false) ~user t name : (string * (unit -> unit Lwt.t)) Lwt.t =
177177
let cache = get_cache t name in
178-
Lwt_mutex.with_lock cache.lock @@ fun () ->
179-
let tmp = Path.cache_tmp t t.next name in
180-
t.next <- t.next + 1;
181178
let snapshot = Path.cache t name in
182-
(* Create cache if it doesn't already exist. *)
183-
begin match Os.check_dir snapshot with
184-
| `Missing -> Btrfs.subvolume_create snapshot
185-
| `Present -> Lwt.return_unit
186-
end >>= fun () ->
187-
(* Create writeable clone. *)
188-
let gen = cache.gen in
189-
Btrfs.subvolume_snapshot `RW ~src:snapshot tmp >>= fun () ->
190-
begin match user with
191-
| `Unix { Obuilder_spec.uid; gid } ->
192-
Os.sudo ["chown"; Printf.sprintf "%d:%d" uid gid; tmp]
193-
| `Windows _ -> assert false (* btrfs not supported on Windows*)
194-
end >>= fun () ->
195-
let release () =
179+
if shared then
180+
(* Shared mode: return the actual cache directory, no copy-on-write *)
196181
Lwt_mutex.with_lock cache.lock @@ fun () ->
197-
begin
198-
if cache.gen = gen then (
199-
(* The cache hasn't changed since we cloned it. Update it. *)
200-
(* todo: check if it has actually changed. *)
201-
cache.gen <- cache.gen + 1;
202-
Btrfs.subvolume_delete snapshot >>= fun () ->
203-
Btrfs.subvolume_snapshot `RO ~src:tmp snapshot
204-
) else Lwt.return_unit
182+
(* Create cache if it doesn't already exist. *)
183+
begin match Os.check_dir snapshot with
184+
| `Missing -> Btrfs.subvolume_create snapshot
185+
| `Present -> Lwt.return_unit
205186
end >>= fun () ->
206-
Btrfs.subvolume_delete tmp
207-
in
208-
Lwt.return (tmp, release)
187+
begin match user with
188+
| `Unix { Obuilder_spec.uid; gid } ->
189+
Os.sudo ["chown"; Printf.sprintf "%d:%d" uid gid; snapshot]
190+
| `Windows _ -> assert false (* btrfs not supported on Windows*)
191+
end >>= fun () ->
192+
let release () = Lwt.return_unit in (* No-op for shared caches *)
193+
Lwt.return (snapshot, release)
194+
else
195+
(* Non-shared mode: existing copy-on-write behavior *)
196+
Lwt_mutex.with_lock cache.lock @@ fun () ->
197+
let tmp = Path.cache_tmp t t.next name in
198+
t.next <- t.next + 1;
199+
(* Create cache if it doesn't already exist. *)
200+
begin match Os.check_dir snapshot with
201+
| `Missing -> Btrfs.subvolume_create snapshot
202+
| `Present -> Lwt.return_unit
203+
end >>= fun () ->
204+
(* Create writeable clone. *)
205+
let gen = cache.gen in
206+
Btrfs.subvolume_snapshot `RW ~src:snapshot tmp >>= fun () ->
207+
begin match user with
208+
| `Unix { Obuilder_spec.uid; gid } ->
209+
Os.sudo ["chown"; Printf.sprintf "%d:%d" uid gid; tmp]
210+
| `Windows _ -> assert false (* btrfs not supported on Windows*)
211+
end >>= fun () ->
212+
let release () =
213+
Lwt_mutex.with_lock cache.lock @@ fun () ->
214+
begin
215+
if cache.gen = gen then (
216+
(* The cache hasn't changed since we cloned it. Update it. *)
217+
(* todo: check if it has actually changed. *)
218+
cache.gen <- cache.gen + 1;
219+
Btrfs.subvolume_delete snapshot >>= fun () ->
220+
Btrfs.subvolume_snapshot `RO ~src:tmp snapshot
221+
) else Lwt.return_unit
222+
end >>= fun () ->
223+
Btrfs.subvolume_delete tmp
224+
in
225+
Lwt.return (tmp, release)
209226

210227
let delete_cache t name =
211228
let cache = get_cache t name in

lib/build.ml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,10 @@ module Make (Raw_store : S.STORE) (Sandbox : S.SANDBOX) (Fetch : S.FETCHER) = st
8282
let to_release = ref [] in
8383
Lwt.finalize
8484
(fun () ->
85-
cache |> Lwt_list.map_s (fun { Obuilder_spec.Cache.id; target; buildkit_options = _ } ->
86-
Store.cache ~user t.store id >|= fun (src, release) ->
85+
cache |> Lwt_list.map_s (fun { Obuilder_spec.Cache.id; target; buildkit_options } ->
86+
let shared = List.mem_assoc "sharing" buildkit_options
87+
&& List.assoc "sharing" buildkit_options = "shared" in
88+
Store.cache ~shared ~user t.store id >|= fun (src, release) ->
8789
to_release := release :: !to_release;
8890
{ Config.Mount.ty = `Bind; src; dst = target; readonly = false }
8991
)
@@ -367,8 +369,10 @@ module Make_Docker (Raw_store : S.STORE) = struct
367369
let to_release = ref [] in
368370
Lwt.finalize
369371
(fun () ->
370-
cache |> Lwt_list.map_s (fun { Obuilder_spec.Cache.id; target; buildkit_options = _ } ->
371-
Store.cache ~user t.store id >|= fun (src, release) ->
372+
cache |> Lwt_list.map_s (fun { Obuilder_spec.Cache.id; target; buildkit_options } ->
373+
let shared = List.mem_assoc "sharing" buildkit_options
374+
&& List.assoc "sharing" buildkit_options = "shared" in
375+
Store.cache ~shared ~user t.store id >|= fun (src, release) ->
372376
to_release := release :: !to_release;
373377
{ Config.Mount.ty = `Volume; src; dst = target; readonly = false }
374378
)

lib/db_store.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ module Make (Raw : S.STORE) = struct
134134
let df t = Raw.df t.raw
135135
let root t = Raw.root t.raw
136136
let cache_stats t = t.cache_hit, t.cache_miss
137-
let cache ~user t = Raw.cache ~user t.raw
137+
let cache ?shared ~user t = Raw.cache ?shared ~user t.raw
138138

139139
let delete ?(log=ignore) t id =
140140
let rec aux id =

lib/db_store.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ module Make (Raw : S.STORE) : sig
2929
val cache_stats : t -> int * int
3030

3131
val cache :
32+
?shared:bool ->
3233
user : Obuilder_spec.user ->
3334
t ->
3435
string ->

lib/docker_store.ml

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -162,41 +162,60 @@ let get_cache t name =
162162
Hashtbl.add t.caches name c;
163163
c
164164

165-
let cache ~user t name : (string * (unit -> unit Lwt.t)) Lwt.t =
165+
let cache ?(shared=false) ~user t name : (string * (unit -> unit Lwt.t)) Lwt.t =
166166
let cache = get_cache t name in
167-
Lwt_mutex.with_lock cache.lock @@ fun () ->
168-
let tmp = Cache.cache_tmp t.next name in
169-
t.next <- t.next + 1;
170167
let snapshot = Cache.cache name in
171-
(* Create cache if it doesn't already exist. *)
172-
let* () =
173-
let* exists = Cache.exists snapshot in
174-
if not exists then Cache.create snapshot
175-
else Lwt.return_unit
176-
in
177-
(* Create writeable clone. *)
178-
let gen = cache.gen in
179-
let* () = Cache.snapshot ~src:snapshot tmp in
180-
let+ () = match user with
181-
| `Unix { Obuilder_spec.uid; gid } ->
182-
let* tmp = Docker.Cmd.mount_point tmp in
183-
Os.sudo ["chown"; strf "%d:%d" uid gid; tmp]
184-
| `Windows _ -> Lwt.return_unit (* FIXME: does Windows need special treatment? *)
185-
in
186-
let release () =
168+
if shared then
169+
(* Shared mode: return the actual cache volume, no copy-on-write *)
187170
Lwt_mutex.with_lock cache.lock @@ fun () ->
171+
(* Create cache if it doesn't already exist. *)
188172
let* () =
189-
if cache.gen = gen then (
190-
(* The cache hasn't changed since we cloned it. Update it. *)
191-
(* todo: check if it has actually changed. *)
192-
cache.gen <- cache.gen + 1;
193-
let* () = Cache.delete snapshot in
194-
Cache.snapshot ~src:tmp snapshot
195-
) else Lwt.return_unit
173+
let* exists = Cache.exists snapshot in
174+
if not exists then Cache.create snapshot
175+
else Lwt.return_unit
196176
in
197-
Cache.delete tmp
198-
in
199-
Cache.name tmp, release
177+
let+ () = match user with
178+
| `Unix { Obuilder_spec.uid; gid } ->
179+
let* mp = Docker.Cmd.mount_point snapshot in
180+
Os.sudo ["chown"; strf "%d:%d" uid gid; mp]
181+
| `Windows _ -> Lwt.return_unit (* FIXME: does Windows need special treatment? *)
182+
in
183+
let release () = Lwt.return_unit in (* No-op for shared caches *)
184+
Cache.name snapshot, release
185+
else
186+
(* Non-shared mode: existing snapshot behavior *)
187+
Lwt_mutex.with_lock cache.lock @@ fun () ->
188+
let tmp = Cache.cache_tmp t.next name in
189+
t.next <- t.next + 1;
190+
(* Create cache if it doesn't already exist. *)
191+
let* () =
192+
let* exists = Cache.exists snapshot in
193+
if not exists then Cache.create snapshot
194+
else Lwt.return_unit
195+
in
196+
(* Create writeable clone. *)
197+
let gen = cache.gen in
198+
let* () = Cache.snapshot ~src:snapshot tmp in
199+
let+ () = match user with
200+
| `Unix { Obuilder_spec.uid; gid } ->
201+
let* tmp = Docker.Cmd.mount_point tmp in
202+
Os.sudo ["chown"; strf "%d:%d" uid gid; tmp]
203+
| `Windows _ -> Lwt.return_unit (* FIXME: does Windows need special treatment? *)
204+
in
205+
let release () =
206+
Lwt_mutex.with_lock cache.lock @@ fun () ->
207+
let* () =
208+
if cache.gen = gen then (
209+
(* The cache hasn't changed since we cloned it. Update it. *)
210+
(* todo: check if it has actually changed. *)
211+
cache.gen <- cache.gen + 1;
212+
let* () = Cache.delete snapshot in
213+
Cache.snapshot ~src:tmp snapshot
214+
) else Lwt.return_unit
215+
in
216+
Cache.delete tmp
217+
in
218+
Cache.name tmp, release
200219

201220
let delete_cache t name =
202221
let cache = get_cache t name in

lib/overlayfs_store.ml

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -243,29 +243,41 @@ let get_cache t name =
243243
Hashtbl.add t.caches name c;
244244
c
245245

246-
let cache ~user t name =
246+
let cache ?(shared=false) ~user t name =
247247
let cache = get_cache t name in
248-
Lwt_mutex.with_lock cache.lock @@ fun () ->
249-
let result, work, merged = Path.cache_result t t.next name in
250-
t.next <- t.next + 1;
251248
let master = Path.cache t name in
252-
(* Create cache if it doesn't already exist. *)
253-
(match Os.check_dir master with
254-
| `Missing -> Overlayfs.create ~mode:"1777" ~user [ master ]
255-
| `Present -> Lwt.return_unit)
256-
>>= fun () ->
257-
cache.children <- cache.children + 1;
258-
Overlayfs.create ~mode:"1777" ~user [ result; work; merged ] >>= fun () ->
259-
let lower = String.split_on_char ':' master |> String.concat "\\:" in
260-
Overlayfs.overlay ~lower ~upper:result ~work ~merged >>= fun () ->
261-
let release () =
249+
if shared then
250+
(* Shared mode: return the actual cache directory, no copy-on-write *)
262251
Lwt_mutex.with_lock cache.lock @@ fun () ->
263-
cache.children <- cache.children - 1;
264-
Overlayfs.umount ~merged >>= fun () ->
265-
Overlayfs.cp ~src:result ~dst:master >>= fun () ->
266-
Overlayfs.delete [ result; work; merged ]
267-
in
268-
Lwt.return (merged, release)
252+
(* Create cache if it doesn't already exist. *)
253+
(match Os.check_dir master with
254+
| `Missing -> Overlayfs.create ~mode:"1777" ~user [ master ]
255+
| `Present -> Lwt.return_unit)
256+
>>= fun () ->
257+
let release () = Lwt.return_unit in (* No-op for shared caches *)
258+
Lwt.return (master, release)
259+
else
260+
(* Non-shared mode: existing overlay behavior *)
261+
Lwt_mutex.with_lock cache.lock @@ fun () ->
262+
let result, work, merged = Path.cache_result t t.next name in
263+
t.next <- t.next + 1;
264+
(* Create cache if it doesn't already exist. *)
265+
(match Os.check_dir master with
266+
| `Missing -> Overlayfs.create ~mode:"1777" ~user [ master ]
267+
| `Present -> Lwt.return_unit)
268+
>>= fun () ->
269+
cache.children <- cache.children + 1;
270+
Overlayfs.create ~mode:"1777" ~user [ result; work; merged ] >>= fun () ->
271+
let lower = String.split_on_char ':' master |> String.concat "\\:" in
272+
Overlayfs.overlay ~lower ~upper:result ~work ~merged >>= fun () ->
273+
let release () =
274+
Lwt_mutex.with_lock cache.lock @@ fun () ->
275+
cache.children <- cache.children - 1;
276+
Overlayfs.umount ~merged >>= fun () ->
277+
Overlayfs.cp ~src:result ~dst:master >>= fun () ->
278+
Overlayfs.delete [ result; work; merged ]
279+
in
280+
Lwt.return (merged, release)
269281

270282
let delete_cache t name =
271283
let () = Printf.printf "0\n" in

lib/qemu_store.ml

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -128,31 +128,42 @@ let get_cache t name =
128128
Hashtbl.add t.caches name c;
129129
c
130130

131-
let cache ~user:_ t name : (string * (unit -> unit Lwt.t)) Lwt.t =
131+
let cache ?(shared=false) ~user:_ t name : (string * (unit -> unit Lwt.t)) Lwt.t =
132132
let cache = get_cache t name in
133-
Lwt_mutex.with_lock cache.lock @@ fun () ->
134-
let tmp = Path.cache_tmp t t.next name in
135-
t.next <- t.next + 1;
136133
let master = Path.cache t name in
137-
(* Create cache if it doesn't already exist. *)
138-
(match Os.check_dir master with
139-
| `Missing -> Qemu_img.create master
140-
| `Present -> Lwt.return ()) >>= fun () ->
141-
cache.children <- cache.children + 1;
142-
let () = Os.ensure_dir tmp in
143-
Os.cp ~src:master tmp >>= fun () ->
144-
let release () =
134+
if shared then
135+
(* Shared mode: return the actual cache directory, no copy-on-write *)
136+
Lwt_mutex.with_lock cache.lock @@ fun () ->
137+
(* Create cache if it doesn't already exist. *)
138+
(match Os.check_dir master with
139+
| `Missing -> Qemu_img.create master
140+
| `Present -> Lwt.return ()) >>= fun () ->
141+
let release () = Lwt.return_unit in (* No-op for shared caches *)
142+
Lwt.return (master, release)
143+
else
144+
(* Non-shared mode: existing copy behavior *)
145145
Lwt_mutex.with_lock cache.lock @@ fun () ->
146-
cache.children <- cache.children - 1;
147-
let cache_stat = Unix.stat (Path.image master) in
148-
let tmp_stat = Unix.stat (Path.image tmp) in
149-
(if tmp_stat.st_size > cache_stat.st_size then
150-
Os.cp ~src:tmp master
151-
else
152-
Lwt.return ()) >>= fun () ->
153-
Os.rm ~directory:tmp
154-
in
155-
Lwt.return (tmp, release)
146+
let tmp = Path.cache_tmp t t.next name in
147+
t.next <- t.next + 1;
148+
(* Create cache if it doesn't already exist. *)
149+
(match Os.check_dir master with
150+
| `Missing -> Qemu_img.create master
151+
| `Present -> Lwt.return ()) >>= fun () ->
152+
cache.children <- cache.children + 1;
153+
let () = Os.ensure_dir tmp in
154+
Os.cp ~src:master tmp >>= fun () ->
155+
let release () =
156+
Lwt_mutex.with_lock cache.lock @@ fun () ->
157+
cache.children <- cache.children - 1;
158+
let cache_stat = Unix.stat (Path.image master) in
159+
let tmp_stat = Unix.stat (Path.image tmp) in
160+
(if tmp_stat.st_size > cache_stat.st_size then
161+
Os.cp ~src:tmp master
162+
else
163+
Lwt.return ()) >>= fun () ->
164+
Os.rm ~directory:tmp
165+
in
166+
Lwt.return (tmp, release)
156167

157168
let delete_cache t name =
158169
let cache = get_cache t name in

0 commit comments

Comments
 (0)