GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Add dispatch strategy, implement bfs strategy by zsmoore · Pull Request #261 · graphql-java/java-dataloader

Conversation

@zsmoore

@zsmoore zsmoore commented Jan 6, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

See discussion in - #256
Build on #243

Allow for custom dispatch strategies

Right now the dispatch strategy (in this PR and the original busy strategy) do not need dataloader references, they just need registry references. Its possible that strategies prefer to have the DL loaded such as a DFS strategy to trigger the dl dispatch immediately without using a registry reference but as there is no current need do not include it.

Because of the circular reference dependency between dispatch strategy and registry, choose to add a bootstrap function into the interface to give a reference to the strategy

BFS strategy handles dispatching in a level by level fashion see example here -

i.e.

Async Path

A
    B (async)
        E
        F
    C
        G (async)
        H
    D
        I
        J

A dispatches immediately, queues B, C, D
B, C, D dispatch queue G, H, I, J
Thread is spawned to wait for B to finish and retry every 30 ms
B finishes, queues E, F
E, F, G, H, I, J dispatched

This dispatch strategy also handles chained dataloaders but beyond that handles ASYNC chained dataloaders such that a dataloader makes an api call then chains a subsequent dataloader.

This is done with minimal thread overload by doing dispatchAll + only queueing a single thread when work is known to exist but not be completed

Added some tests to verify some behavior of the dispatch strategy

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
andimarek and others added 4 commits October 24, 2025 17:56
@andimarek
introduce concept of DispatchStrategy
3243b9b
@zsmoore
Update dispatch strategy and impl bfs strategy
e81ea3c
@zsmoore
Merge branch 'master' into zmoore/dispatch-strategy
55dd69d
@zsmoore
Fix test to update with new dispatch whenCompletes
6efaf3b
@zsmoore

zsmoore commented Jan 6, 2026

Copy link
Copy Markdown
Author

Note to self to follow up on - I believe thinking on this again this may be a greedy level by level traversal rather than true BFS. I will come back to this and add a more complicated test to replicate the example I have to verify if async are waited on a level basis or we continue dispatching to child levels when work is known and only wait if we are blocked on just async calls

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@andimarek
andimarek requested a review from Copilot January 6, 2026 22:12
Copilot started reviewing on behalf of andimarek January 6, 2026 22:13
View session
Copilot AI reviewed Jan 6, 2026
View reviewed changes

Copilot AI left a comment

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a dispatch strategy framework for DataLoaders, allowing custom control over when and how DataLoader batches are dispatched. The primary implementation is a breadth-first search (BFS) dispatch strategy that handles synchronous and asynchronous chained DataLoaders by dispatching level-by-level with a fallback mechanism for async operations.

  • Adds a new DispatchStrategy interface with lifecycle hooks (onRegistryCreation, loadCalled, loadCompleted)
  • Implements BreadthFirstChainedDispatchStrategy with automatic BFS dispatching and scheduled fallback for async scenarios
  • Integrates dispatch strategies into DataLoaderRegistry and DataLoaderOptions for configuration

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
src/main/java/org/dataloader/DispatchStrategy.java New public API interface defining dispatch strategy lifecycle methods with a NO_OP default implementation
src/main/java/org/dataloader/strategy/BreadthFirstChainedDispatchStrategy.java New BFS strategy implementation with fallback dispatch mechanism for async chained loaders
src/main/java/org/dataloader/DataLoaderRegistry.java Integrates dispatch strategy into registry builder, applies strategy to all registered DataLoaders
src/main/java/org/dataloader/DataLoaderOptions.java Adds dispatch strategy field and builder methods to options
src/main/java/org/dataloader/DataLoaderHelper.java Calls dispatch strategy lifecycle hooks when loads are initiated and completed
src/main/java/org/dataloader/registries/ScheduledDataLoaderRegistry.java Updates to use NO_OP dispatch strategy (maintains existing behavior)
src/test/java/org/dataloader/strategy/BreadthFirstChainedDispatchStrategyTest.java Comprehensive test suite covering single-depth, chained, async, and level-by-level dispatch scenarios
src/test/java/org/dataloader/DataLoaderCacheMapTest.java Updates expected dependent counts to account for dispatch strategy hooks

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/main/java/org/dataloader/DataLoaderOptions.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/main/java/org/dataloader/strategy/GreedyLevelByLevelChainedDispatchStrategy.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/test/java/org/dataloader/strategy/GreedyLevelByLevelChainedDispatchStrategyTest.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/main/java/org/dataloader/DataLoaderOptions.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/main/java/org/dataloader/DispatchStrategy.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/main/java/org/dataloader/strategy/BreadthFirstChainedDispatchStrategy.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/main/java/org/dataloader/strategy/GreedyLevelByLevelChainedDispatchStrategy.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/main/java/org/dataloader/DataLoaderOptions.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/main/java/org/dataloader/DataLoaderRegistry.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/main/java/org/dataloader/DataLoaderRegistry.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

@graphql-java graphql-java deleted a comment from Copilot AI Jan 6, 2026
zsmoore and others added 15 commits January 8, 2026 13:07
@zsmoore
Update src/main/java/org/dataloader/DataLoaderOptions.java
5e42344
Co-authored-by: Copilot <[email protected]>
@zsmoore
Minor review comments
7492d2a
@zsmoore
Update src/main/java/org/dataloader/DataLoaderOptions.java
ab0ca13
Co-authored-by: Copilot <[email protected]>
@zsmoore
Update src/main/java/org/dataloader/DispatchStrategy.java
dc98749
Co-authored-by: Copilot <[email protected]>
@zsmoore
Update src/main/java/org/dataloader/DataLoaderRegistry.java
d9a514f
Co-authored-by: Copilot <[email protected]>
@zsmoore
Review comments
01a72d6
@zsmoore
Merge branch 'zmoore/dispatch-strategy' of github.com:zsmoore/java-da…
58af742
…taloader into zmoore/dispatch-strategy
@zsmoore
Update src/main/java/org/dataloader/strategy/BreadthFirstChainedDispa…
26e6835
…tchStrategy.java

Co-authored-by: Copilot <[email protected]>
@zsmoore
Javadoc
6d2db60
@zsmoore
Merge branch 'zmoore/dispatch-strategy' of github.com:zsmoore/java-da…
797761d
…taloader into zmoore/dispatch-strategy
@zsmoore
Update src/main/java/org/dataloader/strategy/BreadthFirstChainedDispa…
ab222f2
…tchStrategy.java

Co-authored-by: Copilot <[email protected]>
@zsmoore
Synchronize resetState
2420b82
@zsmoore
Merge branch 'zmoore/dispatch-strategy' of github.com:zsmoore/java-da…
1ccd687
…taloader into zmoore/dispatch-strategy
@zsmoore
Add complicated test case
4c922c5
@zsmoore
Rename and doc
fa1e720
@zsmoore

zsmoore commented Jan 8, 2026

Copy link
Copy Markdown
Author

Note to self to follow up on - I believe thinking on this again this may be a greedy level by level traversal rather than true BFS. I will come back to this and add a more complicated test to replicate the example I have to verify if async are waited on a level basis or we continue dispatching to child levels when work is known and only wait if we are blocked on just async calls

Wrote out a fairly complicated test in GreedyLevelByLevelChainedDispatchStrategyStressTest to verify the behavior in practice. As a result renamed this as its not a true BFS. In practice we see increased batch sizes compared to our previous ticker mode with scheduled registry so while this is not a true level by level traversal we still see a great balance of speed + efficiency in our real life scenario

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@zsmoore

zsmoore commented Jan 12, 2026

Copy link
Copy Markdown
Author

@andimarek This one should be about ready. Let me know what you think when you're available to review

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

bbakerman
bbakerman reviewed Jan 13, 2026
View reviewed changes
Comment thread src/main/java/org/dataloader/strategy/GreedyLevelByLevelChainedDispatchStrategy.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

bbakerman
bbakerman reviewed Jan 13, 2026
View reviewed changes
Comment thread src/main/java/org/dataloader/DataLoaderHelper.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

bbakerman
bbakerman reviewed Jan 13, 2026
View reviewed changes
Comment thread src/main/java/org/dataloader/DataLoaderHelper.java Resolved

Uh oh!

There was an error while loading. Please reload this page.

bbakerman
bbakerman approved these changes Jan 13, 2026
View reviewed changes

@bbakerman bbakerman left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am happy with this PR - modulo the question on whenComplete ordering

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bbakerman

bbakerman commented Jan 13, 2026

