Skip to content

Commit 8d63cbd

Browse files
Move GC out of test_end_expr to its own keyword gc_between_testitems (#169)
* Move GC out of `test_end_expr` * Introduce new user-facing keyword * wording * tidy comments * Bump version * typo * fix passing of new argument through * typo
1 parent b800f37 commit 8d63cbd

File tree

2 files changed

+28
-13
lines changed

2 files changed

+28
-13
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "ReTestItems"
22
uuid = "817f1d60-ba6b-4fd5-9520-3cf149f6a823"
3-
version = "1.25.0"
3+
version = "1.25.1"
44

55
[deps]
66
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"

src/ReTestItems.jl

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,13 @@ will be run.
165165
- `worker_init_expr::Expr`: an expression that will be run on each worker process before any tests are run.
166166
Can be used to load packages or set up the environment. Must be a `:block` expression.
167167
- `test_end_expr::Expr`: an expression that will be run after each testitem is run.
168-
By default, the expression is `GC.gc(true)`, when `nworkers > 1`, to help with memory pressure,
169-
otherwise it is a no-op.
170168
Can be used to verify that global state is unchanged after running a test. Must be a `:block` expression.
169+
- `gc_between_testitems::Bool`: If `true`, a full garbage collection (GC) will be run after each test item is run.
170+
Defaults to `nworkers > 1`, i.e. `true` when running with multiple worker processes, since multiple worker processes
171+
cannot coordinate to trigger Julia's GC, and it should not be necessary to invoke the GC directly if running without
172+
workers or with a single worker (since the GC will be triggered automatically by the single process running all the tests).
173+
Can also be set using the `RETESTITEMS_GC_BETWEEN_TESTITEMS` environment variable.
174+
Tip: For complete control over GC, set `gc_between_testitems=false` and manually trigger GC in `test_end_expr`.
171175
- `memory_threshold::Real`: Sets the fraction of memory that can be in use before a worker processes are
172176
restarted to free memory. Defaults to $(DEFAULT_MEMORY_THRESHOLD[]). Only supported with `nworkers > 0`.
173177
For example, if set to 0.8, then when >80% of the available memory is in use, a worker process will be killed and
@@ -242,9 +246,10 @@ function runtests(
242246
report::Bool=parse(Bool, get(ENV, "RETESTITEMS_REPORT", "false")),
243247
logs::Symbol=Symbol(get(ENV, "RETESTITEMS_LOGS", default_log_display_mode(report, nworkers))),
244248
verbose_results::Bool=(logs !== :issues && isinteractive()),
245-
test_end_expr::Expr=nworkers>1 ? :(GC.gc(true)) : Expr(:block),
249+
test_end_expr::Expr=Expr(:block),
246250
validate_paths::Bool=parse(Bool, get(ENV, "RETESTITEMS_VALIDATE_PATHS", "false")),
247251
timeout_profile_wait::Real=parse(Int, get(ENV, "RETESTITEMS_TIMEOUT_PROFILE_WAIT", "0")),
252+
gc_between_testitems::Bool=parse(Bool, get(ENV, "RETESTITEMS_GC_BETWEEN_TESTITEMS", string(nworkers > 1))),
248253
)
249254
nworker_threads = _validated_nworker_threads(nworker_threads)
250255
paths′ = _validated_paths(paths, validate_paths)
@@ -267,10 +272,10 @@ function runtests(
267272
debuglvl = Int(debug)
268273
if debuglvl > 0
269274
LoggingExtras.withlevel(LoggingExtras.Debug; verbosity=debuglvl) do
270-
_runtests(shouldrun_combined, paths′, nworkers, nworker_threads, worker_init_expr, test_end_expr, timeout, retries, memory_threshold, verbose_results, debuglvl, report, logs, timeout_profile_wait)
275+
_runtests(shouldrun_combined, paths′, nworkers, nworker_threads, worker_init_expr, test_end_expr, timeout, retries, memory_threshold, verbose_results, debuglvl, report, logs, timeout_profile_wait, gc_between_testitems)
271276
end
272277
else
273-
return _runtests(shouldrun_combined, paths′, nworkers, nworker_threads, worker_init_expr, test_end_expr, timeout, retries, memory_threshold, verbose_results, debuglvl, report, logs, timeout_profile_wait)
278+
return _runtests(shouldrun_combined, paths′, nworkers, nworker_threads, worker_init_expr, test_end_expr, timeout, retries, memory_threshold, verbose_results, debuglvl, report, logs, timeout_profile_wait, gc_between_testitems)
274279
end
275280
end
276281

@@ -284,7 +289,7 @@ end
284289
# By tracking and reusing test environments, we can avoid this issue.
285290
const TEST_ENVS = Dict{String, String}()
286291

287-
function _runtests(shouldrun, paths, nworkers::Int, nworker_threads::String, worker_init_expr::Expr, test_end_expr::Expr, testitem_timeout::Int, retries::Int, memory_threshold::Real, verbose_results::Bool, debug::Int, report::Bool, logs::Symbol, timeout_profile_wait::Int)
292+
function _runtests(shouldrun, paths, nworkers::Int, nworker_threads::String, worker_init_expr::Expr, test_end_expr::Expr, testitem_timeout::Int, retries::Int, memory_threshold::Real, verbose_results::Bool, debug::Int, report::Bool, logs::Symbol, timeout_profile_wait::Int, gc_between_testitems::Bool)
288293
# Don't recursively call `runtests` e.g. if we `include` a file which calls it.
289294
# So we ignore the `runtests(...)` call in `test/runtests.jl` when `runtests(...)`
290295
# was called from the command line.
@@ -304,7 +309,7 @@ function _runtests(shouldrun, paths, nworkers::Int, nworker_threads::String, wor
304309
if is_running_test_runtests_jl(proj_file)
305310
# Assume this is `Pkg.test`, so test env already active.
306311
@debugv 2 "Running in current environment `$(Base.active_project())`"
307-
return _runtests_in_current_env(shouldrun, paths, proj_file, nworkers, nworker_threads, worker_init_expr, test_end_expr, testitem_timeout, retries, memory_threshold, verbose_results, debug, report, logs, timeout_profile_wait)
312+
return _runtests_in_current_env(shouldrun, paths, proj_file, nworkers, nworker_threads, worker_init_expr, test_end_expr, testitem_timeout, retries, memory_threshold, verbose_results, debug, report, logs, timeout_profile_wait, gc_between_testitems)
308313
else
309314
@debugv 1 "Activating test environment for `$proj_file`"
310315
orig_proj = Base.active_project()
@@ -317,7 +322,7 @@ function _runtests(shouldrun, paths, nworkers::Int, nworker_threads::String, wor
317322
testenv = TestEnv.activate()
318323
TEST_ENVS[proj_file] = testenv
319324
end
320-
_runtests_in_current_env(shouldrun, paths, proj_file, nworkers, nworker_threads, worker_init_expr, test_end_expr, testitem_timeout, retries, memory_threshold, verbose_results, debug, report, logs, timeout_profile_wait)
325+
_runtests_in_current_env(shouldrun, paths, proj_file, nworkers, nworker_threads, worker_init_expr, test_end_expr, testitem_timeout, retries, memory_threshold, verbose_results, debug, report, logs, timeout_profile_wait, gc_between_testitems)
321326
finally
322327
Base.set_active_project(orig_proj)
323328
end
@@ -328,7 +333,7 @@ end
328333
function _runtests_in_current_env(
329334
shouldrun, paths, projectfile::String, nworkers::Int, nworker_threads, worker_init_expr::Expr, test_end_expr::Expr,
330335
testitem_timeout::Int, retries::Int, memory_threshold::Real, verbose_results::Bool, debug::Int, report::Bool, logs::Symbol,
331-
timeout_profile_wait::Int,
336+
timeout_profile_wait::Int, gc_between_testitems::Bool,
332337
)
333338
start_time = time()
334339
proj_name = something(Pkg.Types.read_project(projectfile).name, "")
@@ -359,6 +364,10 @@ function _runtests_in_current_env(
359364
ts = res.testset
360365
print_errors_and_captured_logs(testitem, run_number; logs)
361366
report_empty_testsets(testitem, ts)
367+
if gc_between_testitems
368+
@debugv 2 "Running GC"
369+
GC.gc(true)
370+
end
362371
if any_non_pass(ts) && run_number != max_runs
363372
run_number += 1
364373
@info "Retrying $(repr(testitem.name)). Run=$run_number."
@@ -368,7 +377,8 @@ function _runtests_in_current_env(
368377
end
369378
end
370379
elseif !isempty(testitems.testitems)
371-
# Try to free up memory on the coordinator before starting workers
380+
# Try to free up memory on the coordinator before starting workers, since
381+
# the workers won't be able to collect it if they get under memory pressure.
372382
GC.gc(true)
373383
# Use the logger that was set before we eval'd any user code to avoid world age
374384
# issues when logging https://github.com/JuliaLang/julia/issues/33865
@@ -392,7 +402,7 @@ function _runtests_in_current_env(
392402
ti = starting[i]
393403
@spawn begin
394404
with_logger(original_logger) do
395-
manage_worker($w, $proj_name, $testitems, $ti, $nworker_threads, $worker_init_expr, $test_end_expr, $testitem_timeout, $retries, $memory_threshold, $verbose_results, $debug, $report, $logs, $timeout_profile_wait)
405+
manage_worker($w, $proj_name, $testitems, $ti, $nworker_threads, $worker_init_expr, $test_end_expr, $testitem_timeout, $retries, $memory_threshold, $verbose_results, $debug, $report, $logs, $timeout_profile_wait, $gc_between_testitems)
396406
end
397407
end
398408
end
@@ -503,7 +513,8 @@ end
503513

504514
function manage_worker(
505515
worker::Worker, proj_name::AbstractString, testitems::TestItems, testitem::Union{TestItem,Nothing}, nworker_threads, worker_init_expr::Expr, test_end_expr::Expr,
506-
default_timeout::Int, retries::Int, memory_threshold::Real, verbose_results::Bool, debug::Int, report::Bool, logs::Symbol, timeout_profile_wait::Int
516+
default_timeout::Int, retries::Int, memory_threshold::Real, verbose_results::Bool, debug::Int, report::Bool, logs::Symbol, timeout_profile_wait::Int,
517+
gc_between_testitems::Bool
507518
)
508519
ntestitems = length(testitems.testitems)
509520
run_number = 1
@@ -547,6 +558,10 @@ function manage_worker(
547558
push!(testitem.stats, testitem_result.stats)
548559
print_errors_and_captured_logs(testitem, run_number; logs)
549560
report_empty_testsets(testitem, ts)
561+
if gc_between_testitems
562+
@debugv 2 "Running GC on $worker"
563+
remote_fetch(worker, :(GC.gc(true)))
564+
end
550565
if any_non_pass(ts) && run_number != max_runs
551566
run_number += 1
552567
@info "Retrying $(repr(testitem.name)) on $worker. Run=$run_number."

0 commit comments

Comments
 (0)