Skip to content

Commit b7018e7

Browse files
committed
Merge pull request #1746 from simonjbeaumont/ca-134765-creedence
[xs64bit] CA-134765: Fix race condition in database layer
2 parents 509e3b8 + 3fc58a3 commit b7018e7

File tree

5 files changed

+63
-6
lines changed

5 files changed

+63
-6
lines changed

ocaml/database/db_cache_impl.ml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ module W = Debug.Debugger(struct let name = "db_write" end)
3232
open Db_cache_types
3333
open Db_ref
3434

35+
let fist_delay_read_records_where = ref false
36+
3537
(* Only needed by the DB_ACCESS signature *)
3638
let initialise () = ()
3739

@@ -122,8 +124,7 @@ let read_set_ref t rcd =
122124
and iterates through set-refs [returning (fieldname, ref list) list; where fieldname is the
123125
name of the Set Ref field in tbl; and ref list is the list of foreign keys from related
124126
table with remote-fieldname=objref] *)
125-
let read_record t tblname objref =
126-
let db = get_database t in
127+
let read_record_internal db tblname objref =
127128
let tbl = TableSet.find tblname (Database.tableset db) in
128129
let row = Table.find_exn tblname objref tbl in
129130
let fvlist = Row.fold (fun k _ _ d env -> (k,d)::env) row [] in
@@ -142,6 +143,7 @@ let read_record t tblname objref =
142143
let set_ref = List.map (fun (k, v) ->
143144
k, String_unmarshall_helper.set (fun x -> x) v) set_ref in
144145
(fvlist, set_ref)
146+
let read_record t = read_record_internal (get_database t)
145147

146148
(* Delete row from tbl *)
147149
let delete_row_locked t tblname objref =
@@ -210,8 +212,7 @@ let read_refs t tblname =
210212
Table.fold (fun r _ _ _ acc -> r :: acc) tbl []
211213

212214
(* Return a list of all the refs for which the expression returns true. *)
213-
let find_refs_with_filter t (tblname: string) (expr: Db_filter_types.expr) =
214-
let db = get_database t in
215+
let find_refs_with_filter_internal db (tblname: string) (expr: Db_filter_types.expr) =
215216
let tbl = TableSet.find tblname (Database.tableset db) in
216217
let eval_val row = function
217218
| Db_filter_types.Literal x -> x
@@ -221,10 +222,13 @@ let find_refs_with_filter t (tblname: string) (expr: Db_filter_types.expr) =
221222
if Db_filter.eval_expr (eval_val row) expr
222223
then Row.find Db_names.ref row :: acc else acc
223224
) tbl []
225+
let find_refs_with_filter t = find_refs_with_filter_internal (get_database t)
224226

225227
let read_records_where t tbl expr =
226-
let reqd_refs = find_refs_with_filter t tbl expr in
227-
List.map (fun ref->ref, read_record t tbl ref) reqd_refs
228+
let db = get_database t in
229+
let reqd_refs = find_refs_with_filter_internal db tbl expr in
230+
if !fist_delay_read_records_where then Thread.delay 0.5;
231+
List.map (fun ref->ref, read_record_internal db tbl ref) reqd_refs
228232

229233
let process_structured_field_locked t (key,value) tblname fld objref proc_fn_selector =
230234

ocaml/database/db_cache_impl.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ val stats : Db_ref.t -> (string * int) list
1515

1616
(** [refresh_row context tbl ref] generates a RefreshRow event *)
1717
val refresh_row : Db_ref.t -> string -> string -> unit
18+
19+
(** Used for Test_db_lowlevel *)
20+
val fist_delay_read_records_where : bool ref

ocaml/test/OMakefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ OCAML_OBJS = \
2727
test_common \
2828
test_basic \
2929
test_helpers \
30+
test_db_lowlevel \
3031
test_pool_db_backup \
3132
test_xapi_db_upgrade \
3233
test_vdi_allowed_operations \

ocaml/test/suite.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ let base_suite =
1919
[
2020
Test_basic.test;
2121
Test_helpers.test;
22+
Test_db_lowlevel.test;
2223
Test_pool_db_backup.test;
2324
Test_xapi_db_upgrade.test;
2425
Test_vdi_allowed_operations.test;

ocaml/test/test_db_lowlevel.ml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
(*
2+
* Copyright (C) Citrix Systems Inc.
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License as published
6+
* by the Free Software Foundation; version 2.1 only. with the special
7+
* exception on linking described in file LICENSE.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*)
14+
15+
open OUnit
16+
open Test_common
17+
18+
(* If we delete a record after making a Db.get_all_records call, but before the
19+
* call returns, then Db.get_all_records should return successfully (not throw
20+
* an Db_exn.DBCache_NotFound("missing row",...) exception, and the return
21+
* value should include the deleted record. *)
22+
let test_db_get_all_records_race () =
23+
let __context = make_test_database () in
24+
let (vm_ref: API.ref_VM) = make_vm ~__context () in
25+
26+
Db_cache_impl.fist_delay_read_records_where := true;
27+
28+
(* Kick off the thread which will destroy a VM. *)
29+
let destroyer_thread =
30+
Thread.create (fun self -> Db.VM.destroy ~__context ~self) vm_ref
31+
in
32+
33+
(* Call get_all_records *)
34+
let _ =
35+
try Db.VM.get_all_records ~__context
36+
with Db_exn.DBCache_NotFound("missing row", _, _) ->
37+
assert_failure "Race condition present"
38+
in
39+
Thread.join destroyer_thread
40+
41+
let tear_down () =
42+
Db_cache_impl.fist_delay_read_records_where := false
43+
44+
let test =
45+
"test_db_lowlevel" >:::
46+
[
47+
"test_db_get_all_records_race" >:: (bracket id test_db_get_all_records_race tear_down);
48+
]

0 commit comments

Comments
 (0)