Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ gem 'stackprof', platform: :ruby
gem 'pry'
gem 'pry-stack_explorer', platform: :ruby

if RUBY_VERSION >= "3.0"
gem "libev_scheduler"
gem "evt"
end

if RUBY_VERSION >= "3.2.0"
gem "async", "~>2.0"
gem "minitest-mock"
Expand Down
2 changes: 0 additions & 2 deletions gemfiles/mongoid_8.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ gem "ruby-prof", platform: :ruby
gem "pry"
gem "pry-stack_explorer", platform: :ruby
gem "mongoid", "~> 8.0"
gem "libev_scheduler"
gem "evt"
gem "async"
gem "concurrent-ruby", "1.3.4"
gem "minitest-mock"
Expand Down
2 changes: 0 additions & 2 deletions gemfiles/mongoid_9.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ gem "ruby-prof", platform: :ruby
gem "pry"
gem "pry-stack_explorer", platform: :ruby
gem "mongoid", "~> 9.0"
gem "libev_scheduler"
gem "evt"
gem "async"
gem "minitest-mock"

Expand Down
1 change: 0 additions & 1 deletion gemfiles/rails_7.2_postgresql.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ gem "pry-stack_explorer", platform: :ruby
gem "rails", "~> 7.2.0", require: "rails/all"
gem "pg", platform: :ruby
gem "sequel"
gem "evt"
gem "async"
gem "libev_scheduler"
gem "google-protobuf"
Expand Down
1 change: 0 additions & 1 deletion gemfiles/rails_8.0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ gem "rails", "~> 8.0.0", require: "rails/all"
gem "sqlite3"
gem "pg", platform: :ruby
gem "sequel"
gem "evt"
gem "async"
gem "google-protobuf"
gem "minitest-mock"
Expand Down
1 change: 0 additions & 1 deletion gemfiles/rails_8.1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ gem "rails", "~> 8.1.0", require: "rails/all"
gem "sqlite3"
gem "pg", platform: :ruby
gem "sequel"
gem "evt"
gem "async"
gem "google-protobuf"
gem "minitest-mock"
Expand Down
1 change: 0 additions & 1 deletion gemfiles/rails_master.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ gem "rails", github: "rails/rails", require: "rails/all", ref: "main"
gem 'sqlite3'
gem 'pg'
gem "sequel"
gem "evt"
if RUBY_ENGINE == "ruby" # This doesn't work on truffle-ruby because there's no `IO::READABLE`
gem "libev_scheduler"
end
Expand Down
4 changes: 2 additions & 2 deletions lib/graphql/analysis/query_complexity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ def result
future_complexity
end
when nil
subject.logger.warn <<~GRAPHQL
subject.logger.warn <<~MESSAGE
GraphQL-Ruby's complexity cost system is getting some "breaking fixes" in a future version. See the migration notes at https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#complexity_cost_calculation_mode_for-class_method

To opt into the future behavior, configure your schema (#{subject.schema.name ? subject.schema.name : subject.schema.ancestors}) with:

complexity_cost_calculation_mode(:future) # or `:legacy`, `:compare`

GRAPHQL
MESSAGE
max_possible_complexity(mode: :legacy)
else
raise ArgumentError, "Expected `:future`, `:legacy`, `:compare`, or `nil` from `#{query.schema}.complexity_cost_calculation_mode_for` but got: #{query.schema.complexity_cost_calculation_mode.inspect}"
Expand Down
108 changes: 75 additions & 33 deletions lib/graphql/dataloader/async_dataloader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ class AsyncDataloader < Dataloader
def yield(source = Fiber[:__graphql_current_dataloader_source])
trace = Fiber[:__graphql_current_multiplex]&.current_trace
trace&.dataloader_fiber_yield(source)
if (condition = Fiber[:graphql_dataloader_next_tick])
condition.wait
else
Fiber.yield
end
Fiber[:graphql_dataloader_next_tick].wait
trace&.dataloader_fiber_resume(source)
nil
end
Expand All @@ -22,64 +18,106 @@ def run(trace_query_lazy: nil)
source_tasks = []
next_source_tasks = []
first_pass = true

sources_condition = Async::Condition.new
manager = spawn_fiber do
trace&.begin_dataloader(self)
jobs_condition = Async::Condition.new
trace&.begin_dataloader(self)
fiber_vars = get_fiber_variables
raised_error = nil
Sync do |root_task|
while first_pass || !job_fibers.empty?
first_pass = false
fiber_vars = get_fiber_variables

run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace)
set_fiber_variables(fiber_vars)
run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace, root_task, jobs_condition)

Sync do |root_task|
set_fiber_variables(fiber_vars)
while !source_tasks.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
while (task = (source_tasks.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size + next_source_tasks.size) < total_fiber_limit) && spawn_source_task(root_task, sources_condition, trace))))
if task.alive?
root_task.yield # give the source task a chance to run
next_source_tasks << task
end
while !source_tasks.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
while (task = (source_tasks.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size + next_source_tasks.size) < total_fiber_limit) && spawn_source_task(root_task, sources_condition, trace))))
if task.alive?
root_task.yield
next_source_tasks << task
else
task.wait # re-raise errors
end
sources_condition.signal
source_tasks.concat(next_source_tasks)
next_source_tasks.clear
end

sources_condition.signal
source_tasks.concat(next_source_tasks)
next_source_tasks.clear
end
jobs_condition.signal

if !@lazies_at_depth.empty?
with_trace_query_lazy(trace_query_lazy) do
run_next_pending_lazies(job_fibers, trace)
run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace)
run_next_pending_lazies(job_fibers, trace, root_task, jobs_condition)
run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace, root_task, jobs_condition)
end
end
end
trace&.end_dataloader(self)
rescue StandardError => err
raised_error = err
end

manager.resume
if manager.alive?
raise "Invariant: Manager didn't terminate successfully: #{manager}"
if raised_error
raise raised_error
end
trace&.end_dataloader(self)

rescue UncaughtThrowError => e
throw e.tag, e.value
end

private

def run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace)
while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_fiber(trace))))
def run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace, parent_task, condition)
while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_task(trace, parent_task, condition))))
if f.alive?
finished = run_fiber(f)
if !finished
next_job_fibers << f
end
parent_task.yield
next_job_fibers << f
else
f.wait # re-raise errors
end
end
job_fibers.concat(next_job_fibers)
next_job_fibers.clear
end

def spawn_job_task(trace, parent_task, condition)
if !@pending_jobs.empty?
fiber_vars = get_fiber_variables
parent_task.async do
trace&.dataloader_spawn_execution_fiber(@pending_jobs)
Fiber[:graphql_dataloader_next_tick] = condition
set_fiber_variables(fiber_vars)
while job = @pending_jobs.shift
job.call
end
cleanup_fiber
trace&.dataloader_fiber_exit
end
end
end

#### TODO DRY Had to duplicate to remove spawn_job_fiber
def run_next_pending_lazies(job_fibers, trace, parent_task, condition)
smallest_depth = nil
@lazies_at_depth.each_key do |depth_key|
smallest_depth ||= depth_key
if depth_key < smallest_depth
smallest_depth = depth_key
end
end

if smallest_depth
lazies = @lazies_at_depth.delete(smallest_depth)
if !lazies.empty?
lazies.each_with_index do |l, idx|
append_job { l.value }
end
job_fibers.unshift(spawn_job_task(trace, parent_task, condition))
end
end
end

def spawn_source_task(parent_task, condition, trace)
pending_sources = nil
@source_cache.each_value do |source_by_batch_params|
Expand All @@ -102,6 +140,10 @@ def spawn_source_task(parent_task, condition, trace)
s.run_pending_keys
trace&.end_dataloader_source(s)
end
nil
rescue StandardError => err
err
ensure
cleanup_fiber
trace&.dataloader_fiber_exit
end
Expand Down
14 changes: 0 additions & 14 deletions spec/graphql/dataloader/nonblocking_dataloader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -243,19 +243,5 @@ def self.included(child_class)
let(:scheduler_class) { ::DummyScheduler }
include NonblockingDataloaderAssertions
end

if RUBY_ENGINE == "ruby" && !ENV["GITHUB_ACTIONS"]
describe "With libev_scheduler" do
require "libev_scheduler"
let(:scheduler_class) { Libev::Scheduler }
include NonblockingDataloaderAssertions
end

describe "with evt" do
require "evt"
let(:scheduler_class) { Evt::Scheduler }
include NonblockingDataloaderAssertions
end
end
end
end
44 changes: 20 additions & 24 deletions spec/graphql/dataloader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1191,17 +1191,34 @@ def assert_last_max_fiber_count(expected_last_max_fiber_count, message = nil)

res = schema.execute(query_str, context: { dataloader: fiber_counting_dataloader_class.new })
assert_nil res.context.dataloader.fiber_limit
assert_equal 10, FiberCounting.last_spawn_fiber_count

extra_shortlived_jobs_fibers = if fiber_counting_dataloader_class < GraphQL::Dataloader::AsyncDataloader
3
else
0
end
assert_equal 10 + extra_shortlived_jobs_fibers, FiberCounting.last_spawn_fiber_count
assert_last_max_fiber_count(9, "No limit works as expected")

extra_shortlived_jobs_fibers = if fiber_counting_dataloader_class < GraphQL::Dataloader::AsyncDataloader
10 # more here because there are fewer jobs fibers running at any one time
else
0
end
res = schema.execute(query_str, context: { dataloader: fiber_counting_dataloader_class.new(fiber_limit: 4) })
assert_equal 4, res.context.dataloader.fiber_limit
assert_equal 12, FiberCounting.last_spawn_fiber_count
assert_equal 12 + extra_shortlived_jobs_fibers, FiberCounting.last_spawn_fiber_count
assert_last_max_fiber_count(4, "Limit of 4 works as expected")

extra_shortlived_jobs_fibers = if fiber_counting_dataloader_class < GraphQL::Dataloader::AsyncDataloader
4
else
0
end

res = schema.execute(query_str, context: { dataloader: fiber_counting_dataloader_class.new(fiber_limit: 6) })
assert_equal 6, res.context.dataloader.fiber_limit
assert_equal 8, FiberCounting.last_spawn_fiber_count
assert_equal 8 + extra_shortlived_jobs_fibers, FiberCounting.last_spawn_fiber_count
assert_last_max_fiber_count(6, "Limit of 6 works as expected")
end

Expand Down Expand Up @@ -1283,27 +1300,6 @@ def make_schema_from(schema)

include DataloaderAssertions
end

if RUBY_ENGINE == "ruby" && !ENV["GITHUB_ACTIONS"]
describe "nonblocking: true with libev" do
require "libev_scheduler"
def make_schema_from(schema)
Class.new(schema) do
use GraphQL::Dataloader, nonblocking: true
end
end

before do
Fiber.set_scheduler(Libev::Scheduler.new)
end

after do
Fiber.set_scheduler(nil)
end

include DataloaderAssertions
end
end
end

describe "example from #3314" do
Expand Down
2 changes: 1 addition & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
end

# C methods aren't fair game in non-main Ractors
RUN_RACTOR_TESTS = defined?(::Ractor) && !USING_C_PARSER
RUN_RACTOR_TESTS = defined?(::Ractor) && !USING_C_PARSER && !ENV["SKIP_RACTOR_TESTS"]

require "rake"
require "graphql/rake_task"
Expand Down
Loading