graphql 2.6.5 → 2.6.6

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: 3366525e188ac8b81b4dc7bdc8e34bb1839153fbec23d8e2a5faf07c02b56aa6
4
+ data.tar.gz: 414d8f17cee8d40adf8c5923427e83d187efdbd08871c7b9d8bbd4fa13b0e550
5
5
  SHA512:
6
- metadata.gz: 2ee406b8ad164004af45d98e3b5dc70d69396f008a80cc68ef83a4bff212698c0a181644dc2b513e8bd1e71d2baf7d1ba2901ad05f497199556b171a6dfb11ab
7
- data.tar.gz: 2ffd4e01742bd3b0feb207a316b1935d1edff05be8b28afb1c42e5ba47b4df916df802ee15617bb30bd45cdf6fd4f7a59c22b5d73cbabb3d8ee6658f3246d8ef
6
+ metadata.gz: f4423d53187dcf97e67ae1ba93cc9b5135c7db35cab04abc5533b96e97e3abd5d2c4bbbe683a64abc0117a022743a53df804d170e5fbd0fd7430a3987466531b
7
+ data.tar.gz: c6093a9a53e554aed992da96b80007bbe10df7ac9552c4a1dc0cce19ae8a3f33fd3e2cb0858c117c1e061c4b996a4af8a7b161b31b4f3806ff3a30a84dfaddb2
@@ -19,9 +19,12 @@ module GraphQL
19
19
  create_pending_run
20
20
  end
21
21
 
22
+ # @api private
23
+ attr_reader :pending_sources
24
+
22
25
  def create_pending_run
23
26
  jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
24
- @pending_run = Run.new(total_fiber_limit, jobs_fiber_limit)
27
+ @pending_run = Run.new(self, total_fiber_limit, jobs_fiber_limit)
25
28
  end
26
29
 
27
30
  def yield(source = Fiber[:__graphql_current_dataloader_source])
@@ -29,16 +32,17 @@ module GraphQL
29
32
  run = task.graphql_async_dataloader_run
30
33
  trace = run.trace
31
34
  trace&.dataloader_fiber_yield(source)
32
- run.finished_tasks.push(task)
35
+ run.tasks_channel.push([:paused_task, task])
33
36
  condition = task.graphql_async_dataloader_condition
34
37
  condition.wait
35
- run.started_tasks.push(task)
38
+ run.tasks_channel.push([:resumed_task, task])
36
39
  trace&.dataloader_fiber_resume(source)
37
40
  nil
38
41
  end
39
42
 
40
43
  class Run
41
- def initialize(total_fiber_limit, jobs_fiber_limit)
44
+ def initialize(dataloader, total_fiber_limit, jobs_fiber_limit)
45
+ @dataloader = dataloader
42
46
  @root_task = nil
43
47
  @trace = nil
44
48
  @jobs = []
@@ -47,12 +51,10 @@ module GraphQL
47
51
  @jobs_fiber_limit = jobs_fiber_limit
48
52
  @lazies_at_depth = Hash.new { |h, k| h[k] = [] }
49
53
 
50
- @finished_tasks = nil
51
- @started_tasks = nil
52
- @started_count_task = nil
53
- @finished_count_task = nil
54
+ @running_tasks = nil
55
+ @tasks_channel = nil
56
+ @tasks_channel_task = nil
54
57
  @finished_all_tasks = nil
55
- @finished_first_pass = nil
56
58
 
57
59
  @snoozed_jobs_condition = Async::Condition.new
58
60
  @snoozed_sources_condition = Async::Condition.new
@@ -60,73 +62,59 @@ module GraphQL
60
62
 
61
63
  attr_accessor :trace, :root_task
62
64
 
63
- attr_reader :jobs, :lazies_at_depth, :jobs_fiber_limit, :total_fiber_limit, :finished_tasks, :started_tasks, :snoozed_jobs_condition, :snoozed_sources_condition
65
+ attr_reader :jobs, :lazies_at_depth, :jobs_fiber_limit, :snoozed_jobs_condition, :snoozed_sources_condition, :tasks_channel
64
66
 
65
67
  def jobs_bandwidth?
66
- running_count < jobs_fiber_limit
68
+ running_count < @jobs_fiber_limit
67
69
  end
