Skip to content

Let Queue#pop and SizedQueue#push wait through the fiber scheduler - #9805

Open
sampokuokkanen wants to merge 3 commits into
jruby:masterfrom
sampokuokkanen:scheduler-queue-pop
Open

sampokuokkanen wants to merge 3 commits into
jruby:masterfrom
sampokuokkanen:scheduler-queue-pop

Conversation

@sampokuokkanen

Copy link
Copy Markdown
Contributor

Under a fiber scheduler, Queue#pop on an empty queue and SizedQueue#push on a full one blocked the whole thread. The fiber that would fill or drain the queue never got to run, so the thread hung for good.

Both now block through the scheduler, the way MRI's queue_sleep does. Without a scheduler, the extra cost is one read of a null field. The waiter list is only created on first use, like the Mutex code in #9790.

This branch includes #9791's commit as I'm touching the same file and rows here and it has already been approved.

These async and io-event tests used to hang, and now they pass:

The code at some places feels a bit repetive, but it's repetitive also on MRI, but might be worth a refactor once we get more tests passing.

The timed poll loop only checked the item count, so the signal from
closeInternal woke the waiter, it found the queue still empty, and went
back to awaitNanos until the full timeout passed. A closed, empty queue
now returns nil at once and passes the wakeup on to the next consumer,
as takeInternal already does for the untimed pop.
Queue#pop on an empty queue parked the whole thread. Under a fiber
scheduler that meant the fiber that would push into the queue never got
to run. io-event and async hand results between fibers this way.

Fibers now block through the scheduler, as MRI's queue_sleep does. Like
MRI, a push wakes one waiting fiber and close wakes them all. A woken
fiber checks the queue again and blocks again if someone else got the
item first. num_waiting counts these fibers too.

SizedQueue#push still blocks the thread; that comes next.
SizedQueue#push on a full queue parked the whole thread, so under a fiber
scheduler the fiber that would pop never got to run.

Pushing fibers now block through the scheduler the same way Queue#pop
does. A pop wakes one of them; clear, close and max= wake them all, as in
MRI. num_waiting counts them.

If the queue is closed while a fiber waits to push, it raises
ClosedQueueError. Without that check the fiber would retry forever,
because a full queue is reported before a closed one.
@headius

headius commented Oct 15, 2026

Copy link
Copy Markdown
Member

Thank you! I'll give this a review soon (traveling until Saturday).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants