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
//! Drop heavy values on rayon worker threads instead of the caller's thread.
//!
//! Freeing the link stage output (module_table, metas, stmt_infos, ...)
//! takes ~15ms of hot-thread time on a 20k-module build, after the output is
//! already produced. The rayon workers are idle by then, so shipping the
//! drop to one of them takes the free() off the critical path.
//!
//! Deferred drops cannot pile up across builds: this is ENFORCED, not
//! assumed. Every pending drop is counted, and [`drain`] blocks until the
//! count is zero. It is called at every entry point that starts scan/link/
//! render work on the shared rayon pool: `BundleFactory::build_bundle` (full
//! and incremental builds) and the three HMR partial-scan entries in
//! `impl_bundler_hmr.rs`, which bypass `build_bundle`. In steady state it is
//! a no-op (a single uncontended lock check): the frees take ~20ms while any
//! build that could produce the next pair takes hundreds of ms. Even in the
//! worst case, drain blocks for no longer than main spent doing the same
//! frees inline on the same thread.
//!
//! Within a single build the count stays bounded by construction: exactly one
//! object is enqueued, once per build, at the build boundary (right after
//! `generate()` returns). There is no per-item or per-iteration use.
//!
//! Within the SAME build, the free intentionally overlaps the
//! `render_error`/`generateBundle` hooks and the write tail — that overlap IS
//! the optimization. This never extends a memory window relative to main:
//! main held `link_stage_output` alive until `bundle_up` scope end, i.e.
//! through those same hooks; here it is freed concurrently DURING them
//! (measured peak RSS is flat). Only values main itself kept alive through
//! the overlapped region are eligible — that is why the non-incremental
//! `symbol_db`, which main frees inline BEFORE the hooks, is NOT deferred.
//! The dropped value is exclusively owned, so overlap can only cost bounded
//! background CPU, never correctness.
//!
//! The counter is process-global rather than per-bundler on purpose: with
//! concurrent bundler instances, the worst case is one instance waiting (at
//! most the ~20ms free time) for another instance's drops, or a few ms of
//! background free work overlapping a concurrent build — accepted as far
//! cheaper than per-instance plumbing for a non-correctness concern.
//!
//! Do NOT use [`spawn_drop`] for values that are still live before the output
//! exists (e.g. the per-module AST arenas, which main intentionally frees
//! before chunk instantiation/minify allocate — deferring them would extend
//! their memory window and spike peak RSS), or for anything enqueued in a
//! loop.
use ;
/// Number of `spawn_drop` closures that have been enqueued but not yet
/// finished dropping their value.
static PENDING: = new;
static PENDING_IS_ZERO: Condvar = new;
/// Decrements `PENDING` on drop, so the count goes down even if the deferred
/// value's `Drop` impl panics — a panic must not wedge `drain()` forever.
;
/// Drop `value` on a rayon worker thread instead of the caller's thread.
///
/// See the module docs for the invariants call sites must uphold.
/// Block until every pending deferred drop has finished.
///
/// Called once at build entry (`BundleFactory::build_bundle`) so a queued
/// watch rebuild can never overlap the previous build's frees on the shared
/// rayon pool. Expected to be a no-op in steady state; see the module docs.