Skip to content

AsyncDataloader: ClosedQueueError persists in 2.6.5 and can hang requests indefinitely (follow-up to #5654) #5671

Description

@vytautaskubilius

We hit the ClosedQueueError from #5654 on 2.6.5, which includes the #5656 fix - so it looks like the fix doesn't cover at least one code path. In our case the error isn't harmless: losing the ensure-block push means the run's accounting never balances, and the whole request hangs indefinitely.

Versions:

  • graphql 2.6.5
  • async 2.42.0
  • Rails 8.1.3 with config.active_support.isolation_level = :fiber
  • Ruby 3.4, unicorn (single-threaded workers)

A fan-out-heavy query spawns ~40 concurrent source/job tasks for us, which starves the ActiveRecord connection pool and produces 5s ActiveRecord::ConnectionTimeoutErrors inside source fetches. A task that gets woken from one of those waits after its round's queues have already been closed then fails the run.finished_tasks.push($! || task) in the ensure of spawn_source_task:

Async::Queue::ClosedError: Cannot enqueue items to a closed queue!
  async-2.42.0/lib/async/queue.rb:65 in 'Async::Queue#push'
  graphql-2.6.5/lib/graphql/dataloader/async_dataloader.rb:351 in 'block (2 levels) in GraphQL::Dataloader::AsyncDataloader#spawn_source_task'
  async-2.42.0/lib/async/task.rb:224 in 'block in Async::Task#run'

After that the run never completes - the root task waits forever and the reactor sits idle. We watched it for over two minutes with periodic backtrace dumps of the main thread, all identical:

async-2.42.0/lib/async/scheduler.rb:456:in 'IO::Event::Selector::EPoll#select'
...
async-2.42.0/lib/kernel/sync.rb:30:in 'Kernel#Sync'
graphql-2.6.5/lib/graphql/dataloader/async_dataloader.rb:197:in 'GraphQL::Dataloader::AsyncDataloader#run'
graphql-2.6.5/lib/graphql/execution/interpreter.rb:86:in 'block in GraphQL::Execution::Interpreter.run_all'

The race is timing-dependent: the exact same query against the same data sometimes completes in ~5s with the connection timeouts collected as field errors, and sometimes wedges as above. In production (unicorn with a 30s worker timeout) the wedged requests get killed silently, which is what we were originally debugging.

I don't have a minimal script, but the reproduction shape is: any query whose source fan-out exceeds the AR pool size produces late-finishing tasks via checkout timeouts, and each late task rolls the dice on this race - it triggers within a couple of attempts on our data. Happy to run patches or provide more diagnostics against our reproduction.

Pinning back to 2.6.3 avoids the problem entirely (verified A/B on the same environment).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions