Skip to content

Commit 45a6df3

Browse files
Only gc on allocation failure.
1 parent bb47c82 commit 45a6df3

1 file changed

Lines changed: 78 additions & 32 deletions

File tree

cont.c

Lines changed: 78 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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
512512
static struct fiber_pool_allocation *
513513
fiber_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.
668727
static struct fiber_pool_stack
669728
fiber_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

Comments
 (0)