Copy link
Copy Markdown
Member

ps the test are failing

GreedyLevelByLevelChainedDispatchStrategyTest > singleDepthLoadSucceedsMultipleTimes() FAILED
    java.lang.NullPointerException: Cannot invoke "java.lang.Runnable.run()" because "this.onIteration" is null
        at org.dataloader.strategy.GreedyLevelByLevelChainedDispatchStrategy.triggerDeterministicDispatch(GreedyLevelByLevelChainedDispatchStrategy.java:82)
        at org.dataloader.strategy.GreedyLevelByLevelChainedDispatchStrategy.loadCalled(GreedyLevelByLevelChainedDispatchStrategy.java:61)
        at org.dataloader.DataLoaderHelper.load(DataLoaderHelper.java:191)
        at org.dataloader.DataLoader.loadImpl(DataLoader.java:202)
        at org.dataloader.DataLoader.load(DataLoader.java:216)
        at 
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

andimarek added 2 commits January 13, 2026 13:32
@andimarek
expand Strategy arguments, make sure strategy is called earlier
1b3bec4
@andimarek
public api annotation
0164f95
@andimarek

andimarek commented Jan 13, 2026

Copy link
Copy Markdown
Member

ps the test are failing

GreedyLevelByLevelChainedDispatchStrategyTest > singleDepthLoadSucceedsMultipleTimes() FAILED
    java.lang.NullPointerException: Cannot invoke "java.lang.Runnable.run()" because "this.onIteration" is null
        at org.dataloader.strategy.GreedyLevelByLevelChainedDispatchStrategy.triggerDeterministicDispatch(GreedyLevelByLevelChainedDispatchStrategy.java:82)
        at org.dataloader.strategy.GreedyLevelByLevelChainedDispatchStrategy.loadCalled(GreedyLevelByLevelChainedDispatchStrategy.java:61)
        at org.dataloader.DataLoaderHelper.load(DataLoaderHelper.java:191)
        at org.dataloader.DataLoader.loadImpl(DataLoader.java:202)
        at org.dataloader.DataLoader.load(DataLoader.java:216)
        at 

I fixed that.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@andimarek

andimarek commented Jan 13, 2026

Copy link
Copy Markdown
Member

@zsmoore @bbakerman I changed the strategy callback a bit to ensure it is always called before the load itself is returned.

@zsmoore the stress test is failing now (not sure if that was the result of my change, it could be).

I will continue to review.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@andimarek

andimarek commented Jan 13, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

@zsmoore some questions, clarifications:

In your test your "chained dataloader" is actually a few DataLoader.load calls in parallel. What we refer normally to as chained dataloader is

dl1.load(key1).compose(result1 -> var key2 <based on result1)>; return dl2.load(key2))

In CF terms we compose the result of 1 DL to call a second one.

We also have a loadMany function that would server the as the same purpose I believe.

In your test dispatchGoesByLevel you do actually something we have not really seen before I believe: you use a new DataLoader inside the BatchLoader itself, correct? You do it twice inside "root" and inside "secondLevel".

This is again a bit different than the use cased described above we called "chained".

So that is what you are referring to as "level"?

Regarding your strategy:

In my understanding it works like this:
First load triggers dispatch. The dispatch keep calling dispatch as long as there is work todo. You keep a lock for the whole dispatch time. In case the work is not done synchronously it will triggers a delayed dispatch after 30ms to dispatch all DataLoader that were asynchronously queued.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@zsmoore
Fix up tests due to chaining of whenComplete
006c0cf
@zsmoore

zsmoore commented Jan 27, 2026

Copy link
Copy Markdown
Author

@bbakerman @andimarek apologies for the delay, I was ill the past couple of weeks.

Changes since last commits -
1 - Folded the 2 whenCompletes into 1, i agree we can keep the order deterministic for the callbacks this way
2 - Changes to tests WRT equality, dependencies, and exception unfolding Why? With the latest change which made the returned future the chained '.whenComplete' future, we now cannot use an equality assertion as the future is not pass through. This is most likely the preferred behavior as we won't lose exceptions and will bubble them up.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@zsmoore

zsmoore commented Jan 27, 2026

Copy link
Copy Markdown
Author

@zsmoore some questions, clarifications:

In your test your "chained dataloader" is actually a few DataLoader.load calls in parallel. What we refer normally to as chained dataloader is

dl1.load(key1).compose(result1 -> var key2 <based on result1)>; return dl2.load(key2))

In CF terms we compose the result of 1 DL to call a second one.

We also have a loadMany function that would server the as the same purpose I believe.

In your test dispatchGoesByLevel you do actually something we have not really seen before I believe: you use a new DataLoader inside the BatchLoader itself, correct? You do it twice inside "root" and inside "secondLevel".

This is again a bit different than the use cased described above we called "chained".

So that is what you are referring to as "level"?

Regarding your strategy:

In my understanding it works like this: First load triggers dispatch. The dispatch keep calling dispatch as long as there is work todo. You keep a lock for the whole dispatch time. In case the work is not done synchronously it will triggers a delayed dispatch after 30ms to dispatch all DataLoader that were asynchronously queued.

For the understanding of the strategy yes that is the correct understanding of how the strategy is working.

As for the verbage around chained I see the point around the mixing of chaining. We actually have both cases in our use case take for example

Nested chaining
First Name DataLoader (input profile nickname) ->
Call Profile DataLoader (input profile nickname)
return profile.firstName

Top level chaining
(Get Location)
Call Profile DataLoader.compose(call location dataloader using profile id)

When referring to level, I think of the entire call chain as a tree structure.

Take both example above

Nested Chaining
Root function = 0
Call FirstName DL = 1
Call Profile DL = 2

Top level Chaining
Root function = 0
Call ProfileDL = 1
Call LocationDL = 2

In both cases the 'level' or depth of the chain is the same as it takes the same amount of subsequent DL dependents to get the overall result but i believe the difference comes down to organization. You could still swap the structure to be
LocationDL (input profile nickname)
Call ProfileDL (find by profile nickname)
use profileID to call Location endpoint and return

Where this composition is encapsulated in the overall DL itself instead of a top level unfolding of the calls.

Let me know if i understood the question correctly. Thanks again and apologies for the delay

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@zsmoore

zsmoore commented Jan 27, 2026

Copy link
Copy Markdown
Author

Also WRT the parallelization, that is just added to bump up the complexity for the stress test, nothing really core to the test here

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@zsmoore
unify style
52b7da7
@zsmoore

zsmoore commented Feb 4, 2026

Copy link
Copy Markdown
Author

@bbakerman @andimarek friendly ping on this one

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@andimarek

andimarek commented Feb 13, 2026

Copy link
Copy Markdown
Member

hey @zsmoore ... sorry it has been really busy the last week. Will try to come back to this soon.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@zsmoore

zsmoore commented Feb 13, 2026

Copy link
Copy Markdown
Author

No worries at all, the beginning of the year is always crazy

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@github-actions

github-actions Bot commented Apr 15, 2026

Copy link
Copy Markdown

Hello, this pull request has been inactive for 60 days, so we're marking it as stale. If you would like to continue working on this pull request, please make an update within the next 30 days, or we'll close the pull request.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@github-actions github-actions Bot added the Stale label Apr 15, 2026
@github-actions

github-actions Bot commented May 15, 2026

Copy link
Copy Markdown

Hello, as this pull request has been inactive for 90 days, we're closing this pull request. We always welcome contributions, and if you would like to continue, please open a new pull request.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@github-actions github-actions Bot closed this May 15, 2026
@dondonz

dondonz commented May 17, 2026

Copy link
Copy Markdown
Member

Reopening - I wanted to chat about this before the bot closes it

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dondonz dondonz reopened this May 17, 2026
@github-actions github-actions Bot removed the Stale label May 18, 2026
@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown

Hello, this pull request has been inactive for 60 days, so we're marking it as stale. If you would like to continue working on this pull request, please make an update within the next 30 days, or we'll close the pull request.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@github-actions github-actions Bot added the Stale label Jul 18, 2026
@kaqqao

kaqqao commented Jul 18, 2026

Copy link
Copy Markdown

This bot is a scourge...

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@github-actions github-actions Bot removed the Stale label Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

Copilot code review Copilot
Copilot review effort
Applies to this pull request for everyone.Learn more about Copilot code review.
Copilot left review comments
@bbakerman bbakerman bbakerman approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

6 participants

@zsmoore @bbakerman @andimarek @dondonz @kaqqao
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.