Skip to content

Commit 804bb12

Browse files
Khaclaude
andauthored
fix: run shell main on a thread with the configured stack size (#14343)
This PR fixes the new 1GB stack size not being used for the main `lean` thread itself, e.g. for serialization or `--run`. Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0d00a10 commit 804bb12

5 files changed

Lines changed: 68 additions & 17 deletions

File tree

src/runtime/thread.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,7 @@ extern "C" LEAN_EXPORT lean_obj_res lean_internal_set_thread_stack_size(size_t s
167167
return lean_box(0);
168168
}
169169

170-
extern "C" LEAN_EXPORT lean_object * lean_run_main(lean_object * (*main_fn)(int, char **), int argc, char ** argv) {
171-
#ifdef LEAN_MULTI_THREAD
170+
LEAN_EXPORT void set_thread_stack_size_from_env() {
172171
const char * stack_size_env = std::getenv("LEAN_STACK_SIZE_KB");
173172
if (stack_size_env) {
174173
size_t sz = std::strtoull(stack_size_env, nullptr, 10);
@@ -177,18 +176,24 @@ extern "C" LEAN_EXPORT lean_object * lean_run_main(lean_object * (*main_fn)(int,
177176
lthread::set_thread_stack_size(sz);
178177
}
179178
}
179+
}
180+
181+
LEAN_EXPORT void run_with_thread_stack(std::function<void()> const & fn) {
180182
const char * use_thread_env = std::getenv("LEAN_MAIN_USE_THREAD");
181183
if (use_thread_env && std::strcmp(use_thread_env, "0") == 0) {
182-
return main_fn(argc, argv);
184+
fn();
185+
} else {
186+
// Start new thread to use given/default stack size
187+
lthread t(fn);
188+
t.join();
183189
}
184-
// Start new thread to use given/default stack size
190+
}
191+
192+
extern "C" LEAN_EXPORT lean_object * lean_run_main(lean_object * (*main_fn)(int, char **), int argc, char ** argv) {
193+
set_thread_stack_size_from_env();
185194
lean_object * res = nullptr;
186-
lthread t([&]() { res = main_fn(argc, argv); });
187-
t.join();
195+
run_with_thread_stack([&]() { res = main_fn(argc, argv); });
188196
return res;
189-
#else
190-
return main_fn(argc, argv);
191-
#endif
192197
}
193198

194199
LEAN_THREAD_VALUE(bool, g_finalizing, false);

src/runtime/thread.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,4 +267,6 @@ LEAN_EXPORT void register_thread_local_reset_fn(std::function<void()> fn);
267267
We invoke this function before processing a command
268268
and before executing a task. */
269269
LEAN_EXPORT void reset_thread_local();
270+
LEAN_EXPORT void run_with_thread_stack(std::function<void()> const & fn);
271+
LEAN_EXPORT void set_thread_stack_size_from_env();
270272
}

src/util/shell.cpp

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,10 @@ extern "C" LEAN_EXPORT int lean_main(int argc, char ** argv) {
321321
}
322322
consume_io_result(lean_enable_initializer_execution());
323323

324+
// Default the configured thread stack size from the environment as in `lean_run_main`;
325+
// `--tstack` below overrides it.
326+
set_thread_stack_size_from_env();
327+
324328
int rc;
325329
object_ref shell_opts;
326330
try {
@@ -348,12 +352,16 @@ extern "C" LEAN_EXPORT int lean_main(int argc, char ** argv) {
348352

349353
scoped_task_manager scope_task_man(get_shell_num_threads(shell_opts));
350354

351-
try {
352-
return run_shell_main(argc - optind, argv + optind, shell_opts);
353-
} catch (lean::throwable & ex) {
354-
std::cerr << ex.what() << "\n";
355-
} catch (std::bad_alloc & ex) {
356-
std::cerr << "out of memory" << std::endl;
357-
}
358-
return 1;
355+
int shell_rc = 1;
356+
// Do not rely on OS thread stack size, as for Lean executables
357+
run_with_thread_stack([&]() {
358+
try {
359+
shell_rc = run_shell_main(argc - optind, argv + optind, shell_opts);
360+
} catch (lean::throwable & ex) {
361+
std::cerr << ex.what() << "\n";
362+
} catch (std::bad_alloc & ex) {
363+
std::cerr << "out of memory" << std::endl;
364+
}
365+
});
366+
return shell_rc;
359367
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Lean.CompactedRegion
2+
3+
open Lean
4+
5+
/-!
6+
Regression test that the `lean` driver runs its main logic on a thread with the configured
7+
Lean thread stack size instead of directly on the OS main thread, whose fixed default stack
8+
(often 8 MiB) is too small for the runtime's recursive algorithms. Exercises two stack-hungry
9+
paths that run on the driver's main thread: interpreting a non-tail-recursive function at a
10+
depth beyond the OS default stack's interpreter headroom, and compacting a deeply nested
11+
object graph with `CompactedRegion.save`, whose compactor recurses once per nesting level.
12+
-/
13+
14+
def depth : Nat := 100000
15+
16+
def deepRec : Nat → Nat
17+
| 0 => 0
18+
| n+1 => 1 + deepRec n
19+
20+
def mkDeepList (n : Nat) : List Nat := Id.run do
21+
let mut xs := []
22+
for i in [0:n] do
23+
xs := i :: xs
24+
return xs
25+
26+
unsafe def main : IO UInt32 := do
27+
unless deepRec depth == depth do
28+
throw <| IO.userError "deepRec mismatch"
29+
let tmpFile : System.FilePath := "./_tmp_shell_thread_stack.olean"
30+
let xs := mkDeepList (4 * depth)
31+
let _ ← CompactedRegion.save tmpFile `ShellThreadStack xs #[] none
32+
let (ys, _region) ← CompactedRegion.read (α := List Nat) tmpFile #[]
33+
unless ys.length == 4 * depth do
34+
throw <| IO.userError s!"round-trip length mismatch: expected {4 * depth}, got {ys.length}"
35+
IO.FS.removeFile tmpFile
36+
return 0

tests/compile/shell_thread_stack.lean.no_compile

Whitespace-only changes.

0 commit comments

Comments
 (0)