Skip to content

GROOVY-12459: Failed AsyncChannel producer never closes the channel so for await hangs and the exception is dropped - #2987

Open
daniellansun wants to merge 1 commit into
masterfrom
GROOVY-12459
Open

daniellansun wants to merge 1 commit into
masterfrom
GROOVY-12459

Conversation

@daniellansun

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

JMH summary — classic (commit d642669)

Speedup vs trailing 90-day baseline on gh-pages. Higher = faster.
1.00 = in line with history. Per-benchmark ratio, geomean within group.
Time-per-op units inverted so direction is consistent. The calibrated
column divides out this runner's speed vs the baseline hardware, as
measured by Groovy-independent pure-Java ruler benchmarks.

Group Speedup Calibrated n
bench 0.938 × 1.015 × 124
core 1.084 × 1.031 × 108
grails 1.374 × 1.173 × 80

⚠️ 1 benchmark at least 1.5× slower than the 90-day baseline:

  • org.apache.groovy.perf.OperatorBench.spaceshipOperator — 1.56× slower (calibrated)

⚠️ Runner speed differs ≥15% from the historical baseline hardware for: core-hz, grails-ez. Raw speedups are not meaningful for those parts — use the calibrated column.

Runner calibration (this run vs baseline hardware): bench 0.93× (27 rulers) · core-ag 0.96× (3 rulers) · core-hz 1.24× (3 rulers) · grails-ad 0.96× (3 rulers) · grails-ez 1.38× (3 rulers)

Baseline: dev/bench/jmh/<part>/classic/data.js on gh-pages, trailing 90 days. Daily dashboard · Per-suite raw data

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

JMH summary — indy (commit d642669)

Speedup vs trailing 90-day baseline on gh-pages. Higher = faster.
1.00 = in line with history. Per-benchmark ratio, geomean within group.
Time-per-op units inverted so direction is consistent. The calibrated
column divides out this runner's speed vs the baseline hardware, as
measured by Groovy-independent pure-Java ruler benchmarks.

Group Speedup Calibrated n
bench 0.965 × 1.079 × 124
core 2.288 × 2.475 × 108
grails 1.927 × 1.658 × 80

No benchmark is ≥1.5× slower than its 90-day baseline.

⚠️ Runner speed differs ≥15% from the historical baseline hardware for: grails-ez. Raw speedups are not meaningful for those parts — use the calibrated column.

Runner calibration (this run vs baseline hardware): bench 0.91× (27 rulers) · core-ag 0.92× (3 rulers) · core-hz 0.94× (3 rulers) · grails-ad 0.94× (3 rulers) · grails-ez 1.38× (3 rulers)

Baseline: dev/bench/jmh/<part>/indy/data.js on gh-pages, trailing 90 days. Daily dashboard · Per-suite raw data

…o `for await` hangs and the exception is dropped
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.4314%. Comparing base (d77e6df) to head (570c912).

Files with missing lines Patch % Lines
.../org/apache/groovy/runtime/async/AsyncSupport.java 0.0000% 2 Missing ⚠️
...ache/groovy/runtime/async/DefaultAsyncChannel.java 93.7500% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##               master      #2987        +/-   ##
==================================================
+ Coverage     72.4166%   72.4314%   +0.0147%     
- Complexity      40606      40631        +25     
==================================================
  Files            1643       1643                
  Lines          142633     142673        +40     
  Branches        26788      26796         +8     
==================================================
+ Hits           103290     103340        +50     
+ Misses          29850      29841         -9     
+ Partials         9493       9492         -1     
Files with missing lines Coverage Δ
src/main/java/groovy/concurrent/AsyncChannel.java 100.0000% <100.0000%> (+20.3125%) ⬆️
.../main/java/groovy/concurrent/BroadcastChannel.java 73.6000% <100.0000%> (+5.6755%) ⬆️
...java/groovy/concurrent/ChannelClosedException.java 100.0000% <ø> (ø)
src/main/java/groovy/concurrent/ChannelSelect.java 90.1408% <100.0000%> (+0.5886%) ⬆️
...ache/groovy/runtime/async/DefaultAsyncChannel.java 88.4422% <93.7500%> (+1.3454%) ⬆️
.../org/apache/groovy/runtime/async/AsyncSupport.java 77.6398% <0.0000%> (-0.9766%) ⬇️

... and 11 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
B Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@daniellansun

daniellansun commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

Current solution borrows ideas from C#, it looks not very groovy, we can try to find better solution.

using System.Threading.Channels;

var channel = Channel.CreateBounded<int>(2);

_ = Task.Run(async () =>
{
    try
    {
        for (var i = 1; i <= 3; i++)
        {
            if (i == 3)
                throw new InvalidOperationException("sensor failed");
            await channel.Writer.WriteAsync(i * i);
        }
        channel.Writer.Complete();
    }
    catch (Exception failure)
    {
        channel.Writer.Complete(failure);
    }
});

var seen = new List<int>();
try
{
    await foreach (var value in channel.Reader.ReadAllAsync())
        seen.Add(value);
}
catch (InvalidOperationException e)
{
    Console.WriteLine(e.Message); // sensor failed
}

// seen == [1, 4]

@daniellansun

Copy link
Copy Markdown
Contributor Author

Here is the JavaScript solution:

let n = 0;
const stream = new ReadableStream({
  pull(controller) {
    n += 1;
    if (n === 3) {
      controller.error(new Error('sensor failed')); // close with the cause
      return;
    }
    controller.enqueue(n * n);
  }
});

const seen = [];
try {
  for await (const value of stream) {
    seen.push(value);
  }
} catch (failure) {
  console.log(failure.message); // sensor failed
}

@testlens-app

testlens-app Bot commented Oct 18, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: 570c912
▶️ Tests: 122256 executed
⚪️ Checks: 33/33 completed


Learn more about TestLens at testlens.app/docs.

@daniellansun
daniellansun requested a balanced review from Copilot October 18, 2026 16:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

This branch has not been deployed

No deployments
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.

3 participants