Skip to content

refactor: cleanup coroutine scopes#500

Draft
stuebingerb wants to merge 1 commit intomainfrom
refactor/better-coroutine-scope
Draft

refactor: cleanup coroutine scopes#500
stuebingerb wants to merge 1 commit intomainfrom
refactor/better-coroutine-scope

Conversation

@stuebingerb
Copy link
Copy Markdown
Owner

Remove coroutineContext from execution context and use one with the provided dispatcher.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 17, 2026

Codecov Report

❌ Patch coverage is 94.28571% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.65%. Comparing base (5fd5503) to head (8e4332d).
⚠️ Report is 90 commits behind head on main.

Files with missing lines Patch % Lines
...raphql/schema/execution/ParallelRequestExecutor.kt 93.75% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #500      +/-   ##
==========================================
- Coverage   83.66%   83.65%   -0.01%     
==========================================
  Files         152      152              
  Lines        4896     4894       -2     
  Branches      853      853              
==========================================
- Hits         4096     4094       -2     
  Misses        498      498              
  Partials      302      302              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jan 17, 2026

🐰 Bencher Report

Branchrefactor/better-coroutine-scope
Testbedubuntu-latest

🚨 1 Alert

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Lower Boundary
(Limit %)
com.apurebase.kgraphql.RequestCachingBenchmark.invalidRequestThroughput
operations / second (ops/s) x 1e3
📈 plot
🚷 threshold
🚨 alert (🔔)
35.42 ops/s x 1e3
(-83.92%)Baseline: 220.31 ops/s x 1e3
126.24 ops/s x 1e3
(356.41%)

Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
ThroughputBenchmark Result
operations / second (ops/s)
(Result Δ%)
Lower Boundary
operations / second (ops/s)
(Limit %)
com.apurebase.kgraphql.FunctionExecutionBenchmark.benchmarkFunctionExecution📈 view plot
🚷 view threshold
6,078,408.66 ops/s
(+1.69%)Baseline: 5,977,390.47 ops/s
5,507,281.10 ops/s
(90.60%)
com.apurebase.kgraphql.ParallelExecutionBenchmark.queryBenchmark📈 view plot
🚷 view threshold
768.99 ms
(-0.21%)Baseline: 770.63 ms
772.70 ms
(99.52%)
com.apurebase.kgraphql.QueryBenchmark.largeList📈 view plot
🚷 view threshold
4.42 ops/s
(+51.59%)Baseline: 2.92 ops/s
-4.54 ops/s
(-102.79%)
com.apurebase.kgraphql.QueryBenchmark.manyChildren📈 view plot
🚷 view threshold
159.60 ops/s
(-14.36%)Baseline: 186.36 ops/s
135.74 ops/s
(85.05%)
com.apurebase.kgraphql.QueryBenchmark.manyDataChildren📈 view plot
🚷 view threshold
8.80 ops/s
(-1.51%)Baseline: 8.93 ops/s
8.66 ops/s
(98.42%)
com.apurebase.kgraphql.QueryBenchmark.manyOperations📈 view plot
🚷 view threshold
257.23 ops/s
(-1.96%)Baseline: 262.37 ops/s
151.96 ops/s
(59.08%)
com.apurebase.kgraphql.QueryBenchmark.nestedObject📈 view plot
🚷 view threshold
8,809.67 ops/s
(+4.41%)Baseline: 8,437.38 ops/s
6,022.97 ops/s
(68.37%)
com.apurebase.kgraphql.RequestCachingBenchmark.invalidRequest📈 view plot
🚷 view threshold
🚨 view alert (🔔)
35,420.50 ops/s
(-83.92%)Baseline: 220,309.98 ops/s
126,242.23 ops/s
(356.41%)

com.apurebase.kgraphql.RequestCachingBenchmark.largeRequest📈 view plot
🚷 view threshold
8,563.10 ops/s
(+1.46%)Baseline: 8,439.89 ops/s
7,623.27 ops/s
(89.02%)
com.apurebase.kgraphql.RequestCachingBenchmark.smallRequest📈 view plot
🚷 view threshold
12,924.17 ops/s
(+2.00%)Baseline: 12,671.36 ops/s
11,155.95 ops/s
(86.32%)
com.apurebase.kgraphql.SimpleExecutionOverheadBenchmark.benchmark📈 view plot
🚷 view threshold
459,765.69 ops/s
(-2.18%)Baseline: 470,010.79 ops/s
455,828.82 ops/s
(99.14%)
🐰 View full continuous benchmarking report in Bencher

Base automatically changed from refactor/minor-enhancements to main January 19, 2026 09:28
@stuebingerb stuebingerb force-pushed the refactor/better-coroutine-scope branch from f44e1ee to fb92944 Compare January 19, 2026 10:23
@stuebingerb stuebingerb marked this pull request as draft January 25, 2026 09:34
@stuebingerb
Copy link
Copy Markdown
Owner Author

Performance regression needs further analysis

Remove coroutineContext from execution context and use one with the
provided dispatcher.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="kgraphql/api/kgraphql.api">

<violation number="1" location="kgraphql/api/kgraphql.api:871">
P2: This changes a public constructor signature and introduces a binary-incompatible API break for existing consumers compiled against the previous 4-parameter constructor.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread kgraphql/api/kgraphql.api

public final class com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor$ExecutionContext {
public fun <init> (Lkotlinx/coroutines/CoroutineScope;Lcom/apurebase/kgraphql/request/Variables;Lcom/apurebase/kgraphql/Context;Ljava/util/Map;)V
public fun <init> (Lcom/apurebase/kgraphql/request/Variables;Lcom/apurebase/kgraphql/Context;Ljava/util/Map;)V
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 23, 2026

Choose a reason for hiding this comment

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

P2: This changes a public constructor signature and introduces a binary-incompatible API break for existing consumers compiled against the previous 4-parameter constructor.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kgraphql/api/kgraphql.api, line 871:

<comment>This changes a public constructor signature and introduces a binary-incompatible API break for existing consumers compiled against the previous 4-parameter constructor.</comment>

<file context>
@@ -868,10 +868,9 @@ public final class com/apurebase/kgraphql/schema/execution/ParallelRequestExecut
 
 public final class com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor$ExecutionContext {
-	public fun <init> (Lkotlinx/coroutines/CoroutineScope;Lcom/apurebase/kgraphql/request/Variables;Lcom/apurebase/kgraphql/Context;Ljava/util/Map;)V
+	public fun <init> (Lcom/apurebase/kgraphql/request/Variables;Lcom/apurebase/kgraphql/Context;Ljava/util/Map;)V
 	public final fun getLoaders ()Ljava/util/Map;
 	public final fun getRequestContext ()Lcom/apurebase/kgraphql/Context;
</file context>
Fix with Cubic

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0fe68c9f-0d2d-4efc-88bd-207f889c4c26

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor.kt (1)

236-263: Consider simplifying the coroutineScope { async { ... } } pattern.

The current pattern wraps the function body in coroutineScope { async { ... } } and returns a Deferred<ObjectNode>. However, since coroutineScope waits for all children (including the async) before returning, the returned Deferred is always already-completed by the time the function returns.

This adds overhead without benefit since:

  1. The coroutineScope blocks until the async completes
  2. Callers then call .await() on an already-completed Deferred

The parallelism for child fields IS correctly preserved via the inner async calls at lines 247 and 251. The issue is just the outer wrapper.

Note: This may relate to the "Performance regression needs further analysis" comment in the PR. Consider measuring whether this pattern impacts performance for deeply nested queries.

♻️ Alternative: Simplify to return value directly

If the function is always awaited immediately by callers, consider:

-    private suspend fun <T> createObjectNode(
-        ctx: ExecutionContext,
-        value: T,
-        node: Execution.Node,
-        type: Type
-    ): Deferred<ObjectNode> = coroutineScope {
-        async {
+    private suspend fun <T> createObjectNode(
+        ctx: ExecutionContext,
+        value: T,
+        node: Execution.Node,
+        type: Type
+    ): ObjectNode = coroutineScope {
             val objectNode = jsonNodeFactory.objectNode()
             // ... rest of implementation
             objectNode
-        }
     }

This would require updating call sites to wrap in async where parallelism is needed, but would remove the redundant async/await overhead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor.kt`
around lines 236 - 263, The function createObjectNode currently wraps its work
in coroutineScope { async { ... } } returning a Deferred that's
already-completed; remove the redundant outer coroutineScope/async and make
createObjectNode a suspend function that directly builds and returns an
ObjectNode (keeping inner async calls for child fields that call handleFragment
and handleProperty and awaiting those deferreds); update the function signature
to return ObjectNode (not Deferred<ObjectNode>) and adjust all call sites that
used .await() to call directly (or wrap in async there if true Deferred is
needed) so you preserve parallelism of the child deferreds while eliminating the
outer overhead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@kgraphql/api/kgraphql.api`:
- Around line 870-875: The change removed CoroutineScope from the
ExecutionContext API causing a breaking change: restore backward compatibility
by reintroducing a constructor overload for
com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor$ExecutionContext
that accepts (Variables, Context, Map, CoroutineScope) and re-add a getScope()
accessor (or provide a new method name that exposes the scope) so external
callers can still supply and read a custom scope; alternatively, add a
factory/helper method that accepts a CoroutineScope and constructs the existing
constructor, and mark the old/removed constructor as deprecated if you intend to
phase it out rather than immediately remove it.

---

Nitpick comments:
In
`@kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor.kt`:
- Around line 236-263: The function createObjectNode currently wraps its work in
coroutineScope { async { ... } } returning a Deferred that's already-completed;
remove the redundant outer coroutineScope/async and make createObjectNode a
suspend function that directly builds and returns an ObjectNode (keeping inner
async calls for child fields that call handleFragment and handleProperty and
awaiting those deferreds); update the function signature to return ObjectNode
(not Deferred<ObjectNode>) and adjust all call sites that used .await() to call
directly (or wrap in async there if true Deferred is needed) so you preserve
parallelism of the child deferreds while eliminating the outer overhead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ec18236-ae73-47d6-b85f-99eefc23da30

📥 Commits

Reviewing files that changed from the base of the PR and between 25d0ff7 and 8e4332d.

📒 Files selected for processing (4)
  • kgraphql/api/kgraphql.api
  • kgraphql/src/main/kotlin/com/apurebase/kgraphql/Extensions.kt
  • kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt
  • kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor.kt

Comment thread kgraphql/api/kgraphql.api
Comment on lines 870 to 875
public final class com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor$ExecutionContext {
public fun <init> (Lkotlinx/coroutines/CoroutineScope;Lcom/apurebase/kgraphql/request/Variables;Lcom/apurebase/kgraphql/Context;Ljava/util/Map;)V
public fun <init> (Lcom/apurebase/kgraphql/request/Variables;Lcom/apurebase/kgraphql/Context;Ljava/util/Map;)V
public final fun getLoaders ()Ljava/util/Map;
public final fun getRequestContext ()Lcom/apurebase/kgraphql/Context;
public final fun getScope ()Lkotlinx/coroutines/CoroutineScope;
public final fun getVariables ()Lcom/apurebase/kgraphql/request/Variables;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Breaking API change: ExecutionContext constructor and accessor modified.

The removal of CoroutineScope from the ExecutionContext constructor and the removal of getScope() constitute a breaking change for any consumers who:

  1. Construct ExecutionContext directly with a custom scope
  2. Access the scope via getScope() for custom async work

Consider:

  • Documenting this in release notes/migration guide
  • If this is part of a minor/patch version, consider deprecation before removal
  • If external consumers need scope access, consider alternative APIs (e.g., exposing structured concurrency helpers)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kgraphql/api/kgraphql.api` around lines 870 - 875, The change removed
CoroutineScope from the ExecutionContext API causing a breaking change: restore
backward compatibility by reintroducing a constructor overload for
com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor$ExecutionContext
that accepts (Variables, Context, Map, CoroutineScope) and re-add a getScope()
accessor (or provide a new method name that exposes the scope) so external
callers can still supply and read a custom scope; alternatively, add a
factory/helper method that accepts a CoroutineScope and constructs the existing
constructor, and mark the old/removed constructor as deprecated if you intend to
phase it out rather than immediately remove it.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 4 files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant