Skip to content

Add FutureGroup.addCancelable()#726

Open
nex3 wants to merge 3 commits into
dart-lang:mainfrom
nex3:future-group-add-cancelable
Open

Add FutureGroup.addCancelable()#726
nex3 wants to merge 3 commits into
dart-lang:mainfrom
nex3:future-group-add-cancelable

Conversation

@nex3

@nex3 nex3 commented Nov 26, 2024

Copy link
Copy Markdown
Member

No description provided.

@github-actions

Copy link
Copy Markdown

Package publishing

Package Version Status Publish tag (post-merge)
package:async 2.13.0 ready to publish async-v2.13.0
package:characters 1.4.0 ready to publish characters-v1.4.0
package:args 2.6.1-wip WIP (no publish necessary)
package:collection 1.19.1-wip WIP (no publish necessary)
package:convert 3.1.2 already published at pub.dev
package:crypto 3.0.6 already published at pub.dev
package:fixnum 1.1.1 already published at pub.dev
package:logging 1.3.0 already published at pub.dev
package:os_detect 2.0.3-wip WIP (no publish necessary)
package:path 1.9.1 already published at pub.dev
package:platform 3.1.6 already published at pub.dev
package:typed_data 1.4.0 already published at pub.dev

Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.

@github-actions

github-actions Bot commented Nov 26, 2024

Copy link
Copy Markdown

PR Health

License Headers ✔️
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
Files
no missing headers

All source files should start with a license header.

This check can be disabled by tagging the PR with skip-license-check.

API leaks ✔️

The following packages contain symbols visible in the public API, but not exported by the library. Export these symbols or remove them from your publicly visible API.

Package Leaked API symbol Leaking sources

This check can be disabled by tagging the PR with skip-leaking-check.

Breaking changes ✔️
Package Change Current Version New Version Needed Version Looking good?
async Non-Breaking 2.13.0 2.14.0-wip 2.14.0 ✔️

This check can be disabled by tagging the PR with skip-breaking-check.

Coverage ✔️
File Coverage
pkgs/async/lib/src/future_group.dart 💚 100 %

This check for test coverage is informational (issues shown here will not fail the PR).

This check can be disabled by tagging the PR with skip-coverage-check.

Changelog Entry ✔️
Package Changed Files

Changes to files need to be accounted for in their respective changelogs.

This check can be disabled by tagging the PR with skip-changelog-check.

@mosuem mosuem requested a review from a team April 17, 2025 13:01
@mosuem

mosuem commented Oct 7, 2025

Copy link
Copy Markdown
Member

@nex3 is it still your intention to land this PR? Just a friendly ping.

@nex3

nex3 commented Oct 7, 2025

Copy link
Copy Markdown
Member Author

I'd be happy to land it. I'm still waiting on a review.

@mosuem

mosuem commented Oct 8, 2025

Copy link
Copy Markdown
Member

Then redirecting the friendly ping to @lrhn or @natebosch :)

Comment thread pkgs/async/lib/src/future_group.dart Outdated
@override
void add(Future<T> task) {
void add(Future<T> task) =>
addCancelable(CancelableOperation.fromFuture(task));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I worry about the performance penalty here. By wrapping in a CancelableOperation we'd be creating 3 extra Completer instances along with a few book-keeping objects and function literals and tear-offs, for every Future added to the group.

Would it make sense to build a CancelableOperationGroup instead which has this overhead for the use cases that need it? It may be feasible to build as an adapter on FutureGroup to keep the implementation small.

Would it need to be much more complex than this?

class CancelableOperationGroup<T> {
  final FutureGroup<Object?> _group = FutureGroup();
  Future<List<T>> get future async =>
    (await _group.future).where((e) => e != _sentinel).whereType<T>().toList();
  void add(CancelableOperation<T> task) {
    final normalizedToSentinel =
      task.then((r) => r, onCancel: () => _sentinel);
    _group.add(normalizedToSentinel.valueOrCancellation);
  }
  // other members of FutureGroup may be useful to forward as well
  // bool get isClosed => _group.isClosed;
  // etc
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Now that you mention it, we can actually use the same technique in this class to avoid adding overhead for normal futures.

Comment thread pkgs/async/lib/src/future_group.dart Outdated
@nex3 nex3 force-pushed the future-group-add-cancelable branch from 6bc6363 to 32cd9b6 Compare November 26, 2025 23:14
@nex3 nex3 requested a review from natebosch November 26, 2025 23:37

@natebosch natebosch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks good, I don't think I have concerns about the new conditional and cast per value in the result. I also think this behavior for cancelled operations makes sense - it's easy enough to understand how "without adding a value" from addCancelable docs interacts with "in the order they were added" from the class doc.

cc @lrhn in case he does have concern about the behavior of cancelled operations.

@mosuem

mosuem commented Jun 22, 2026

Copy link
Copy Markdown
Member

@nex3 This is ready to merge I believe :)

@nex3

nex3 commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

I don't have the power to merge it.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants