@@ -558,6 +558,88 @@ describe("NodeRuntime resource budgets", () => {
558558 } ) ;
559559 } ) ;
560560
561+ // -----------------------------------------------------------------------
562+ // maxHandles
563+ // -----------------------------------------------------------------------
564+
565+ describe ( "maxHandles" , ( ) => {
566+ it ( "register maxHandles+1 handles — last one throws" , async ( ) => {
567+ const capture = createConsoleCapture ( ) ;
568+ proc = createTestNodeRuntime ( {
569+ onStdio : capture . onStdio ,
570+ resourceBudgets : { maxHandles : 3 } ,
571+ } ) ;
572+
573+ const result = await proc . exec ( `
574+ let succeeded = 0;
575+ let errors = 0;
576+ for (let i = 0; i < 5; i++) {
577+ try {
578+ _registerHandle('test:' + i, 'test handle ' + i);
579+ succeeded++;
580+ } catch (e) {
581+ if (e.message.includes('ERR_RESOURCE_BUDGET_EXCEEDED')) errors++;
582+ }
583+ }
584+ // Cleanup registered handles so sandbox can exit
585+ for (let i = 0; i < 3; i++) _unregisterHandle('test:' + i);
586+ console.log('succeeded:' + succeeded);
587+ console.log('errors:' + errors);
588+ ` ) ;
589+
590+ expect ( result . code ) . toBe ( 0 ) ;
591+ const out = capture . stdout ( ) ;
592+ expect ( out ) . toContain ( "succeeded:3" ) ;
593+ expect ( out ) . toContain ( "errors:2" ) ;
594+ } ) ;
595+
596+ it ( "register handles, remove some, register more — works up to cap" , async ( ) => {
597+ const capture = createConsoleCapture ( ) ;
598+ proc = createTestNodeRuntime ( {
599+ onStdio : capture . onStdio ,
600+ resourceBudgets : { maxHandles : 3 } ,
601+ } ) ;
602+
603+ const result = await proc . exec ( `
604+ let firstBatch = 0;
605+ let secondBatch = 0;
606+ let errors = 0;
607+
608+ // First batch: register 3 handles (fills cap)
609+ for (let i = 0; i < 3; i++) {
610+ try {
611+ _registerHandle('batch1:' + i, 'first batch ' + i);
612+ firstBatch++;
613+ } catch (e) { errors++; }
614+ }
615+
616+ // Remove first batch handles (frees slots)
617+ for (let i = 0; i < 3; i++) _unregisterHandle('batch1:' + i);
618+
619+ // Second batch: register 3 more (slots freed)
620+ for (let i = 0; i < 3; i++) {
621+ try {
622+ _registerHandle('batch2:' + i, 'second batch ' + i);
623+ secondBatch++;
624+ } catch (e) { errors++; }
625+ }
626+
627+ // Cleanup
628+ for (let i = 0; i < 3; i++) _unregisterHandle('batch2:' + i);
629+
630+ console.log('firstBatch:' + firstBatch);
631+ console.log('secondBatch:' + secondBatch);
632+ console.log('errors:' + errors);
633+ ` ) ;
634+
635+ expect ( result . code ) . toBe ( 0 ) ;
636+ const out = capture . stdout ( ) ;
637+ expect ( out ) . toContain ( "firstBatch:3" ) ;
638+ expect ( out ) . toContain ( "secondBatch:3" ) ;
639+ expect ( out ) . toContain ( "errors:0" ) ;
640+ } ) ;
641+ } ) ;
642+
561643 describe ( "host timer cleanup" , ( ) => {
562644 it ( "clears host timers on dispose after normal execution" , async ( ) => {
563645 proc = createTestNodeRuntime ( { } ) ;
0 commit comments