@@ -507,13 +507,14 @@ fiber_pool_allocate_memory(size_t * count, size_t stride)
507507
508508// Given an existing fiber pool, expand it by the specified number of stacks.
509509// @param count the maximum number of stacks to allocate.
510- // @return the allocated fiber pool .
510+ // @return the new allocation on success, or NULL on failure with errno set .
511511// @sa fiber_pool_allocation_free
512512static struct fiber_pool_allocation *
513513fiber_pool_expand (struct fiber_pool * fiber_pool , size_t count )
514514{
515515 struct fiber_pool_allocation * allocation ;
516- RB_VM_LOCK_ENTER ();
516+ unsigned int lev ;
517+ RB_VM_LOCK_ENTER_LEV (& lev );
517518 {
518519 STACK_GROW_DIR_DETECTION ;
519520
@@ -524,7 +525,11 @@ fiber_pool_expand(struct fiber_pool * fiber_pool, size_t count)
524525 void * base = fiber_pool_allocate_memory (& count , stride );
525526
526527 if (base == NULL ) {
527- rb_raise (rb_eFiberError , "can't alloc machine stack to fiber (%" PRIuSIZE " x %" PRIuSIZE " bytes): %s" , count , size , ERRNOMSG );
528+ int err = errno ;
529+ if (!err ) err = ENOMEM ;
530+ errno = err ;
531+ RB_VM_LOCK_LEAVE_LEV (& lev );
532+ return NULL ;
528533 }
529534
530535 struct fiber_pool_vacancy * vacancies = fiber_pool -> vacancies ;
@@ -553,18 +558,26 @@ fiber_pool_expand(struct fiber_pool * fiber_pool, size_t count)
553558 DWORD old_protect ;
554559
555560 if (!VirtualProtect (page , RB_PAGE_SIZE , PAGE_READWRITE | PAGE_GUARD , & old_protect )) {
561+ int err = errno ;
562+ if (!err ) err = ENOMEM ;
556563 VirtualFree (allocation -> base , 0 , MEM_RELEASE );
557564 ruby_xfree (allocation );
558- rb_raise (rb_eFiberError , "can't set a guard page: %s" , ERRNOMSG );
565+ errno = err ;
566+ RB_VM_LOCK_LEAVE_LEV (& lev );
567+ return NULL ;
559568 }
560569#elif defined(__wasi__ )
561570 // wasi-libc's mprotect emulation doesn't support PROT_NONE.
562571 (void )page ;
563572#else
564573 if (mprotect (page , RB_PAGE_SIZE , PROT_NONE ) < 0 ) {
574+ int err = errno ;
575+ if (!err ) err = ENOMEM ;
565576 munmap (allocation -> base , count * stride );
566577 ruby_xfree (allocation );
567- rb_raise (rb_eFiberError , "can't set a guard page: %s" , ERRNOMSG );
578+ errno = err ;
579+ RB_VM_LOCK_LEAVE_LEV (& lev );
580+ return NULL ;
568581 }
569582#endif
570583
@@ -594,7 +607,7 @@ fiber_pool_expand(struct fiber_pool * fiber_pool, size_t count)
594607 fiber_pool -> vacancies = vacancies ;
595608 fiber_pool -> count += count ;
596609 }
597- RB_VM_LOCK_LEAVE ( );
610+ RB_VM_LOCK_LEAVE_LEV ( & lev );
598611
599612 return allocation ;
600613}
@@ -616,7 +629,10 @@ fiber_pool_initialize(struct fiber_pool * fiber_pool, size_t size, size_t count,
616629
617630 fiber_pool -> vm_stack_size = vm_stack_size ;
618631
619- fiber_pool_expand (fiber_pool , count );
632+ if (RB_UNLIKELY (!fiber_pool_expand (fiber_pool , count ))) {
633+ rb_raise (rb_eFiberError , "can't alloc machine stack to fiber (%" PRIuSIZE " x %" PRIuSIZE " bytes): %s" ,
634+ count , fiber_pool -> size , strerror (errno ));
635+ }
620636}
621637
622638#ifdef FIBER_POOL_ALLOCATION_FREE
@@ -664,42 +680,72 @@ fiber_pool_allocation_free(struct fiber_pool_allocation * allocation)
664680}
665681#endif
666682
683+ // Number of stacks to request when expanding the pool (clamped to min/max).
684+ static inline size_t
685+ fiber_pool_stack_expand_count (const struct fiber_pool * pool )
686+ {
687+ size_t count = pool -> count ;
688+ if (count > FIBER_POOL_ALLOCATION_MAXIMUM_SIZE ) count = FIBER_POOL_ALLOCATION_MAXIMUM_SIZE ;
689+ if (count < pool -> initial_count ) count = pool -> initial_count ;
690+ return count ;
691+ }
692+
693+ // When the vacancy list is empty, grow the pool (and run GC only if mmap fails). Caller holds the VM lock.
694+ // Returns NULL if expansion failed after GC + retry; errno is set. Otherwise returns a vacancy.
695+ static struct fiber_pool_vacancy *
696+ fiber_pool_stack_acquire_expand (struct fiber_pool * fiber_pool )
697+ {
698+ size_t count = fiber_pool_stack_expand_count (fiber_pool );
699+
700+ if (DEBUG ) fprintf (stderr , "fiber_pool_stack_acquire: expanding fiber pool to %" PRIuSIZE " stacks\n" , count );
701+
702+ struct fiber_pool_vacancy * vacancy = NULL ;
703+
704+ if (RB_LIKELY (fiber_pool_expand (fiber_pool , count ))) {
705+ return fiber_pool_vacancy_pop (fiber_pool );
706+ } else {
707+ if (DEBUG ) fprintf (stderr , "fiber_pool_stack_acquire: expand failed (%s), collecting garbage\n" , strerror (errno ));
708+
709+ rb_gc ();
710+
711+ // After running GC, the vacancy list may have some stacks:
712+ vacancy = fiber_pool_vacancy_pop (fiber_pool );
713+ if (RB_LIKELY (vacancy )) {
714+ return vacancy ;
715+ }
716+
717+ // Try to expand the fiber pool again:
718+ if (RB_LIKELY (fiber_pool_expand (fiber_pool , count ))) {
719+ return fiber_pool_vacancy_pop (fiber_pool );
720+ } else {
721+ return NULL ;
722+ }
723+ }
724+ }
725+
667726// Acquire a stack from the given fiber pool. If none are available, allocate more.
668727static struct fiber_pool_stack
669728fiber_pool_stack_acquire (struct fiber_pool * fiber_pool )
670729{
671730 struct fiber_pool_vacancy * vacancy ;
672731
673- RB_VM_LOCK_ENTER ();
732+ unsigned int lev ;
733+ RB_VM_LOCK_ENTER_LEV (& lev );
674734 {
735+ // Fast path: try to acquire a stack from the vacancy list:
675736 vacancy = fiber_pool_vacancy_pop (fiber_pool );
676737
677738 if (DEBUG ) fprintf (stderr , "fiber_pool_stack_acquire: %p used=%" PRIuSIZE "\n" , (void * )fiber_pool -> vacancies , fiber_pool -> used );
678739
679- // During allocation, if we run out of stacks, we may need to collect garbage to free up some stacks before trying to allocate more.
680- if (!vacancy ) {
681- if (DEBUG ) fprintf (stderr , "fiber_pool_stack_acquire: no stacks available, collecting garbage\n" );
682- rb_gc ();
683-
684- // In the worst case, we garbage collect, but all fibers are reachable, so we don't free any stacks. We will try to expand the fiber pool:
685- vacancy = fiber_pool_vacancy_pop (fiber_pool );
686- }
687-
688- if (!vacancy ) {
689- const size_t maximum = FIBER_POOL_ALLOCATION_MAXIMUM_SIZE ;
690- const size_t minimum = fiber_pool -> initial_count ;
691-
692- size_t count = fiber_pool -> count ;
693- if (count > maximum ) count = maximum ;
694- if (count < minimum ) count = minimum ;
740+ // Slow path: If the pool has no vacancies, expand first. Only run GC when expansion fails (e.g. mmap), so we can reclaim stacks from dead fibers before retrying:
741+ if (RB_UNLIKELY (!vacancy )) {
742+ vacancy = fiber_pool_stack_acquire_expand (fiber_pool );
695743
696- if (DEBUG ) fprintf (stderr , "fiber_pool_stack_acquire: expanding fiber pool to %" PRIuSIZE " stacks\n" , count );
697- fiber_pool_expand (fiber_pool , count );
698-
699- // The free list should now contain some stacks:
700- VM_ASSERT (fiber_pool -> vacancies );
701-
702- vacancy = fiber_pool_vacancy_pop (fiber_pool );
744+ // If expansion failed, raise an error:
745+ if (RB_UNLIKELY (!vacancy )) {
746+ RB_VM_LOCK_LEAVE_LEV (& lev );
747+ rb_raise (rb_eFiberError , "can't allocate fiber stack: %s" , strerror (errno ));
748+ }
703749 }
704750
705751 VM_ASSERT (vacancy );
@@ -718,7 +764,7 @@ fiber_pool_stack_acquire(struct fiber_pool * fiber_pool)
718764
719765 fiber_pool_stack_reset (& vacancy -> stack );
720766 }
721- RB_VM_LOCK_LEAVE ( );
767+ RB_VM_LOCK_LEAVE_LEV ( & lev );
722768
723769 return vacancy -> stack ;
724770}
0 commit comments