68
70
 
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
71
+ def sources_bandwidth?
72
+ running_count < current_sources_fiber_limit
76
73
  end
77
74
 
78
75
  def close_queues
79
- @finished_tasks.close
80
- @finished_count_task.cancel
81
-
82
- @started_tasks.close
83
- @started_count_task.cancel
84
- end
85
-
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
76
+ @tasks_channel.close
77
+ @tasks_channel_task.cancel
92
78
  end
93
79
 
94
80
  def wait_for_queues
95
- if !@finished_first_pass.resolved?
96
- @finished_first_pass.resolve(true)
97
- end
98
-
99
81
  @finished_all_tasks.wait
100
82
  @finished_all_tasks = Async::Promise.new
101
83
  end
102
84
 
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
119
- end
85
+ def wait_for_no_running_tasks
86
+ @no_running_tasks.wait
87
+ @no_running_tasks = Async::Promise.new
88
+ end
120
89
 
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)
90
+ def new_queues(mode)
91
+ @tasks_channel = Async::Queue.new(parent: @root_task)
92
+ @no_running_tasks = Async::Promise.new
93
+ @finished_all_tasks = Async::Promise.new
94
+ @running_tasks = []
95
+ @tasks_channel_task = @root_task.async do |_t|
96
+ while ((msg, data) = @tasks_channel.wait)
97
+ case msg
98
+ when :started_task
99
+ @running_tasks.push(data)
100
+ data.run
101
+ when :resumed_task
102
+ @running_tasks.push(data)
103
+ when :finished_task, :paused_task
104
+ @running_tasks.delete(data)
105
+ has_pending_work = mode == :jobs ? @jobs.any? : @dataloader.pending_sources.any?(&:pending?) # rubocop:disable Development/NoneWithoutBlockCop
106
+ if @running_tasks.empty?
107
+ @no_running_tasks.resolve(true)
108
+ has_bandwidth = mode == :jobs ? jobs_bandwidth? : sources_bandwidth?
109
+ if (!has_pending_work) || (!has_bandwidth)
110
+ @finished_all_tasks.resolve(true)
111
+ end
129
112
  end
113
+ when :task_error
114
+ @no_running_tasks.resolve(true)
115
+ @finished_all_tasks.reject(data)
116
+ else
117
+ raise ArgumentError, "Unknown tasks_channel action: #{msg.inspect}"
130
118
  end
131
119
  end
132
120
  end
@@ -135,6 +123,23 @@ module GraphQL
135
123
  def running?
136
124
  @snoozed_jobs_condition.waiting? || @snoozed_sources_condition.waiting?
137
125
  end
126
+
127
+ def current_sources_fiber_limit
128
+ within_limit = @total_fiber_limit - running_count
129
+ if within_limit < 1
130
+ 1
131
+ else
132
+ within_limit
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ def running_count
139
+ @snoozed_jobs_condition.instance_variable_get(:@ready).num_waiting +
140
+ @snoozed_sources_condition.instance_variable_get(:@ready).num_waiting +
141
+ (@running_tasks&.size || 0)
142
+ end
138
143
  end
139
144
 
140
145
  def append_job(callable = nil, &block)
@@ -177,9 +182,10 @@ module GraphQL
177
182
  end
178
183
  prev_pending_keys.each do |source_instance, pending|
179
184
  pending.each do |key, value|
180
- if !source_instance.results.key?(key)
181
- source_instance.pending[key] = value
182
- end
185
+ next if source_instance.results.key?(key)
186
+
187
+ queue_pending_source(source_instance) if source_instance.pending.empty?
188
+ source_instance.pending[key] = value
183
189
  end
184
190
  end
185
191
  end
@@ -203,13 +209,14 @@ module GraphQL
203
209
 
204
210
  while first_pass || run.running? || !jobs.empty?
205
211
  first_pass = false
206
- run_pending_steps(run)
207
- run_sources(run)
212
+ run_queue(run, run.snoozed_jobs_condition, :jobs)
213
+ run_queue(run, run.snoozed_sources_condition, :sources)
208
214
 
209
215
  if !run.lazies_at_depth.empty?
210
216
  with_trace_query_lazy(trace_query_lazy) do
211
- run_next_pending_lazies(run)
212
- run_pending_steps(run)
217
+ if enqueue_next_pending_lazies(run.lazies_at_depth)
218
+ run_queue(run, run.snoozed_jobs_condition, :jobs)
219
+ end
213
220
  end
