fix(elc): add el_mem_check() memory guard — abort before OS OOM-kill
Add el_mem_check() to el_runtime.c: reads ELC_MAX_MEM_MB (default 512), checks RSS via getrusage (macOS bytes / Linux KB normalised to MB), prints a clear diagnostic to stderr and exits(1) if exceeded. Wire it into two places: - compiler.el: upfront check at --emit-header entry point - codegen.el: per-function check in the streaming loop after each el_arena_pop, so runaway growth is caught at the earliest function boundary rather than after the machine is already dying.
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/resource.h> /* getrusage — memory guard */
|
||||
#ifdef HAVE_CURL
|
||||
#include <curl/curl.h>
|
||||
#endif
|
||||
@@ -5674,6 +5675,50 @@ el_val_t getpid_now(void) {
|
||||
return (el_val_t)getpid();
|
||||
}
|
||||
|
||||
/* el_mem_check — self-terminating memory guard for long-running compiler runs.
|
||||
*
|
||||
* Call this periodically (e.g. after each function compiled) to detect runaway
|
||||
* memory growth before the OS OOM-killer fires. Reads the limit from the env
|
||||
* var ELC_MAX_MEM_MB (default 512 MB). If resident set size exceeds the limit,
|
||||
* prints a diagnostic to stderr and exits with code 1 so the caller (elb or a
|
||||
* CI script) can handle the failure gracefully instead of having the whole
|
||||
* machine go down.
|
||||
*
|
||||
* Platform notes:
|
||||
* macOS — ru_maxrss is in bytes.
|
||||
* Linux — ru_maxrss is in kilobytes.
|
||||
* We normalise to MB before comparing.
|
||||
*
|
||||
* Returns 0 always (the only non-return path is the exit() branch).
|
||||
*/
|
||||
el_val_t el_mem_check(void) {
|
||||
/* Read limit from env; default 512 MB. */
|
||||
long limit_mb = 512;
|
||||
const char *env_val = getenv("ELC_MAX_MEM_MB");
|
||||
if (env_val && *env_val) {
|
||||
long v = atol(env_val);
|
||||
if (v > 0) limit_mb = v;
|
||||
}
|
||||
|
||||
struct rusage ru;
|
||||
if (getrusage(RUSAGE_SELF, &ru) != 0) return 0; /* can't read — skip check */
|
||||
|
||||
long rss_mb;
|
||||
#if defined(__APPLE__) || defined(__MACH__)
|
||||
/* macOS: ru_maxrss is bytes */
|
||||
rss_mb = (long)(ru.ru_maxrss / (1024L * 1024L));
|
||||
#else
|
||||
/* Linux: ru_maxrss is kilobytes */
|
||||
rss_mb = (long)(ru.ru_maxrss / 1024L);
|
||||
#endif
|
||||
|
||||
if (rss_mb >= limit_mb) {
|
||||
fprintf(stderr, "elc: memory limit exceeded (%ldMB), aborting\n", limit_mb);
|
||||
exit(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── args() — command-line argument access ──────────────────────────────────
|
||||
* Compiled El programs call args() to get a list of CLI arguments.
|
||||
* Call el_runtime_init_args(argc, argv) at the start of C main() to populate.
|
||||
|
||||
@@ -531,6 +531,12 @@ el_val_t parse_int(el_val_t s, el_val_t default_val);
|
||||
el_val_t exit_program(el_val_t code);
|
||||
el_val_t getpid_now(void);
|
||||
|
||||
/* Self-terminating memory guard. Reads ELC_MAX_MEM_MB (default 512) and
|
||||
* exits with code 1 if resident memory exceeds the limit. Call periodically
|
||||
* during long compilation loops (e.g. after each function is compiled).
|
||||
* Returns 0 when memory is within bounds. */
|
||||
el_val_t el_mem_check(void);
|
||||
|
||||
/* ── CGI identity ─────────────────────────────────────────────────────────────
|
||||
* Called at the start of main() in CGI programs (those with a `cgi {}` block).
|
||||
* Records the program's DHARMA identity before any other code executes. */
|
||||
|
||||
@@ -3730,6 +3730,7 @@ fn codegen_streaming(tokens: [Any], sigs: [Map<String, Any>], source: String) ->
|
||||
cg_fn(stmt)
|
||||
el_release(stmt)
|
||||
el_arena_pop(fn_arena_mark)
|
||||
el_mem_check()
|
||||
}
|
||||
} else {
|
||||
if is_top_level_decl(stmt) {
|
||||
|
||||
@@ -571,6 +571,7 @@ fn main() -> Void {
|
||||
// BinOp chains (e.g. checkout.el) — parse() builds O(whole-program AST)
|
||||
// while scan_fn_sigs_el keeps peak memory at O(tokens).
|
||||
if do_emit_header {
|
||||
el_mem_check()
|
||||
let raw_source: String = fs_read(src_path)
|
||||
let hdr_tokens: [Any] = lex(raw_source)
|
||||
let hdr_sigs: [Map<String, Any>] = scan_fn_sigs_el(hdr_tokens)
|
||||
|
||||
Reference in New Issue
Block a user