File: moved_while_blocking.ml

package info (click to toggle)
ocaml 5.4.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 44,384 kB
  • sloc: ml: 370,196; ansic: 52,820; sh: 27,419; asm: 5,462; makefile: 3,684; python: 974; awk: 278; javascript: 273; perl: 59; fortran: 21; cs: 9
file content (132 lines) | stat: -rw-r--r-- 4,146 bytes parent folder | download | duplicates (2)
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
(* TEST
 include systhreads;
 hassysthreads;
 {
   bytecode;
 }{
   native;
 }
*)

(* A few triggers, to control timing of events between threads.
   `await a` will wait until after `set a` has been called. *)

let t2_begin = Atomic.make false
let t2_promoting = Atomic.make false
let t2_finish_promote = Atomic.make false
let t2_done = Atomic.make false
let t2_quit = Atomic.make false

(* `await a` waits for the trigger `a` *)
let await a =
  while not (Atomic.get a) do Thread.yield () done

(* `set a` pulls the trigger `a` *)
let set a =
  Atomic.set a true

(* no-alloc printing to stdout *)
let say msg =
  Unix.write_substring Unix.stdout msg 0 (String.length msg)
  |> ignore

(*

The intended sequence of events in this test is as follows:

- thread 1 spawns thread 2 to run thread_fn.

- thread 2 starts thread_fn, waits for t2_begin.

- thread 1 starts a profile, sampling at 100%, which logs allocations and
  has a complex "promote" callback which hands control flow back and
  forth between threads.
- thread 1 allocates a large object (creating tracking entry 0), then
  sets t2_begin and awaits t2_promoting. The alloc_major callback is run at
  some point here, so tracking entry 0 now has no pending callbacks.

- thread 2 wakes on t2_begin.
- thread 2 allocates a small object, a ref cell, on the minor heap. This
  creates tracking entry 1, and runs the alloc_minor callback.
- thread 2 commands a minor collection.
- In the minor collection, the small object is promoted. Tracking entry 1 is
  now marked as promoted and having a runnable callback.
- The promotion callback runs (thread 2 runs this, because thread 1
  is still waiting for t2_promoting). In the promotion callback, t2_promoting
  is set, and then t2_finish_promote is awaited.

- thread 1 wakes on t2_promoting, clears its root, and sets off a full
  major collection which should collect thread 1's large block. The
  after-major-GC function runs, marking tracking entry 0 as deallocated.
- thread 1 then sets t2_finish_promote and awaits t2_done.

- thread 2 wakes on t2_finish_promote, finishes its promotion callback, then
  returns to its main flow of control, clearing the reference to its small
  block, setting t2_done and awaiting t2_quit.

- thread 1 wakes on t2:done, does another full collection, which should
  free the small block from thread 2 and mark its tracking entry for a dealloc
  callback. Then it stops the profile, sets t2_quit, and joins thread 2.

- thread 2 wakes on t2_quit and exits.

- thread 1 joins thread 2 and exits.

Note that the implementation of threads in the bytecode backend
performs some allocations of its own. TODO: update these to use
CAML_DONT_TRACK to avoid statmemprof. For now, I have tweaked the test
so that it doesn't track minor allocations of sizes larger than 1.

*)

let static_ref = ref 0
let global = ref static_ref
let thread_fn () =
  await t2_begin;
  say "T2: alloc\n";
  global := ref 0;
  say "T2: minor GC\n";
  Gc.minor ();
  global := static_ref;
  say "T2: done\n";
  set t2_done;
  await t2_quit

let big = ref [| |]

let fill_big () = big := Array.make 1000 42
  [@@inline never] (* Prevent flambda to move the allocated array in a global
                      root (see #9978). *)
let empty_big () = big := [| |]
  [@@inline never]

let () =
  let th = Thread.create thread_fn () in
  let _:Gc.Memprof.t = Gc.Memprof.(start ~sampling_rate:1.
    { null_tracker with
      alloc_minor = (fun info -> say "    minor alloc\n"; Some ());
      alloc_major = (fun _ -> say "    major alloc\n"; Some "major block\n");
      promote = (fun () ->
        say "    promoting...\n";
        set t2_promoting;
        await t2_finish_promote;
        say "    ...done promoting\n";
        Some "promoted block\n");
      dealloc_major = (fun msg ->
        say "    major dealloc: "; say msg)})
  in
  say "T1: alloc\n";
  fill_big ();
  set t2_begin;
  await t2_promoting;
  say "T1: major GC\n";
  empty_big ();
  Gc.full_major ();
  set t2_finish_promote;
  await t2_done;
  say "T1: major GC\n";
  Gc.full_major ();
  say "T1: done\n";
  Gc.Memprof.stop ();
  set t2_quit;
  Thread.join th