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

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions