graphql 2.6.5 → 2.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ece536c0702c8789a3a5c79de82af4bd2ff806a3209d0ac4da88fc968629d19
4
- data.tar.gz: 0063f1ea552e7782029083eb027138a0725af89a65265dc643469f2227184134
3
+ metadata.gz: 75192ceea307d33645d2d511db98530f8021c8af45af33fc75e86dcd69f53952
4
+ data.tar.gz: ff3ee9e77876ad45fb67491fb5076df4614ab086ecfa15e476b3d35bca2e3b22
5
5
  SHA512:
6
- metadata.gz: 2ee406b8ad164004af45d98e3b5dc70d69396f008a80cc68ef83a4bff212698c0a181644dc2b513e8bd1e71d2baf7d1ba2901ad05f497199556b171a6dfb11ab
7
- data.tar.gz: 2ffd4e01742bd3b0feb207a316b1935d1edff05be8b28afb1c42e5ba47b4df916df802ee15617bb30bd45cdf6fd4f7a59c22b5d73cbabb3d8ee6658f3246d8ef
6
+ metadata.gz: 50bc8591ece79fc8070b4c3a6a3611f29e9a4ad84a94e6f310a5fd2c00ac0d10b6db5ebe44474e1d5f002a76615d6a25dd4dbfc2a8f56ec1c2d0d88972ce95e2
7
+ data.tar.gz: b28b83923ac25bb605d7fe1e9b25a64e37aa8173a64c958849287cdd0c87cc419deea25f36b4d525a6ea17088b7a02cb0a51011750c568337a6274b0373ff43b
@@ -4,6 +4,11 @@ module GraphQL
4
4
  class Dataloader
5
5
  class AsyncDataloader < Dataloader
6
6
  def self.use(...)
7
+ install_graphql_methods
8
+ super
9
+ end
10
+
11
+ def self.install_graphql_methods
7
12
  if !Async::Task.method_defined?(:cancel)
8
13
  Async::Task.alias_method(:cancel, :stop)
9
14
  end
@@ -11,7 +16,6 @@ module GraphQL
11
16
  Async::Task.attr_accessor(:graphql_async_dataloader_run)
12
17
  Async::Task.attr_accessor(:graphql_async_dataloader_condition)
13
18
  end
14
- super
15
19
  end
16
20
 
17
21
  def initialize(...)
@@ -19,9 +23,12 @@ module GraphQL
19
23
  create_pending_run
20
24
  end
21
25
 
26
+ # @api private
27
+ attr_reader :pending_sources
28
+
22
29
  def create_pending_run
23
30
  jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
24
- @pending_run = Run.new(total_fiber_limit, jobs_fiber_limit)
31
+ @pending_run = Run.new(self, total_fiber_limit, jobs_fiber_limit)
25
32
  end
26
33
 
27
34
  def yield(source = Fiber[:__graphql_current_dataloader_source])
@@ -29,16 +36,17 @@ module GraphQL
29
36
  run = task.graphql_async_dataloader_run
30
37
  trace = run.trace
31
38
  trace&.dataloader_fiber_yield(source)
32
- run.finished_tasks.push(task)
39
+ run.tasks_channel.push([:paused_task, task])
33
40
  condition = task.graphql_async_dataloader_condition
34
41
  condition.wait
35
- run.started_tasks.push(task)
42
+ run.tasks_channel.push([:resumed_task, task])
36
43
  trace&.dataloader_fiber_resume(source)
37
44
  nil
38
45
  end
39
46
 
40
47
  class Run
41
- def initialize(total_fiber_limit, jobs_fiber_limit)
48
+ def initialize(dataloader, total_fiber_limit, jobs_fiber_limit)
49
+ @dataloader = dataloader
42
50
  @root_task = nil
43
51
  @trace = nil
44
52
  @jobs = []
@@ -47,12 +55,13 @@ module GraphQL
47
55
  @jobs_fiber_limit = jobs_fiber_limit
48
56
  @lazies_at_depth = Hash.new { |h, k| h[k] = [] }
49
57
 
50
- @finished_tasks = nil
51
- @started_tasks = nil
52
- @started_count_task = nil
53
- @finished_count_task = nil
54
- @finished_all_tasks = nil
55
- @finished_first_pass = nil
58
+ @running_tasks = nil
59
+ @tasks_channel = nil
60
+ @tasks_channel_task = nil
61
+ @activity = nil
62
+ @task_error = nil
63
+ @expected_resumes = 0
64
+ @mode = nil
56
65
 
57
66
  @snoozed_jobs_condition = Async::Condition.new
58
67
  @snoozed_sources_condition = Async::Condition.new
@@ -60,74 +69,76 @@ module GraphQL
60
69
 
61
70
  attr_accessor :trace, :root_task
62
71
 
63
- attr_reader :jobs, :lazies_at_depth, :jobs_fiber_limit, :total_fiber_limit, :finished_tasks, :started_tasks, :snoozed_jobs_condition, :snoozed_sources_condition
72
+ attr_reader :jobs, :lazies_at_depth, :jobs_fiber_limit, :snoozed_jobs_condition, :snoozed_sources_condition, :tasks_channel
64
73
 
65
74
  def jobs_bandwidth?
66
- running_count < jobs_fiber_limit
75
+ running_count < @jobs_fiber_limit
67
76
  end
68
77
 
69
- def allowed_sources_tasks
70
- within_limit = total_fiber_limit - running_count
71
- if within_limit < 1
72
- 1
73
- else
74
- within_limit
75
- end
78
+ def sources_bandwidth?
79
+ running_count < current_sources_fiber_limit
76
80
  end
77
81
 
78
82
  def close_queues
79
- @finished_tasks.close
80
- @finished_count_task.cancel
83
+ @tasks_channel.close
84
+ @tasks_channel_task.cancel
85
+ end
81
86
 
82
- @started_tasks.close
83
- @started_count_task.cancel
87
+ def wait_for_activity
88
+ @activity.wait
84
89
  end
85
90
 
86
- def running_count
87
- @snoozed_jobs_condition.instance_variable_get(:@ready).num_waiting +
88
- @snoozed_sources_condition.instance_variable_get(:@ready).num_waiting +
89
- @started_count +
90
- @started_tasks.size -
91
- @finished_count
91
+ def quiesced?
92
+ @running_tasks.empty? && @tasks_channel.empty? && @expected_resumes == 0
92
93
  end
93
94
 
94
- def wait_for_queues
95
- if !@finished_first_pass.resolved?
96
- @finished_first_pass.resolve(true)
97
- end
95
+ def has_pending_work?
96
+ @mode == :jobs ? @jobs.any? : @dataloader.pending_sources.any?(&:pending?) # rubocop:disable Development/NoneWithoutBlockCop
97
+ end
98
98
 
99
- @finished_all_tasks.wait
100
- @finished_all_tasks = Async::Promise.new
99
+ def has_bandwidth?
100
+ @mode == :jobs ? jobs_bandwidth? : sources_bandwidth?
101
101
  end
102
102
 
103
- def new_queues
104
- @finished_tasks = Async::Queue.new
105
- @finished_count = 0
106
- @started_tasks = Async::Queue.new
107
- @started_count = 0
108
- @finished_first_pass = Async::Promise.new
109
- @finished_all_tasks = Async::Promise.new
110
-
111
- @started_count_task = @root_task.async do |task|
112
- @finished_first_pass.wait
113
- while task = @started_tasks.wait
114
- @started_count += 1
115
- if task.status == :initialized # could also be resumed after waiting
116
- task.run
117
- end
118
- end
103
+ # Signalled tasks don't appear in any accounting until their first slice
104
+ # pushes `:resumed_task`, so they have to be counted at signal time:
105
+ def expect_resumes(count)
106
+ @expected_resumes = count
107
+ end
108
+
109
+ def check_error!
110
+ if (err = @task_error)
111
+ @task_error = nil
112
+ raise err
119
113
  end
114
+ end
120
115
 
121
- @finished_count_task = @root_task.async do |task|
122
- while t_or_err = @finished_tasks.wait
123
- if t_or_err.is_a?(StandardError)
124
- @finished_all_tasks.reject(t_or_err)
125
- else
126
- @finished_count += 1
127
- if @finished_count == @started_count
128
- @finished_all_tasks.resolve(true)
116
+ def new_queues(mode)
117
+ @mode = mode
118
+ @tasks_channel = Async::Queue.new(parent: @root_task)
119
+ @activity = Async::Condition.new
120
+ @task_error = nil
121
+ @expected_resumes = 0
122
+ @running_tasks = []
123
+ @tasks_channel_task = @root_task.async do |_t|
124
+ while ((msg, data) = @tasks_channel.wait)
125
+ case msg
126
+ when :started_task
127
+ @running_tasks.push(data)
128
+ data.run
129
+ when :resumed_task
130
+ if @expected_resumes > 0
131
+ @expected_resumes -= 1
129
132
  end
133
+ @running_tasks.push(data)
134
+ when :finished_task, :paused_task
135
+ @running_tasks.delete(data)
136
+ when :task_error
137
+ @task_error ||= data
138
+ else
139
+ raise ArgumentError, "Unknown tasks_channel action: #{msg.inspect}"
130
140
  end
141
+ @activity.signal
131
142
  end
132
143
  end
133
144
  end
@@ -135,6 +146,23 @@ module GraphQL
135
146
  def running?
136
147
  @snoozed_jobs_condition.waiting? || @snoozed_sources_condition.waiting?
137
148
  end
149
+
150
+ def current_sources_fiber_limit
151
+ within_limit = @total_fiber_limit - running_count
152
+ if within_limit < 1
153
+ 1
154
+ else
155
+ within_limit
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ def running_count
162
+ @snoozed_jobs_condition.instance_variable_get(:@ready).num_waiting +
163
+ @snoozed_sources_condition.instance_variable_get(:@ready).num_waiting +
164
+ (@running_tasks&.size || 0)
165
+ end
138
166
  end
139
167
 
140
168
  def append_job(callable = nil, &block)
@@ -177,9 +205,10 @@ module GraphQL
177
205
  end
178
206
  prev_pending_keys.each do |source_instance, pending|
179
207
  pending.each do |key, value|
180
- if !source_instance.results.key?(key)
181
- source_instance.pending[key] = value
182
- end
208
+ next if source_instance.results.key?(key)
209
+
210
+ queue_pending_source(source_instance) if source_instance.pending.empty?
211
+ source_instance.pending[key] = value
183
212
  end
184
213
  end
185
214
  end
@@ -203,13 +232,14 @@ module GraphQL
203
232
 
204
233
  while first_pass || run.running? || !jobs.empty?
205
234
  first_pass = false
206
- run_pending_steps(run)
207
- run_sources(run)
235
+ run_queue(run, run.snoozed_jobs_condition, :jobs)
236
+ run_queue(run, run.snoozed_sources_condition, :sources)
208
237
 
209
238
  if !run.lazies_at_depth.empty?
210
239
  with_trace_query_lazy(trace_query_lazy) do
211
- run_next_pending_lazies(run)
212
- run_pending_steps(run)
240
+ if enqueue_next_pending_lazies(run.lazies_at_depth)
241
+ run_queue(run, run.snoozed_jobs_condition, :jobs)
242
+ end
213
243
  end
214
244
  end
215
245
  end
@@ -232,129 +262,88 @@ module GraphQL
232
262
 
233
263
  private
234
264
 
235
- def run_pending_steps(run)
236
- run.new_queues
265
+ def run_queue(run, condition, mode)
266
+ opened_queues = false
237
267
 
238
- if (unsnoozed = run.snoozed_jobs_condition.waiting?)
239
- run.snoozed_jobs_condition.signal
268
+ if condition.waiting?
269
+ opened_queues = true
270
+ run.new_queues(mode)
271
+ run.expect_resumes(condition.instance_variable_get(:@ready).num_waiting)
272
+ condition.signal
240
273
  end
241
- pending_jobs = run.jobs
242
- while (!pending_jobs.empty? && (has_limit = run.jobs_bandwidth?)) || (unsnoozed)
243
- unsnoozed = false
244
- if has_limit
245
- spawn_job_task(run)
246
- end
247
- run.wait_for_queues
248
- end
249
- ensure
250
- run.close_queues
251
- end
252
274
 
253
- def spawn_job_task(run)
254
- pending_jobs = run.jobs
255
- if !pending_jobs.empty?
256
- fiber_vars = get_fiber_variables
257
- new_task = Async::Task.new(run.root_task) do |task|
258
- run.trace&.dataloader_spawn_execution_fiber(pending_jobs)
259
- task.graphql_async_dataloader_run = run
260
- task.graphql_async_dataloader_condition = run.snoozed_jobs_condition
261
- set_fiber_variables(fiber_vars)
262
- while job = pending_jobs.shift
263
- job.call
275
+ loop do
276
+ pending_work = (mode == :jobs) ? (!run.jobs.empty? && run.jobs_bandwidth? ? run.jobs : nil) : (drain_pending_sources)
277
+ if pending_work
278
+ if opened_queues == false
279
+ opened_queues = true
280
+ run.new_queues(mode)
264
281
  end
265
- ensure
266
- cleanup_fiber
267
- run.finished_tasks.push($! || task)
268
- run.trace&.dataloader_fiber_exit
282
+ num_tasks = mode == :sources ? run.current_sources_fiber_limit : 1
283
+ if num_tasks > pending_work.size
284
+ num_tasks = pending_work.size
285
+ end
286
+ spawn_tasks(run, mode, condition, pending_work, num_tasks)
269
287
  end
270
- run.started_tasks.push(new_task)
271
- new_task
272
- end
273
- end
274
-
275
- def run_sources(run)
276
- run.new_queues
277
288
 
278
- if (unsnoozed = run.snoozed_sources_condition.waiting?)
279
- run.snoozed_sources_condition.signal
280
- end
281
-
282
- allowed_tasks = run.allowed_sources_tasks
283
- while (has_pending = @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) } ) || unsnoozed
284
- unsnoozed = false
285
- if has_pending
286
- spawn_source_task(run, allowed_tasks)
289
+ if !opened_queues
290
+ break
287
291
  end
288
- run.wait_for_queues
289
- end
290
- ensure
291
- run.close_queues
292
- end
293
292
 
294
- #### TODO DRY Had to duplicate to remove spawn_job_fiber
295
- def run_next_pending_lazies(run)
296
- smallest_depth = nil
297
- run.lazies_at_depth.each_key do |depth_key|
298
- smallest_depth ||= depth_key
299
- if depth_key < smallest_depth
300
- smallest_depth = depth_key
301
- end
302
- end
293
+ run.check_error!
303
294
 
304
- if smallest_depth
305
- lazies = run.lazies_at_depth.delete(smallest_depth)
306
- if !lazies.empty?
307
- begin
308
- run.new_queues
309
- lazies.each_with_index do |l, idx|
310
- append_job { l.value }
311
- end
312
- spawn_job_task(run) # Todo what was the last `true` condition?
313
- run.wait_for_queues
314
- ensure
315
- run.close_queues
295
+ if run.quiesced?
296
+ if !run.has_pending_work? || !run.has_bandwidth?
297
+ break
316
298
  end
299
+ # Quiesced, but more work appeared - loop around to drain it.
300
+ else
301
+ run.wait_for_activity
317
302
  end
318
303
  end
319
- end
320
-
321
- def spawn_source_task(run, num_tasks)
322
- pending_sources = nil
323
- @source_cache.each_value do |source_by_batch_params|
324
- source_by_batch_params.each_value do |source|
325
- if source.pending?
326
- pending_sources ||= []
327
- pending_sources << source
328
- end
329
- end
304
+ ensure
305
+ if opened_queues
306
+ run.close_queues
330
307
  end
308
+ end
331
309
 
332
- if pending_sources
333
- if num_tasks == Float::INFINITY
334
- num_tasks = pending_sources.size
335
- end
336
- fiber_vars = get_fiber_variables
337
- trace = run.trace
338
- num_tasks.times do
339
- new_task = Async::Task.new(run.root_task) do |task|
340
- task.graphql_async_dataloader_run = run
341
- task.graphql_async_dataloader_condition = run.snoozed_sources_condition
342
- trace&.dataloader_spawn_source_fiber(pending_sources)
343
- set_fiber_variables(fiber_vars)
344
- while (source = pending_sources.shift)
310
+ # Use a separate method for this so that the outer loop's reassignment of `pending_work`
311
+ # doesn't affect already-running tasks which (would) close over that variable
312
+ def spawn_tasks(run, mode, condition, pending_work, num_tasks)
313
+ fiber_vars = get_fiber_variables
314
+ trace = run.trace
315
+ num_tasks.times do
316
+ new_task = Async::Task.new(run.root_task) do |task|
317
+ task.graphql_async_dataloader_run = run
318
+ task.graphql_async_dataloader_condition = condition
319
+ set_fiber_variables(fiber_vars)
320
+ case mode
321
+ when :jobs
322
+ trace&.dataloader_spawn_execution_fiber(pending_work)
323
+ while job = pending_work.shift
324
+ job.call
325
+ end
326
+ when :sources
327
+ trace&.dataloader_spawn_source_fiber(pending_work)
328
+ while (source = pending_work.shift)
329
+ Fiber[:__graphql_current_dataloader_source] = source
345
330
  trace&.begin_dataloader_source(source)
346
331
  source.run_pending_keys
347
332
  trace&.end_dataloader_source(source)
348
333
  end
349
- nil
350
- ensure
351
- run.finished_tasks.push($! || task)
352
- cleanup_fiber
353
- trace&.dataloader_fiber_exit
334
+ else
335
+ raise ArgumentError, "Unknown mode: #{mode.inspect}"
354
336
  end
355
- run.started_tasks.push(new_task)
356
- new_task
337
+ nil
338
+ rescue StandardError => err
339
+ run.tasks_channel.push([:task_error, err])
340
+ else
341
+ run.tasks_channel.push([:finished_task, task])
342
+ ensure
343
+ cleanup_fiber
344
+ trace&.dataloader_fiber_exit
357
345
  end
346
+ run.tasks_channel.push([:started_task, new_task])
358
347
  end
359
348
  end
360
349
  end
@@ -20,9 +20,7 @@ module GraphQL
20
20
  # @return [Dataloader::Request] a pending request for a value from `key`. Call `.load` on that object to wait for the result.
21
21
  def request(value)
22
22
  res_key = result_key_for(value)
23
- if !@results.key?(res_key)
24
- @pending[res_key] ||= normalize_fetch_key(value)
25
- end
23
+ add_pending_key(res_key, value)
26
24
  Dataloader::Request.new(self, value)
27
25
  end
28
26
 
@@ -51,9 +49,7 @@ module GraphQL
51
49
  def request_all(values)
52
50
  values.each do |v|
53
51
  res_key = result_key_for(v)
54
- if !@results.key?(res_key)
55
- @pending[res_key] ||= normalize_fetch_key(v)
56
- end
52
+ add_pending_key(res_key, v)
57
53
  end
58
54
  Dataloader::RequestAll.new(self, values)
59
55
  end
@@ -65,7 +61,7 @@ module GraphQL
65
61
  if @results.key?(result_key)
66
62
  result_for(result_key)
67
63
  else
68
- @pending[result_key] ||= normalize_fetch_key(value)
64
+ add_pending_key(result_key, value)
69
65
  sync([result_key])
70
66
  result_for(result_key)
71
67
  end
@@ -79,8 +75,7 @@ module GraphQL
79
75
  values.each { |v|
80
76
  k = result_key_for(v)
81
77
  result_keys << k
82
- if !@results.key?(k)
83
- @pending[k] ||= normalize_fetch_key(v)
78
+ if add_pending_key(k, v)
84
79
  pending_keys << k
85
80
  end
86
81
  }
@@ -106,6 +101,7 @@ module GraphQL
106
101
  # Then run the batch and update the cache.
107
102
  # @return [void]
108
103
  def sync(pending_result_keys)
104
+ @dataloader.queue_pending_source(self) if pending?
109
105
  @dataloader.yield(self)
110
106
  iterations = 0
111
107
  while pending_result_keys.any? { |key| !@results.key?(key) }
@@ -193,6 +189,15 @@ module GraphQL
193
189
 
194
190
  private
195
191
 
192
+ def add_pending_key(result_key, value)
193
+ return false if @results.key?(result_key)
194
+
195
+ was_empty = @pending.empty?
196
+ @pending[result_key] ||= normalize_fetch_key(value)
197
+ @dataloader.queue_pending_source(self) if was_empty
198
+ true
199
+ end
200
+
196
201
  # Reads and returns the result for the key from the internal cache, or raises an error if the result was an error
197
202
  # @param key [Object] key passed to {#load} or {#load_all}
198
203
  # @return [Object] The result from {#fetch} for `key`.
@@ -59,6 +59,8 @@ module GraphQL
59
59
 
60
60
  def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit)
61
61
  @source_cache = Hash.new { |h, k| h[k] = {} }.compare_by_identity
62
+ @pending_source_set = Set.new.compare_by_identity
63
+ @pending_sources = []
62
64
  @pending_jobs = []
63
65
  if !nonblocking.nil?
64
66
  @nonblocking = nonblocking
@@ -108,7 +110,7 @@ module GraphQL
108
110
  # @param batch_parameters [Array<Object>]
109
111
  # @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
110
112
  # and cached for the lifetime of this {Multiplex}.
111
- if (RUBY_ENGINE == "ruby" && RUBY_ENGINE < "3") || RUBY_ENGINE == "truffleruby" # truffle-ruby wasn't doing well with the implementation below
113
+ if (RUBY_ENGINE == "ruby" && RUBY_VERSION < "3") || RUBY_ENGINE == "truffleruby" # truffle-ruby wasn't doing well with the implementation below
112
114
  def with(source_class, *batch_args)
113
115
  batch_key = source_class.batch_key_for(*batch_args)
114
116
  @source_cache[source_class][batch_key] ||= begin
@@ -148,6 +150,14 @@ module GraphQL
148
150
  nil
149
151
  end
150
152
 
153
+ # @api private
154
+ def queue_pending_source(source)
155
+ if @pending_source_set.add?(source)
156
+ @pending_sources << source
157
+ end
158
+ nil
159
+ end
160
+
151
161
  # Clear any already-loaded objects from {Source} caches
152
162
  # @return [void]
153
163
  def clear_cache
@@ -187,9 +197,10 @@ module GraphQL
187
197
  @lazies_at_depth = prev_lazies_at_depth
188
198
  prev_pending_keys.each do |source_instance, pending|
189
199
  pending.each do |key, value|
190
- if !source_instance.results.key?(key)
191
- source_instance.pending[key] = value
192
- end
200
+ next if source_instance.results.key?(key)
201
+
202
+ queue_pending_source(source_instance) if source_instance.pending.empty?
203
+ source_instance.pending[key] = value
193
204
  end
194
205
  end
195
206
  end
@@ -212,8 +223,10 @@ module GraphQL
212
223
 
213
224
  if !@lazies_at_depth.empty?
214
225
  with_trace_query_lazy(trace_query_lazy) do
215
- run_next_pending_lazies(job_fibers, trace)
216
- run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
226
+ if enqueue_next_pending_lazies(@lazies_at_depth)
227
+ job_fibers.unshift(spawn_job_fiber(trace))
228
+ run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
229
+ end
217
230
  end
218
231
  end
219
232
  end
@@ -274,24 +287,19 @@ module GraphQL
274
287
 
275
288
  private
276
289
 
277
- def run_next_pending_lazies(job_fibers, trace)
278
- smallest_depth = nil
279
- @lazies_at_depth.each_key do |depth_key|
280
- smallest_depth ||= depth_key
281
- if depth_key < smallest_depth
282
- smallest_depth = depth_key
283
- end
284
- end
290
+ # Returns true if anything was actually enqueued
291
+ def enqueue_next_pending_lazies(lazies_at_depth)
292
+ smallest_depth = lazies_at_depth.each_key.min
293
+ return false if smallest_depth.nil?
285
294
 
286
- if smallest_depth
287
- lazies = @lazies_at_depth.delete(smallest_depth)
288
- if !lazies.empty?
289
- lazies.each_with_index do |l, idx|
290
- append_job { l.value }
291
- end
292
- job_fibers.unshift(spawn_job_fiber(trace))
293
- end
295
+ lazies = lazies_at_depth.delete(smallest_depth)
296
+ return false if lazies.empty?
297
+
298
+ lazies.each do |lazy|
299
+ append_job { lazy.value }
294
300
  end
301
+
302
+ true
295
303
  end
296
304
 
297
305
  def run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
@@ -305,7 +313,7 @@ module GraphQL
305
313
  end
306
314
  join_queues(job_fibers, next_job_fibers)
307
315
 
308
- while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
316
+ while (!source_fibers.empty? || !@pending_sources.empty?)
309
317
  while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber(trace)))
310
318
  if f.alive?
311
319
  finished = run_fiber(f)
@@ -356,21 +364,29 @@ module GraphQL
356
364
  end
357
365
  end
358
366
 
359
- def spawn_source_fiber(trace)
360
- pending_sources = nil
361
- @source_cache.each_value do |source_by_batch_params|
362
- source_by_batch_params.each_value do |source|
363
- if source.pending?
364
- pending_sources ||= []
365
- pending_sources << source
366
- end
367
- end
367
+ def drain_pending_sources
368
+ pending_sources = @pending_sources
369
+ @pending_sources = []
370
+ @pending_source_set.clear
371
+
372
+ pending_sources.select!(&:pending?)
373
+ pending_sources.empty? ? nil : pending_sources
374
+ end
375
+
376
+ def dequeue_pending_source
377
+ while (source = @pending_sources.shift)
378
+ @pending_source_set.delete(source)
379
+ return source if source.pending?
368
380
  end
381
+ end
369
382
 
370
- if pending_sources
383
+ def spawn_source_fiber(trace)
384
+ if !@pending_sources.empty?
371
385
  spawn_fiber do
372
- trace&.dataloader_spawn_source_fiber(pending_sources)
373
- pending_sources.each do |source|
386
+ trace&.dataloader_spawn_source_fiber(@pending_sources)
387
+ # This will find sources which were enqueued during `#fetch`:
388
+ while (source = dequeue_pending_source)
389
+ next if !source.pending?
374
390
  Fiber[:__graphql_current_dataloader_source] = source
375
391
  trace&.begin_dataloader_source(source)
376
392
  source.run_pending_keys
@@ -765,8 +765,7 @@ module GraphQL
765
765
 
766
766
  response_list
767
767
  rescue NoMethodError => err
768
- # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
769
- if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
768
+ if err.name == :each && err.receiver == value
770
769
  # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
771
770
  raise ListResultFailedError.new(value: value, field: field, path: current_path)
772
771
  else
@@ -281,8 +281,8 @@ module GraphQL
281
281
  end
282
282
  result = dir_defn.resolve_operation(selected_operation, query, objects, dir_args, query.context)
283
283
  if result.is_a?(Finalizer)
284
- result.path = path
285
- add_finalizer(query, result, nil, data)
284
+ result.path = beginning_path
285
+ add_finalizer(query, data, nil, result)
286
286
  if result.is_a?(HaltExecution)
287
287
  continue_execution = false
288
288
  break
@@ -83,12 +83,12 @@ module GraphQL
83
83
  end
84
84
  end
85
85
 
86
- continue_selections.each do |frs|
87
- @runner.add_step(frs)
88
- end
89
-
90
86
  i += 2
91
87
  end
88
+
89
+ continue_selections.each do |frs|
90
+ @runner.add_step(frs)
91
+ end
92
92
  end
93
93
  end
94
94
  end
@@ -19,7 +19,7 @@ module GraphQL
19
19
  @storage = ast_variables.each_with_object({}) do |ast_variable, memo|
20
20
  if schema.validate_max_errors && schema.validate_max_errors <= @errors.count
21
21
  add_max_errors_reached_message
22
- break
22
+ break memo
23
23
  end
24
24
  # Find the right value for this variable:
25
25
  # - First, use the value provided at runtime
@@ -82,6 +82,7 @@ module GraphQL
82
82
  replace_late_bound_types_with_built_in(types)
83
83
 
84
84
  schema_extensions = nil
85
+ definitions_by_name = nil
85
86
  document.definitions.each do |definition|
86
87
  case definition
87
88
  when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition
@@ -101,9 +102,16 @@ module GraphQL
101
102
  if prev_type.nil? || prev_type.is_a?(Schema::LateBoundType)
102
103
  if definition.is_a?(GraphQL::Language::Nodes::ObjectTypeDefinition) || definition.is_a?(Language::Nodes::InterfaceTypeDefinition)
103
104
  interface_names = definition.interfaces.map(&:name)
104
- transitive_names = interface_names.map { |n| document.definitions.find { |d| d.respond_to?(:name) && d.name == n }&.interfaces&.map(&:name) }
105
- transitive_names.flatten!
106
- transitive_names.compact!
105
+ if !interface_names.empty?
106
+ definitions_by_name ||= document.definitions.each_with_object({}) do |d, by_name|
107
+ by_name[d.name] ||= d if d.respond_to?(:name)
108
+ end
109
+ transitive_names = interface_names.map { |n| definitions_by_name[n]&.interfaces&.map(&:name) }
110
+ transitive_names.flatten!
111
+ transitive_names.compact!
112
+ else
113
+ transitive_names = interface_names
114
+ end
107
115
  if !(missing_transitive_interfaces = transitive_names - interface_names).empty?
108
116
  raise GraphQL::Schema::InvalidDocumentError, "type #{definition.name} is missing one or more transitive interface names: #{missing_transitive_interfaces.join(", ")}. Add them to the type's `implements` list and try again."
109
117
  end
@@ -168,6 +168,9 @@ module GraphQL
168
168
  # Let validation handle this
169
169
  value
170
170
  end
171
+ elsif arg_defn.default_value?
172
+ value = arg_defn.default_value
173
+ graphql_value = arg_type.coerce_isolated_result(value) unless value.nil?
171
174
  else
172
175
  value = graphql_value = nil
173
176
  end
@@ -83,7 +83,7 @@ module GraphQL
83
83
  is_authed, new_return_value = authorized?(**@prepared_arguments)
84
84
  rescue GraphQL::UnauthorizedError => err
85
85
  new_return_value = q.schema.unauthorized_object(err)
86
- is_authed = true # the error was handled
86
+ is_authed = false
87
87
  end
88
88
  end
89
89
 
@@ -25,6 +25,10 @@ module GraphQL
25
25
  def ==(other)
26
26
  self.class == other.class && of_type == other.of_type
27
27
  end
28
+
29
+ def deconstruct_keys(_keys)
30
+ { of_type: of_type }
31
+ end
28
32
  end
29
33
  end
30
34
  end
@@ -665,8 +665,8 @@ module GraphQL
665
665
  inherited_um = find_inherited_value(:union_memberships, EMPTY_HASH).fetch(type.graphql_name, EMPTY_ARRAY)
666
666
  own_um + inherited_um
667
667
  else
668
- joined_um = own_union_memberships.dup
669
- find_inherited_value(:union_memberhips, EMPTY_HASH).each do |k, v|
668
+ joined_um = own_union_memberships.transform_values(&:dup)
669
+ find_inherited_value(:union_memberships, EMPTY_HASH).each do |k, v|
670
670
  um = joined_um[k] ||= []
671
671
  um.concat(v)
672
672
  end
@@ -860,7 +860,7 @@ module GraphQL
860
860
  # @return [Array<GraphQL::StaticValidation::Error >]
861
861
  def validate(string_or_document, rules: nil, context: nil)
862
862
  doc = if string_or_document.is_a?(String)
863
- GraphQL.parse(string_or_document)
863
+ GraphQL.parse(string_or_document, max_tokens: max_query_string_tokens)
864
864
  else
865
865
  string_or_document
866
866
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.6.5"
3
+ VERSION = "2.6.7"
4
4
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.5
4
+ version: 2.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-07-06 00:00:00.000000000 Z
10
+ date: 2026-07-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -261,34 +261,6 @@ dependencies:
261
261
  - - ">="
262
262
  - !ruby/object:Gem::Version
263
263
  version: '0'
264
- - !ruby/object:Gem::Dependency
265
- name: jekyll
266
- requirement: !ruby/object:Gem::Requirement
267
- requirements:
268
- - - ">="
269
- - !ruby/object:Gem::Version
270
- version: '0'
271
- type: :development
272
- prerelease: false
273
- version_requirements: !ruby/object:Gem::Requirement
274
- requirements:
275
- - - ">="
276
- - !ruby/object:Gem::Version
277
- version: '0'
278
- - !ruby/object:Gem::Dependency
279
- name: jekyll-sass-converter
280
- requirement: !ruby/object:Gem::Requirement
281
- requirements:
282
- - - "~>"
283
- - !ruby/object:Gem::Version
284
- version: '2.2'
285
- type: :development
286
- prerelease: false
287
- version_requirements: !ruby/object:Gem::Requirement
288
- requirements:
289
- - - "~>"
290
- - !ruby/object:Gem::Version
291
- version: '2.2'
292
264
  - !ruby/object:Gem::Dependency
293
265
  name: yard
294
266
  requirement: !ruby/object:Gem::Requirement
@@ -303,34 +275,6 @@ dependencies:
303
275
  - - ">="
304
276
  - !ruby/object:Gem::Version
305
277
  version: '0'
306
- - !ruby/object:Gem::Dependency
307
- name: jekyll-algolia
308
- requirement: !ruby/object:Gem::Requirement
309
- requirements:
310
- - - ">="
311
- - !ruby/object:Gem::Version
312
- version: '0'
313
- type: :development
314
- prerelease: false
315
- version_requirements: !ruby/object:Gem::Requirement
316
- requirements:
317
- - - ">="
318
- - !ruby/object:Gem::Version
319
- version: '0'
320
- - !ruby/object:Gem::Dependency
321
- name: jekyll-redirect-from
322
- requirement: !ruby/object:Gem::Requirement
323
- requirements:
324
- - - ">="
325
- - !ruby/object:Gem::Version
326
- version: '0'
327
- type: :development
328
- prerelease: false
329
- version_requirements: !ruby/object:Gem::Requirement
330
- requirements:
331
- - - ">="
332
- - !ruby/object:Gem::Version
333
- version: '0'
334
278
  - !ruby/object:Gem::Dependency
335
279
  name: m
336
280
  requirement: !ruby/object:Gem::Requirement