Skip to content

Commit 88d649b

Browse files
committed
implemented --hard-heap-limit and --heap-target-increment
1 parent 8ac9291 commit 88d649b

File tree

8 files changed

+102
-6
lines changed

8 files changed

+102
-6
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>]\"");
8282
}
8383
#ifdef _P64
8484
if (hint == 0) {

src/gc-stock.c

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

3296+
// Kill the process if we are above the hard heap limit
3297+
if (heap_size > jl_options.hard_heap_limit) {
3298+
jl_errorf("Heap size of %zu bytes exceeds the hard limit of %zu bytes. "
3299+
"Please increase the heap limit with `--hard-heap-limit`.",
3300+
heap_size, jl_options.hard_heap_limit);
3301+
}
3302+
// Ignore heap limit computation from MemBalancer-like heuristics
3303+
// if the heap target increment goes above the value specified through
3304+
// `--heap-target-increment`.
3305+
// Note that if we reach this code, we can guarantee that the heap size
3306+
// is less than the hard limit, so there will be some room to grow the heap
3307+
// until the next GC without hitting the hard limit.
3308+
if (jl_options.heap_target_increment != GC_HARD_HEAP_LIMIT_FOR_UNSET_FLAG) {
3309+
target_heap = heap_size + jl_options.heap_target_increment;
3310+
jl_atomic_store_relaxed(&gc_heap_stats.heap_target, target_heap);
3311+
}
3312+
32963313
double old_ratio = (double)promoted_bytes/(double)heap_size;
32973314
if (heap_size > user_max) {
32983315
next_sweep_full = 1;
@@ -3690,6 +3707,9 @@ void jl_gc_init(void)
36903707
arraylist_new(&finalizer_list_marked, 0);
36913708
arraylist_new(&to_finalize, 0);
36923709
jl_atomic_store_relaxed(&gc_heap_stats.heap_target, default_collect_interval);
3710+
if (jl_options.hard_heap_limit != GC_HARD_HEAP_LIMIT_FOR_UNSET_FLAG) {
3711+
jl_atomic_store_relaxed(&gc_heap_stats.heap_target, jl_options.hard_heap_limit);
3712+
}
36933713
gc_num.interval = default_collect_interval;
36943714
gc_num.allocd = 0;
36953715
gc_num.max_pause = 0;
@@ -3703,7 +3723,7 @@ void jl_gc_init(void)
37033723
if (jl_options.heap_size_hint == 0) {
37043724
char *cp = getenv(HEAP_SIZE_HINT);
37053725
if (cp)
3706-
hint = parse_heap_size_hint(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"");
3726+
hint = parse_heap_size_option(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"");
37073727
}
37083728
#ifdef _P64
37093729
total_mem = uv_get_total_memory();

src/jloptions.c

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <limits.h>
44
#include <errno.h>
55

6+
#include "options.h"
67
#include "julia.h"
78
#include "julia_internal.h"
89

@@ -36,7 +37,7 @@ JL_DLLEXPORT const char *jl_get_default_sysimg_path(void)
3637

3738
/* This function is also used by gc-stock.c to parse the
3839
* JULIA_HEAP_SIZE_HINT environment variable. */
39-
uint64_t parse_heap_size_hint(const char *optarg, const char *option_name)
40+
uint64_t parse_heap_size_option(const char *optarg, const char *option_name)
4041
{
4142
long double value = 0.0;
4243
char unit[4] = {0};
@@ -151,6 +152,8 @@ JL_DLLEXPORT void jl_init_options(void)
151152
0, // strip-ir
152153
0, // permalloc_pkgimg
153154
0, // heap-size-hint
155+
GC_HARD_HEAP_LIMIT_FOR_UNSET_FLAG, // hard-heap-limit
156+
GC_HEAP_TARGET_INCREMENT_FOR_UNSET_FLAG, // heap-target-increment
154157
0, // trace_compile_timing
155158
JL_TRIM_NO, // trim
156159
0, // task_metrics
@@ -289,6 +292,16 @@ static const char opts[] =
289292
" number of bytes, optionally in units of: B, K (kibibytes),\n"
290293
" M (mebibytes), G (gibibytes), T (tebibytes), or % (percentage\n"
291294
" of physical memory).\n\n"
295+
" --hard-heap-limit=<size>[<unit>] Set a hard limit on the heap size: if we ever go above this\n"
296+
" limit, we will abort. The value may be specified as a\n"
297+
" number of bytes, optionally in units of: B, K (kibibytes),\n"
298+
" M (mebibytes), G (gibibytes), T (tebibytes), or % (percentage\n"
299+
" of physical memory).\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), T (tebibytes),\n"
304+
" or % (percentage of physical memory).\n\n"
292305
;
293306

294307
static const char opts_hidden[] =
@@ -380,6 +393,8 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
380393
opt_strip_metadata,
381394
opt_strip_ir,
382395
opt_heap_size_hint,
396+
opt_hard_heap_limit,
397+
opt_heap_target_increment,
383398
opt_gc_threads,
384399
opt_permalloc_pkgimg,
385400
opt_trim,
@@ -451,6 +466,8 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
451466
{ "strip-ir", no_argument, 0, opt_strip_ir },
452467
{ "permalloc-pkgimg",required_argument, 0, opt_permalloc_pkgimg },
453468
{ "heap-size-hint", required_argument, 0, opt_heap_size_hint },
469+
{ "hard-heap-limit", required_argument, 0, opt_hard_heap_limit },
470+
{ "heap-target-increment", required_argument, 0, opt_heap_target_increment },
454471
{ "trim", optional_argument, 0, opt_trim },
455472
{ 0, 0, 0, 0 }
456473
};
@@ -960,11 +977,23 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
960977
break;
961978
case opt_heap_size_hint:
962979
if (optarg != NULL)
963-
jl_options.heap_size_hint = parse_heap_size_hint(optarg, "--heap-size-hint=<size>[<unit>]");
980+
jl_options.heap_size_hint = parse_heap_size_option(optarg, "--heap-size-hint=<size>[<unit>]");
964981
if (jl_options.heap_size_hint == 0)
965982
jl_errorf("julia: invalid memory size specified in --heap-size-hint=<size>[<unit>]");
966983

967984
break;
985+
case opt_hard_heap_limit:
986+
if (optarg != NULL)
987+
jl_options.hard_heap_limit = parse_heap_size_option(optarg, "--hard-heap-limit=<size>[<unit>]");
988+
if (jl_options.hard_heap_limit == 0 || jl_options.hard_heap_limit == GC_HARD_HEAP_LIMIT_FOR_UNSET_FLAG)
989+
jl_errorf("julia: invalid memory size specified in --hard-heap-limit=<size>[<unit>]");
990+
break;
991+
case opt_heap_target_increment:
992+
if (optarg != NULL)
993+
jl_options.heap_target_increment = parse_heap_size_option(optarg, "--heap-target-increment=<size>[<unit>]");
994+
if (jl_options.heap_target_increment == 0 || jl_options.heap_target_increment == GC_HEAP_TARGET_INCREMENT_FOR_UNSET_FLAG)
995+
jl_errorf("julia: invalid memory size specified in --heap-target-increment=<size>[<unit>]");
996+
break;
968997
case opt_gc_threads:
969998
errno = 0;
970999
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
@@ -2575,7 +2575,7 @@ JL_DLLEXPORT ssize_t jl_sizeof_jl_options(void);
25752575
JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp);
25762576
JL_DLLEXPORT char *jl_format_filename(const char *output_pattern);
25772577

2578-
uint64_t parse_heap_size_hint(const char *optarg, const char *option_name);
2578+
uint64_t parse_heap_size_option(const char *optarg, const char *option_name);
25792579

25802580
// Set julia-level ARGS array according to the arguments provided in
25812581
// argc/argv

src/options.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@
7474
// GC_TIME prints time taken by each phase of GC
7575
// #define GC_TIME
7676

77+
#define GC_HARD_HEAP_LIMIT_FOR_UNSET_FLAG (UINT64_MAX)
78+
#define GC_HEAP_TARGET_INCREMENT_FOR_UNSET_FLAG (UINT64_MAX)
79+
7780
// pool allocator configuration options
7881

7982
// GC_SMALL_PAGE allocates objects in 4k pages

test/cmdlineargs.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,14 @@ 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+
# Set the hard heap limit to 100MB, try to allocate an array of 200MB
1220+
# and assert that the process is aborted, by checking the exit code.
1221+
cmd = `$(Base.julia_cmd()) --startup-file=no --hard-heap-limit=100M -e "a = Array{UInt8}(undef, 200*1024*1024); GC.gc()"`
1222+
exit_code = run(cmd)
1223+
@test exit_code != 0 # The process should be aborted due to the hard heap limit
1224+
end
12171225
end
12181226

12191227
## `Main.main` entrypoint
@@ -1253,6 +1261,38 @@ end
12531261
end
12541262
end
12551263

1264+
@testset "--hard-heap-limit" begin
1265+
exename = `$(Base.julia_cmd())`
1266+
@test errors_not_signals(`$exename --hard-heap-limit -e "exit(0)"`)
1267+
@testset "--hard-heap-limit=$str" for str in ["asdf","","0","1.2vb","b","GB","2.5GB̂","1.2gb2","42gigabytes","5gig","2GiB","NaNt"]
1268+
@test errors_not_signals(`$exename --hard-heap-limit=$str -e "exit(0)"`)
1269+
end
1270+
k = 1024
1271+
m = 1024k
1272+
g = 1024m
1273+
t = 1024g
1274+
@testset "--hard-heap-limit=$str" for (str, val) in [("1", 1), ("1e7", 1e7), ("2.5e7", 2.5e7), ("1MB", 1m), ("2.5g", 2.5g), ("1e4kB", 1e4k),
1275+
("1e100", typemax(UInt64)), ("1e500g", typemax(UInt64)), ("1e-12t", 1), ("500000000b", 500000000)]
1276+
@test parse(UInt64,read(`$exename --hard-heap-limit=$str -E "Base.JLOptions().hard_heap_limit"`, String)) == val
1277+
end
1278+
end
1279+
1280+
@testset "--heap-target-increment" begin
1281+
exename = `$(Base.julia_cmd())`
1282+
@test errors_not_signals(`$exename --heap-target-increment -e "exit(0)"`)
1283+
@testset "--heap-target-increment=$str" for str in ["asdf","","0","1.2vb","b","GB","2.5GB̂","1.2gb2","42gigabytes","5gig","2GiB","NaNt"]
1284+
@test errors_not_signals(`$exename --heap-target-increment=$str -e "exit(0)"`)
1285+
end
1286+
k = 1024
1287+
m = 1024k
1288+
g = 1024m
1289+
t = 1024g
1290+
@testset "--heap-target-increment=$str" for (str, val) in [("1", 1), ("1e7", 1e7), ("2.5e7", 2.5e7), ("1MB", 1m), ("2.5g", 2.5g), ("1e4kB", 1e4k),
1291+
("1e100", typemax(UInt64)), ("1e500g", typemax(UInt64)), ("1e-12t", 1), ("500000000b", 500000000)]
1292+
@test parse(UInt64,read(`$exename --heap-target-increment=$str -E "Base.JLOptions().heap_target_increment"`, String)) == val
1293+
end
1294+
end
1295+
12561296
@testset "--timeout-for-safepoint-straggler" begin
12571297
exename = `$(Base.julia_cmd())`
12581298
timeout = 120

0 commit comments

Comments
 (0)