@@ -28,8 +28,10 @@ use prost::Message;
2828use rspace_plus_plus:: rspace:: util:: unpack_option_with_peek;
2929use std:: collections:: { BTreeMap , BTreeSet } ;
3030use std:: collections:: { HashMap , HashSet } ;
31+ use std:: future:: Future ;
3132use std:: pin:: Pin ;
3233use std:: sync:: { Arc , RwLock } ;
34+ use std:: task:: { Context , Poll } ;
3335
3436use crate :: rust:: interpreter:: accounting:: costs:: {
3537 add_cost, bytes_to_hex_cost, diff_cost, hex_to_bytes_cost, interpolate_cost, keys_method_cost,
@@ -57,6 +59,46 @@ use super::unwrap_option_safe;
5759use super :: util:: GeneratedMessage ;
5860use models:: rust:: pathmap_crate_type_mapper:: PathMapCrateTypeMapper ;
5961
62+ /// Minimum remaining stack space (in bytes) before growing.
63+ /// When the current stack has less than this amount remaining, a new stack segment is allocated.
64+ // 128 KB is too small: a single recursion frame in the Rholang interpreter
65+ // (eval → produce/consume → dispatch → eval) consumes more than 128 KB between
66+ // stacker checks, so the overflow happens before stacker can grow the stack.
67+ const STACK_RED_ZONE : usize = 1024 * 1024 ; // 1 MB
68+
69+ /// Size of each new stack segment allocated when the red zone is reached.
70+ const STACK_GROW_SIZE : usize = 2 * 1024 * 1024 ; // 2 MB
71+
72+ /// A Future wrapper that dynamically grows the thread stack during polling.
73+ ///
74+ /// The Rholang interpreter uses deep async recursion: eval → produce/consume → dispatch → eval.
75+ /// Each poll of this recursive future chain adds stack frames. In debug builds, unoptimized
76+ /// async state machines consume ~1-2KB per recursion level, causing stack overflow with the
77+ /// default 2MB thread stack.
78+ ///
79+ /// `StackGrowingFuture` wraps each recursive entry point (eval, produce, consume, dispatch).
80+ /// On each poll, `stacker::maybe_grow` checks remaining stack space. If below STACK_RED_ZONE,
81+ /// it allocates a new STACK_GROW_SIZE segment and runs the poll there. This allows arbitrarily
82+ /// deep Rholang recursion (e.g., longslow.rho with 32768 iterations) without stack overflow.
83+ ///
84+ /// See: https://github.com/F1R3FLY-io/f1r3node/issues/305
85+ /// See: https://github.com/F1R3FLY-io/f1r3node/issues/306
86+ struct StackGrowingFuture < F > {
87+ inner : F ,
88+ }
89+
90+ impl < F : Future > Future for StackGrowingFuture < F > {
91+ type Output = F :: Output ;
92+
93+ fn poll ( self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < Self :: Output > {
94+ // SAFETY: Structural pin projection on a single-field struct with no Drop impl.
95+ // `inner` is only accessed through this pinned projection, and StackGrowingFuture
96+ // does not implement Unpin when F doesn't, preserving pin guarantees.
97+ let inner = unsafe { self . map_unchecked_mut ( |s| & mut s. inner ) } ;
98+ stacker:: maybe_grow ( STACK_RED_ZONE , STACK_GROW_SIZE , || inner. poll ( cx) )
99+ }
100+ }
101+
60102/**
61103 * Reduce is the interface for evaluating Rholang expressions.
62104 */
@@ -95,7 +137,7 @@ impl DebruijnInterpreter {
95137 env : & ' a Env < Par > ,
96138 rand : Blake2b512Random ,
97139 ) -> Pin < Box < dyn std:: future:: Future < Output = Result < ( ) , InterpreterError > > + std:: marker:: Send + ' a > > {
98- Box :: pin ( self . eval_inner ( par, env, rand) )
140+ Box :: pin ( StackGrowingFuture { inner : self . eval_inner ( par, env, rand) } )
99141 }
100142
101143 async fn eval_inner (
@@ -212,7 +254,7 @@ impl DebruijnInterpreter {
212254 data : ListParWithRandom ,
213255 persistent : bool ,
214256 ) -> Pin < Box < dyn std:: future:: Future < Output = Result < DispatchType , InterpreterError > > + std:: marker:: Send + ' a > > {
215- Box :: pin ( self . produce_inner ( chan, data, persistent) )
257+ Box :: pin ( StackGrowingFuture { inner : self . produce_inner ( chan, data, persistent) } )
216258 }
217259
218260 async fn produce_inner (
@@ -283,7 +325,7 @@ impl DebruijnInterpreter {
283325 persistent : bool ,
284326 peek : bool ,
285327 ) -> Pin < Box < dyn std:: future:: Future < Output = Result < DispatchType , InterpreterError > > + std:: marker:: Send + ' a > > {
286- Box :: pin ( self . consume_inner ( binds, body, persistent, peek) )
328+ Box :: pin ( StackGrowingFuture { inner : self . consume_inner ( binds, body, persistent, peek) } )
287329 }
288330
289331 async fn consume_inner (
@@ -543,7 +585,7 @@ impl DebruijnInterpreter {
543585 is_replay : bool ,
544586 previous_output : Vec < Par > ,
545587 ) -> Pin < Box < dyn std:: future:: Future < Output = Result < DispatchType , InterpreterError > > + std:: marker:: Send + ' a > > {
546- Box :: pin ( self . dispatch_inner ( continuation, data_list, is_replay, previous_output) )
588+ Box :: pin ( StackGrowingFuture { inner : self . dispatch_inner ( continuation, data_list, is_replay, previous_output) } )
547589 }
548590
549591 async fn dispatch_inner (
0 commit comments