214
221
  end
215
222
  end
@@ -232,129 +239,78 @@ module GraphQL
232
239
 
233
240
  private
234
241
 
235
- def run_pending_steps(run)
236
- run.new_queues
242
+ def run_queue(run, condition, mode)
243
+ should_wait_for_all_tasks = false
237
244
 
238
- if (unsnoozed = run.snoozed_jobs_condition.waiting?)
239
- run.snoozed_jobs_condition.signal
245
+ if (unsnoozed = condition.waiting?)
246
+ should_wait_for_all_tasks = true
247
+ run.new_queues(mode)
248
+ condition.signal
240
249
  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
250
 
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
251
+ while (pending_work = (mode == :jobs) ? (!run.jobs.empty? && run.jobs_bandwidth? ? run.jobs : nil) : (drain_pending_sources)) || unsnoozed
252
+ unsnoozed = false
253
+ if pending_work
254
+ if should_wait_for_all_tasks == false
255
+ should_wait_for_all_tasks = true
256
+ run.new_queues(mode)
264
257
  end
265
- ensure
266
- cleanup_fiber
267
- run.finished_tasks.push($! || task)
268
- run.trace&.dataloader_fiber_exit
258
+ num_tasks = mode == :sources ? run.current_sources_fiber_limit : 1
259
+ if num_tasks > pending_work.size
260
+ num_tasks = pending_work.size
261
+ end
262
+ spawn_tasks(run, mode, condition, pending_work, num_tasks)
269
263
  end
270
- run.started_tasks.push(new_task)
271
- new_task
272
- end
273
- end
274
264
 
275
- def run_sources(run)
276
- run.new_queues
277
-
278
- if (unsnoozed = run.snoozed_sources_condition.waiting?)
279
- run.snoozed_sources_condition.signal
265
+ run.wait_for_no_running_tasks
280
266
  end
281
267
 
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)
287
- end
268
+ if should_wait_for_all_tasks
288
269
  run.wait_for_queues
289
270
  end
290
271
  ensure
291
- run.close_queues
292
- end
293
-
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
303
-
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
316
- end
317
- end
272
+ if should_wait_for_all_tasks
273
+ run.close_queues
318
274
  end
319
275
  end
320
276
 
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
330
- end
331
-
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)
277
+ # Use a separate method for this so that the outer loop's reassignment of `pending_work`
278
+ # doesn't affect already-running tasks which (would) close over that variable
279
+ def spawn_tasks(run, mode, condition, pending_work, num_tasks)
280
+ fiber_vars = get_fiber_variables
281
+ trace = run.trace
282
+ num_tasks.times do
283
+ new_task = Async::Task.new(run.root_task) do |task|
284
+ task.graphql_async_dataloader_run = run
285
+ task.graphql_async_dataloader_condition = condition
286
+ set_fiber_variables(fiber_vars)
287
+ case mode
288
+ when :jobs
289
+ trace&.dataloader_spawn_execution_fiber(pending_work)
290
+ while job = pending_work.shift
291
+ job.call
292
+ end
293
+ when :sources
294
+ trace&.dataloader_spawn_source_fiber(pending_work)
295
+ while (source = pending_work.shift)
296
+ Fiber[:__graphql_current_dataloader_source] = source
345
297
  trace&.begin_dataloader_source(source)
346
298
  source.run_pending_keys
347
299
  trace&.end_dataloader_source(source)
348
300
  end
349
- nil
350
- ensure
351
- run.finished_tasks.push($! || task)
352
- cleanup_fiber
353
- trace&.dataloader_fiber_exit
301
+ else
302
+ raise ArgumentError, "Unknown mode: #{mode.inspect}"
354
303
  end
355
- run.started_tasks.push(new_task)
356
- new_task
304
+ nil
305
+ rescue StandardError => err
306
+ run.tasks_channel.push([:task_error, err])
307
+ else
308
+ run.tasks_channel.push([:finished_task, task])
309
+ ensure
310
+ cleanup_fiber
311
+ trace&.dataloader_fiber_exit
357
312
  end
313
+ run.tasks_channel.push([:started_task, new_task])
358
314
  end
359
315
  end
360
316
  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
@@ -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
@@ -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.6"
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.6
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-21 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