Skip to content

Commit 8d2708c

Browse files
authored
reland: hard heap limit flag (JuliaLang#58600)
Relands: JuliaLang#58487.
1 parent 6f1da03 commit 8d2708c

File tree

7 files changed

+136
-14
lines changed

7 files changed

+136
-14
lines changed

base/options.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ struct JLOptions
6060
strip_ir::Int8
6161
permalloc_pkgimg::Int8
6262
heap_size_hint::UInt64
63+
hard_heap_limit::UInt64
64+
heap_target_increment::UInt64
6365
trace_compile_timing::Int8
6466
trim::Int8
6567
task_metrics::Int8

src/gc-mmtk.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ void jl_gc_init(void) {
7878
if (jl_options.heap_size_hint == 0) {
7979
char *cp = getenv(HEAP_SIZE_HINT);
8080
if (cp)
81-
hint = parse_heap_size_hint(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"");
81+
hint = parse_heap_size_option(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"", 1);
8282
}
8383
#ifdef _P64
8484
if (hint == 0) {

src/gc-stock.c

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3214,7 +3214,7 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) JL_NOTS
32143214
uint64_t target_heap;
32153215
const char *reason = ""; (void)reason; // for GC_TIME output stats
32163216
old_heap_size = heap_size; // TODO: Update these values dynamically instead of just during the GC
3217-
if (collection == JL_GC_AUTO) {
3217+
if (collection == JL_GC_AUTO && jl_options.hard_heap_limit == 0) {
32183218
// update any heuristics only when the user does not force the GC
32193219
// but still update the timings, since GC was run and reset, even if it was too early
32203220
uint64_t target_allocs = 0.0;
@@ -3295,6 +3295,27 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) JL_NOTS
32953295
target_heap = jl_atomic_load_relaxed(&gc_heap_stats.heap_target);
32963296
}
32973297

3298+
// Kill the process if we are above the hard heap limit
3299+
if (jl_options.hard_heap_limit != 0) {
3300+
if (heap_size > jl_options.hard_heap_limit) {
3301+
// Can't use `jl_errorf` here, because it will try to allocate memory
3302+
// and we are already at the hard limit.
3303+
jl_safe_printf("Heap size exceeded hard limit of %" PRIu64 " bytes.\n",
3304+
jl_options.hard_heap_limit);
3305+
abort();
3306+
}
3307+
}
3308+
// Ignore heap limit computation from MemBalancer-like heuristics
3309+
// if the heap target increment goes above the value specified through
3310+
// `--heap-target-increment`.
3311+
// Note that if we reach this code, we can guarantee that the heap size
3312+
// is less than the hard limit, so there will be some room to grow the heap
3313+
// until the next GC without hitting the hard limit.
3314+
if (jl_options.heap_target_increment != 0) {
3315+
target_heap = heap_size + jl_options.heap_target_increment;
3316+
jl_atomic_store_relaxed(&gc_heap_stats.heap_target, target_heap);
3317+
}
3318+
32983319
double old_ratio = (double)promoted_bytes/(double)heap_size;
32993320
if (heap_size > user_max) {
33003321
next_sweep_full = 1;
@@ -3692,6 +3713,9 @@ void jl_gc_init(void)
36923713
arraylist_new(&finalizer_list_marked, 0);
36933714
arraylist_new(&to_finalize, 0);
36943715
jl_atomic_store_relaxed(&gc_heap_stats.heap_target, default_collect_interval);
3716+
if (jl_options.hard_heap_limit != 0) {
3717+
jl_atomic_store_relaxed(&gc_heap_stats.heap_target, jl_options.hard_heap_limit);
3718+
}
36953719
gc_num.interval = default_collect_interval;
36963720
gc_num.allocd = 0;
36973721
gc_num.max_pause = 0;
@@ -3705,7 +3729,7 @@ void jl_gc_init(void)
37053729
if (jl_options.heap_size_hint == 0) {
37063730
char *cp = getenv(HEAP_SIZE_HINT);
37073731
if (cp)
3708-
hint = parse_heap_size_hint(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"");
3732+
hint = parse_heap_size_option(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"", 1);
37093733
}
37103734
#ifdef _P64
37113735
total_mem = uv_get_total_memory();

src/jloptions.c

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ JL_DLLEXPORT const char *jl_get_default_sysimg_path(void)
3636

3737
/* This function is also used by gc-stock.c to parse the
3838
* JULIA_HEAP_SIZE_HINT environment variable. */
39-
uint64_t parse_heap_size_hint(const char *optarg, const char *option_name)
39+
uint64_t parse_heap_size_option(const char *optarg, const char *option_name, int allow_pct)
4040
{
4141
long double value = 0.0;
4242
char unit[4] = {0};
@@ -62,14 +62,16 @@ uint64_t parse_heap_size_hint(const char *optarg, const char *option_name)
6262
multiplier <<= 40;
6363
break;
6464
case '%':
65-
if (value > 100)
66-
jl_errorf("julia: invalid percentage specified in %s", option_name);
67-
uint64_t mem = uv_get_total_memory();
68-
uint64_t cmem = uv_get_constrained_memory();
69-
if (cmem > 0 && cmem < mem)
70-
mem = cmem;
71-
multiplier = mem/100;
72-
break;
65+
if (allow_pct) {
66+
if (value > 100)
67+
jl_errorf("julia: invalid percentage specified in %s", option_name);
68+
uint64_t mem = uv_get_total_memory();
69+
uint64_t cmem = uv_get_constrained_memory();
70+
if (cmem > 0 && cmem < mem)
71+
mem = cmem;
72+
multiplier = mem/100;
73+
break;
74+
}
7375
default:
7476
jl_errorf("julia: invalid argument to %s (%s)", option_name, optarg);
7577
break;
@@ -151,6 +153,8 @@ JL_DLLEXPORT void jl_init_options(void)
151153
0, // strip-ir
152154
0, // permalloc_pkgimg
153155
0, // heap-size-hint
156+
0, // hard-heap-limit
157+
0, // heap-target-increment
154158
0, // trace_compile_timing
155159
JL_TRIM_NO, // trim
156160
0, // task_metrics
@@ -289,6 +293,14 @@ static const char opts[] =
289293
" number of bytes, optionally in units of: B, K (kibibytes),\n"
290294
" M (mebibytes), G (gibibytes), T (tebibytes), or % (percentage\n"
291295
" of physical memory).\n\n"
296+
" --hard-heap-limit=<size>[<unit>] Set a hard limit on the heap size: if we ever go above this\n"
297+
" limit, we will abort. The value may be specified as a\n"
298+
" number of bytes, optionally in units of: B, K (kibibytes),\n"
299+
" M (mebibytes), G (gibibytes) or T (tebibytes).\n\n"
300+
" --heap-target-increment=<size>[<unit>] Set an upper bound on how much the heap target\n"
301+
" can increase between consecutive collections. The value may be\n"
302+
" specified as a number of bytes, optionally in units of: B,\n"
303+
" K (kibibytes), M (mebibytes), G (gibibytes) or T (tebibytes).\n\n"
292304
;
293305

294306
static const char opts_hidden[] =
@@ -380,6 +392,8 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
380392
opt_strip_metadata,
381393
opt_strip_ir,
382394
opt_heap_size_hint,
395+
opt_hard_heap_limit,
396+
opt_heap_target_increment,
383397
opt_gc_threads,
384398
opt_permalloc_pkgimg,
385399
opt_trim,
@@ -451,6 +465,8 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
451465
{ "strip-ir", no_argument, 0, opt_strip_ir },
452466
{ "permalloc-pkgimg",required_argument, 0, opt_permalloc_pkgimg },
453467
{ "heap-size-hint", required_argument, 0, opt_heap_size_hint },
468+
{ "hard-heap-limit", required_argument, 0, opt_hard_heap_limit },
469+
{ "heap-target-increment", required_argument, 0, opt_heap_target_increment },
454470
{ "trim", optional_argument, 0, opt_trim },
455471
{ 0, 0, 0, 0 }
456472
};
@@ -960,11 +976,23 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
960976
break;
961977
case opt_heap_size_hint:
962978
if (optarg != NULL)
963-
jl_options.heap_size_hint = parse_heap_size_hint(optarg, "--heap-size-hint=<size>[<unit>]");
979+
jl_options.heap_size_hint = parse_heap_size_option(optarg, "--heap-size-hint=<size>[<unit>]", 1);
964980
if (jl_options.heap_size_hint == 0)
965981
jl_errorf("julia: invalid memory size specified in --heap-size-hint=<size>[<unit>]");
966982

967983
break;
984+
case opt_hard_heap_limit:
985+
if (optarg != NULL)
986+
jl_options.hard_heap_limit = parse_heap_size_option(optarg, "--hard-heap-limit=<size>[<unit>]", 0);
987+
if (jl_options.hard_heap_limit == 0)
988+
jl_errorf("julia: invalid memory size specified in --hard-heap-limit=<size>[<unit>]");
989+
break;
990+
case opt_heap_target_increment:
991+
if (optarg != NULL)
992+
jl_options.heap_target_increment = parse_heap_size_option(optarg, "--heap-target-increment=<size>[<unit>]", 0);
993+
if (jl_options.heap_target_increment == 0)
994+
jl_errorf("julia: invalid memory size specified in --heap-target-increment=<size>[<unit>]");
995+
break;
968996
case opt_gc_threads:
969997
errno = 0;
970998
long nmarkthreads = strtol(optarg, &endptr, 10);

src/jloptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ typedef struct {
6464
int8_t strip_ir;
6565
int8_t permalloc_pkgimg;
6666
uint64_t heap_size_hint;
67+
uint64_t hard_heap_limit;
68+
uint64_t heap_target_increment;
6769
int8_t trace_compile_timing;
6870
int8_t trim;
6971
int8_t task_metrics;

src/julia.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2578,7 +2578,7 @@ JL_DLLEXPORT ssize_t jl_sizeof_jl_options(void);
25782578
JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp);
25792579
JL_DLLEXPORT char *jl_format_filename(const char *output_pattern);
25802580

2581-
uint64_t parse_heap_size_hint(const char *optarg, const char *option_name);
2581+
uint64_t parse_heap_size_option(const char *optarg, const char *option_name, int allow_pct);
25822582

25832583
// Set julia-level ARGS array according to the arguments provided in
25842584
// argc/argv

test/cmdlineargs.jl

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,12 @@ end
12141214

12151215
@test readchomp(`$(Base.julia_cmd()) --startup-file=no --heap-size-hint=10M -e "println(@ccall jl_gc_get_max_memory()::UInt64)"`) == "$(1*1024*1024)"
12161216
end
1217+
1218+
@testset "hard heap limit" begin
1219+
cmd = `$(Base.julia_cmd()) --hard-heap-limit=30M -E "mutable struct ListNode; v::Int64; next::Union{ListNode, Nothing}; end\n
1220+
l = ListNode(0, nothing); while true; l = ListNode(0, l); end"`
1221+
@test !success(cmd)
1222+
end
12171223
end
12181224

12191225
## `Main.main` entrypoint
@@ -1253,6 +1259,66 @@ end
12531259
end
12541260
end
12551261

1262+
@testset "--hard-heap-limit" begin
1263+
exename = `$(Base.julia_cmd())`
1264+
@test errors_not_signals(`$exename --hard-heap-limit -e "exit(0)"`)
1265+
@testset "--hard-heap-limit=$str" for str in ["asdf","","0","1.2vb","b","GB","2.5GB̂","1.2gb2","42gigabytes","5gig","2GiB","NaNt"]
1266+
@test errors_not_signals(`$exename --hard-heap-limit=$str -e "exit(0)"`)
1267+
end
1268+
k = UInt64(1) << 10
1269+
m = UInt64(1) << 20
1270+
g = UInt64(1) << 30
1271+
t = UInt64(1) << 40
1272+
one_hundred_mb_strs_and_vals = [
1273+
("100000000", 100000000), ("1e8", 1e8), ("100MB", 100m), ("100m", 100m), ("1e5kB", 1e5k),
1274+
]
1275+
@testset "--hard-heap-limit=$str" for (str, val) in one_hundred_mb_strs_and_vals
1276+
@test parse(UInt64,read(`$exename --hard-heap-limit=$str -E "Base.JLOptions().hard_heap_limit"`, String)) == val
1277+
end
1278+
two_and_a_half_gigabytes_strs_and_vals = [
1279+
("2500000000", 2500000000), ("2.5e9", 2.5e9), ("2.5g", 2.5g), ("2.5GB", 2.5g), ("2.5e6mB", 2.5e6m),
1280+
]
1281+
@testset "--hard-heap-limit=$str" for (str, val) in two_and_a_half_gigabytes_strs_and_vals
1282+
@test parse(UInt64,read(`$exename --hard-heap-limit=$str -E "Base.JLOptions().hard_heap_limit"`, String)) == val
1283+
end
1284+
one_terabyte_strs_and_vals = [
1285+
("1TB", 1t), ("1024GB", 1t),
1286+
]
1287+
@testset "--hard-heap-limit=$str" for (str, val) in one_terabyte_strs_and_vals
1288+
@test parse(UInt64,read(`$exename --hard-heap-limit=$str -E "Base.JLOptions().hard_heap_limit"`, String)) == val
1289+
end
1290+
end
1291+
1292+
@testset "--heap-target-increment" begin
1293+
exename = `$(Base.julia_cmd())`
1294+
@test errors_not_signals(`$exename --heap-target-increment -e "exit(0)"`)
1295+
@testset "--heap-target-increment=$str" for str in ["asdf","","0","1.2vb","b","GB","2.5GB̂","1.2gb2","42gigabytes","5gig","2GiB","NaNt"]
1296+
@test errors_not_signals(`$exename --heap-target-increment=$str -e "exit(0)"`)
1297+
end
1298+
k = UInt64(1) << 10
1299+
m = UInt64(1) << 20
1300+
g = UInt64(1) << 30
1301+
t = UInt64(1) << 40
1302+
one_hundred_mb_strs_and_vals = [
1303+
("100000000", 100000000), ("1e8", 1e8), ("100MB", 100m), ("100m", 100m), ("1e5kB", 1e5k),
1304+
]
1305+
@testset "--heap-target-increment=$str" for (str, val) in one_hundred_mb_strs_and_vals
1306+
@test parse(UInt64,read(`$exename --heap-target-increment=$str -E "Base.JLOptions().heap_target_increment"`, String)) == val
1307+
end
1308+
two_and_a_half_gigabytes_strs_and_vals = [
1309+
("2500000000", 2500000000), ("2.5e9", 2.5e9), ("2.5g", 2.5g), ("2.5GB", 2.5g), ("2.5e6mB", 2.5e6m),
1310+
]
1311+
@testset "--heap-target-increment=$str" for (str, val) in two_and_a_half_gigabytes_strs_and_vals
1312+
@test parse(UInt64,read(`$exename --heap-target-increment=$str -E "Base.JLOptions().heap_target_increment"`, String)) == val
1313+
end
1314+
one_terabyte_strs_and_vals = [
1315+
("1TB", 1t), ("1024GB", 1t),
1316+
]
1317+
@testset "--heap-target-increment=$str" for (str, val) in one_terabyte_strs_and_vals
1318+
@test parse(UInt64,read(`$exename --heap-target-increment=$str -E "Base.JLOptions().heap_target_increment"`, String)) == val
1319+
end
1320+
end
1321+
12561322
@testset "--timeout-for-safepoint-straggler" begin
12571323
exename = `$(Base.julia_cmd())`
12581324
timeout = 120

0 commit comments

Comments
 (0)