Skip to content

GC Always Full Flag #58713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions base/options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ struct JLOptions
trim::Int8
task_metrics::Int8
timeout_for_safepoint_straggler_s::Int16
gc_sweep_always_full::Int8
end

# This runs early in the sysimage != is not defined yet
Expand Down
11 changes: 0 additions & 11 deletions doc/src/manual/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -530,17 +530,6 @@ Allows you to enable or disable zones for a specific Julia run.
For instance, setting the variable to `+GC,-INFERENCE` will enable the `GC` zones and disable
the `INFERENCE` zones. See [Dynamically Enabling and Disabling Zones](@ref).

### [`JULIA_GC_NO_GENERATIONAL`](@id JULIA_GC_NO_GENERATIONAL)

If set to anything besides `0`, then the Julia garbage collector never performs
"quick sweeps" of memory.

!!! note

This environment variable only has an effect if Julia was compiled with
garbage-collection debugging (that is, if `WITH_GC_DEBUG_ENV` is set to `1`
in the build configuration).

### [`JULIA_GC_WAIT_FOR_DEBUGGER`](@id JULIA_GC_WAIT_FOR_DEBUGGER)

If set to anything besides `0`, then the Julia garbage collector will wait for
Expand Down
5 changes: 1 addition & 4 deletions src/gc-debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -892,10 +892,7 @@ void gc_heuristics_summary(
void jl_gc_debug_init(void)
{
#ifdef GC_DEBUG_ENV
char *env = getenv("JULIA_GC_NO_GENERATIONAL");
if (env && strcmp(env, "0") != 0)
jl_gc_debug_env.always_full = 1;
env = getenv("JULIA_GC_WAIT_FOR_DEBUGGER");
char *env = getenv("JULIA_GC_WAIT_FOR_DEBUGGER");
jl_gc_debug_env.wait_for_debugger = env && strcmp(env, "0") != 0;
gc_debug_alloc_init(&jl_gc_debug_env.pool, "POOL");
gc_debug_alloc_init(&jl_gc_debug_env.other, "OTHER");
Expand Down
2 changes: 1 addition & 1 deletion src/gc-stock.c
Original file line number Diff line number Diff line change
Expand Up @@ -3155,7 +3155,7 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) JL_NOTS
// If the live data outgrows the suggested max_total_memory
// we keep going with minimum intervals and full gcs until
// we either free some space or get an OOM error.
if (gc_sweep_always_full) {
if (jl_options.gc_sweep_always_full) {
sweep_full = 1;
gc_count_full_sweep_reason(FULL_SWEEP_REASON_SWEEP_ALWAYS_FULL);
}
Expand Down
3 changes: 0 additions & 3 deletions src/gc-stock.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ typedef struct {
} jl_alloc_num_t;

typedef struct {
int always_full;
int wait_for_debugger;
jl_alloc_num_t pool;
jl_alloc_num_t other;
Expand Down Expand Up @@ -647,14 +646,12 @@ NOINLINE void gc_mark_loop_unwind(jl_ptls_t ptls, jl_gc_markqueue_t *mq, int off

#ifdef GC_DEBUG_ENV
JL_DLLEXPORT extern jl_gc_debug_env_t jl_gc_debug_env;
#define gc_sweep_always_full jl_gc_debug_env.always_full
int jl_gc_debug_check_other(void);
int gc_debug_check_pool(void);
void jl_gc_debug_print(void);
void gc_scrub_record_task(jl_task_t *ta) JL_NOTSAFEPOINT;
void gc_scrub(void);
#else
#define gc_sweep_always_full 0
static inline int jl_gc_debug_check_other(void)
{
return 0;
Expand Down
26 changes: 18 additions & 8 deletions src/jloptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ JL_DLLEXPORT void jl_init_options(void)
JL_TRIM_NO, // trim
0, // task_metrics
-1, // timeout_for_safepoint_straggler_s
0, // gc_sweep_always_full
};
jl_options_initialized = 1;
}
Expand Down Expand Up @@ -293,14 +294,6 @@ static const char opts[] =
" number of bytes, optionally in units of: B, K (kibibytes),\n"
" M (mebibytes), G (gibibytes), T (tebibytes), or % (percentage\n"
" of physical memory).\n\n"
" --hard-heap-limit=<size>[<unit>] Set a hard limit on the heap size: if we ever go above this\n"
" limit, we will abort. The value may be specified as a\n"
" number of bytes, optionally in units of: B, K (kibibytes),\n"
" M (mebibytes), G (gibibytes) or T (tebibytes).\n\n"
" --heap-target-increment=<size>[<unit>] Set an upper bound on how much the heap target\n"
" can increase between consecutive collections. The value may be\n"
" specified as a number of bytes, optionally in units of: B,\n"
" K (kibibytes), M (mebibytes), G (gibibytes) or T (tebibytes).\n\n"
;

static const char opts_hidden[] =
Expand Down Expand Up @@ -344,6 +337,18 @@ static const char opts_hidden[] =
" and can throw errors. With unsafe-warn warnings will be\n"
" printed for dynamic call sites that might lead to such\n"
" errors. In safe mode compile-time errors are given instead.\n"
" --hard-heap-limit=<size>[<unit>] Set a hard limit on the heap size: if we ever\n"
" go above this limit, we will abort. The value\n"
" may be specified as a number of bytes,\n"
" optionally in units of: B, K (kibibytes),\n"
" M (mebibytes), G (gibibytes) or T (tebibytes).\n"
" --heap-target-increment=<size>[<unit>] Set an upper bound on how much the heap\n"
" target can increase between consecutive\n"
" collections. The value may be specified as\n"
" a number of bytes, optionally in units of:\n"
" B, K (kibibytes), M (mebibytes), G (gibibytes)\n"
" or T (tebibytes).\n"
" --gc-sweep-always-full Makes the GC always do a full sweep of the heap\n"
;

JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
Expand Down Expand Up @@ -394,6 +399,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
opt_heap_size_hint,
opt_hard_heap_limit,
opt_heap_target_increment,
opt_gc_sweep_always_full,
opt_gc_threads,
opt_permalloc_pkgimg,
opt_trim,
Expand Down Expand Up @@ -467,6 +473,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
{ "heap-size-hint", required_argument, 0, opt_heap_size_hint },
{ "hard-heap-limit", required_argument, 0, opt_hard_heap_limit },
{ "heap-target-increment", required_argument, 0, opt_heap_target_increment },
{ "gc-sweep-always-full", no_argument, 0, opt_gc_sweep_always_full },
{ "trim", optional_argument, 0, opt_trim },
{ 0, 0, 0, 0 }
};
Expand Down Expand Up @@ -1027,6 +1034,9 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
jl_errorf("julia: --timeout-for-safepoint-straggler=<seconds>; seconds must be an integer between 1 and %d", INT16_MAX);
jl_options.timeout_for_safepoint_straggler_s = (int16_t)timeout;
break;
case opt_gc_sweep_always_full:
jl_options.gc_sweep_always_full = 1;
break;
case opt_trim:
if (optarg == NULL || !strcmp(optarg,"safe"))
jl_options.trim = JL_TRIM_SAFE;
Expand Down
1 change: 1 addition & 0 deletions src/jloptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ typedef struct {
int8_t trim;
int8_t task_metrics;
int16_t timeout_for_safepoint_straggler_s;
int8_t gc_sweep_always_full;
} jl_options_t;

#endif
9 changes: 9 additions & 0 deletions test/gc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ end
@testset "Full GC reasons" begin
full_sweep_reasons_test()
end

@testset "GC Always Full" begin
prog = "using Test;\n
for _ in 1:10; GC.gc(); end;\n
reasons = Base.full_sweep_reasons();\n
@test reasons[:FULL_SWEEP_REASON_SWEEP_ALWAYS_FULL] >= 10;"
cmd = `$(Base.julia_cmd()) --depwarn=error --startup-file=no --gc-sweep-always-full -e $prog`
@test success(cmd)
end
end

@testset "Base.GC docstrings" begin
Expand Down