(* TEST *) (* Tests that an exception in the alloc_minor callback, during a combined allocation, causes already-run allocation callbacks to be reflected by deallocation callbacks. *) exception MyExc of string module MP = Gc.Memprof (* Similar infrastructure to stop_start_in_callback test *) (* We need sets of 3-tuples of integers *) module Int3Tuples = struct type t = int * int * int let compare (x0,y0,z0) (x1,y1,z1) = match Stdlib.compare x0 x1 with | 0 -> (match Stdlib.compare y0 y1 with | 0 -> Stdlib.compare z0 z1 | c -> c) | c -> c end module AllocSet = Set.Make(Int3Tuples) (* A combined 7-block 33-word allocation *) let[@inline never] f33 n = ((n, n, (n, n, n, (n,n,n,n,n))), (n, n, (n, n, n, (n,n,n,n,0)))) (* Raise exceptions from allocation callbacks. In the native code backend, we have combined allocations. If a single allocation callback from a combined allocation raises an exception, none of the blocks in that combined allocation are actually allocated. However, some allocation callbacks may have already been called, before the exception is raised, so statmemprof causes their deallocation callbacks also to be called, so that allocation and deallocation callbacks can be matched up. In the bytecode backend, there are no combined allocations, so these special cases don't apply: allocation callbacks called before the one which raises an exception reflect actual allocations which happened at that time, so statmemprof doesn't have to fake corresponding deallocations. *) let raise_in_alloc () = let n_alloc = ref 0 in (* number of allocations in current profile *) let n_prof = ref 0 in (* number of profiles *) let n_exc = ref 0 in (* number of exceptions handled *) let excs = ref AllocSet.empty in (* sets of (profile count, allocation count, size), for each operation *) let allocs = ref AllocSet.empty in let deallocs = ref AllocSet.empty in let record s (p, a, sz) = s := AllocSet.add (p,a,sz) (!s) in let dealloc_minor minor = (record deallocs minor; ()) in let dealloc_major major = (record deallocs major; ()) in let alloc_minor (info:MP.allocation) = (incr n_alloc; let p = !n_prof in let a = !n_alloc in let sz = info.size + 1 in (* add 1 for header word *) record allocs (p,a,sz); (* stop profile N after N allocations *) if a >= p then (record excs (p,a,sz); raise (MyExc "from allocation callback")); Some (p, a, sz)) in let promote minor = Some minor in let alloc_major info = (assert false) in (* We don't expect any *) let tracker = { MP.alloc_minor ; dealloc_minor ; promote ; alloc_major ; dealloc_major } in let start () = (incr n_prof; n_alloc := 0; ignore (MP.start ~sampling_rate:1.0 tracker)) in let arr = ref [] in for i = 1 to 10 do start (); (try arr := (f33 42) :: (!arr); with MyExc s -> (incr n_exc)); MP.stop(); Gc.minor(); done; arr := []; Gc.full_major(); let alloc_size = AllocSet.fold (fun (p,a,sz) tot -> tot + sz) (!allocs) 0 in let alloc_count = AllocSet.cardinal (!allocs) in let dealloc_size = AllocSet.fold (fun (p,a,sz) tot -> tot + sz) (!deallocs) 0 in let dealloc_count = AllocSet.cardinal (!deallocs) in (* Every allocation callback is either raised or deallocated *) assert (AllocSet.disjoint (!deallocs) (!excs)); assert (AllocSet.equal (AllocSet.union (!deallocs) (!excs)) (!allocs)); (* Each call to f33 would allocates 7 blocks of 33 words, (sizes 6, 5, 4, 6, 5, 4, 3) plus the 3 words for the cons cell to add the result to !arr, making 8 blocks of 36 words. So we see this behaviour, as we iterate through the loop: i allocs exn words 1 1 1 6 6 2 2 1 11 6+5 3 3 1 15 6+5+4 4 4 1 21 6+5+4+6 5 5 1 26 6+5+4+6+5 6 6 1 30 6+5+4+6+5+4 7 7 1 33 6+5+4+6+5+4+3 8 8 1 36 6+5+4+6+5+4+3+3 9 8 0 36 6+5+4+6+5+4+3+3 10 8 0 36 6+5+4+6+5+4+3+3 52 8 250 total and of those "allocations" (most of which never actually take place with the native code backend), the profile sees deallocations for all except 8 (the ones for which the callbacks raise exceptions), which add up to 36 words. *) assert (dealloc_count = 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 8); assert (alloc_count = dealloc_count + !n_exc); assert (dealloc_size = (0 + 6 + 6 + 5 + 6 + 5 + 4 + 6 + 5 + 4 + 6 + 6 + 5 + 4 + 6 + 5 + 6 + 5 + 4 + 6 + 5 + 4 + 6 + 5 + 4 + 6 + 5 + 4 + 3 + 6 + 5 + 4 + 6 + 5 + 4 + 3 + 3 + 6 + 5 + 4 + 6 + 5 + 4 + 3 + 3)); assert (alloc_size = dealloc_size + (6 + 5 + 4 + 6 + 5 + 4 + 3 + 3)); arr let _ = raise_in_alloc ()