|
1 | | -(* TSan stress suite. |
| 1 | +(* TSan stress suite — dispatcher. |
2 | 2 |
|
3 | | - Dispatcher for race-hunting scenarios. Each scenario targets a mutable |
4 | | - state hotspot that the standard test suite does not exercise across |
5 | | - domains. Scenarios are added incrementally. |
| 3 | + Each scenario targets a mutable-state hotspot that the standard test |
| 4 | + suite does not exercise across domains. Iteration count is driven by |
| 5 | + [IRMIN_TSAN_STRESS_ITER] (default 100). |
6 | 6 |
|
7 | | - Iteration count: [IRMIN_TSAN_STRESS_ITER] env var (default 100). *) |
| 7 | + Scenarios and expected outcomes under TSan: |
| 8 | +
|
| 9 | + - mem: clean data-race warning at irmin_mem.ml:51 (Hashtbl.add in |
| 10 | + the global cache) and on the shared KMap mutable. |
| 11 | + - watch: clean data-race warning at watch.ml:33 (listen_dir_hook |
| 12 | + ref assignment). |
| 13 | + - ao: SEGV — concurrent Buffer.add_string corrupts the buffer |
| 14 | + fast enough that TSan's signal handler fires before the |
| 15 | + race warning is written. The SEGV itself is evidence of |
| 16 | + the race; a clean warning would need finer-grained access. |
| 17 | + - dict: SEGV — same pattern as ao, via the two unguarded Hashtbl.t |
| 18 | + caches plus the append path through Ao. |
| 19 | + - fs: TSan "nested bug, aborting" — the race interacts with Eio |
| 20 | + scheduler/pool internals in a way the sanitizer can't |
| 21 | + unwind. Surfaces the problem but not a specific site. |
| 22 | +
|
| 23 | + Because ao/dict/fs crash, each scenario is run in its own process by |
| 24 | + the @tsan-stress dune alias so one crash does not hide the others. |
| 25 | +
|
| 26 | + Usage: |
| 27 | + main.exe run all scenarios |
| 28 | + main.exe all same |
| 29 | + main.exe <name> run one scenario (ao|dict|mem|watch|fs) *) |
8 | 30 |
|
9 | 31 | let iter_count = |
10 | 32 | match Sys.getenv_opt "IRMIN_TSAN_STRESS_ITER" with |
11 | 33 | | Some s -> int_of_string s |
12 | 34 | | None -> 100 |
13 | 35 |
|
14 | | -let scenarios : (string * (iter:int -> unit)) list = [] |
| 36 | +type env = Eio_unix.Stdenv.base |
| 37 | + |
| 38 | +let scenarios : (string * (env:env -> iter:int -> unit)) list = |
| 39 | + [ |
| 40 | + ("ao", Stress_ao_buf.run); |
| 41 | + ("dict", Stress_dict.run); |
| 42 | + ("mem", Stress_mem_cache.run); |
| 43 | + ("watch", Stress_watch.run); |
| 44 | + ("fs", Stress_fs_pool.run); |
| 45 | + ] |
15 | 46 |
|
16 | | -let run_all () = |
17 | | - List.iter |
18 | | - (fun (name, fn) -> |
19 | | - Printf.printf "tsan-stress: %s (iter=%d)\n%!" name iter_count; |
20 | | - fn ~iter:iter_count) |
21 | | - scenarios |
| 47 | +let run_one ~env (name, fn) = |
| 48 | + Printf.printf "tsan-stress: %s (iter=%d)\n%!" name iter_count; |
| 49 | + fn ~env ~iter:iter_count |
22 | 50 |
|
23 | 51 | let () = |
24 | 52 | let which = |
25 | | - if Array.length Sys.argv >= 2 then Sys.argv.(1) else "all" |
| 53 | + match Sys.argv with |
| 54 | + | [| _ |] | [| _; "all" |] -> `All |
| 55 | + | [| _; name |] -> `One name |
| 56 | + | _ -> |
| 57 | + prerr_endline "usage: main.exe [all|ao|dict|mem|watch|fs]"; |
| 58 | + exit 2 |
26 | 59 | in |
| 60 | + Eio_main.run @@ fun env -> |
27 | 61 | match which with |
28 | | - | "all" -> run_all () |
29 | | - | name -> ( |
| 62 | + | `All -> List.iter (run_one ~env) scenarios |
| 63 | + | `One name -> ( |
30 | 64 | match List.assoc_opt name scenarios with |
31 | | - | Some fn -> fn ~iter:iter_count |
| 65 | + | Some fn -> run_one ~env (name, fn) |
32 | 66 | | None -> |
33 | 67 | Printf.eprintf "tsan-stress: unknown scenario %S\n%!" name; |
34 | 68 | exit 2) |
0 commit comments