Skip to content

Fix concurrency issues in tool execution #2730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 6, 2021

Conversation

gspencergoog
Copy link
Collaborator

Description

This replaces the task queue called MultiFutureTracker with a TaskQueue, which fixes the concurrency issue where the old class didn't really wait for tasks to complete before starting a new one.

This uses a completer to provide a new future to wait for each task, uses a Queue to keep the tasks, and returns a Future<T> with the result of the task instead of waiting on a Future<void>.

There is also a future to wait on that will complete when the queue is empty (replacing wait) called tasksComplete.

Related Issues

Tests

  • Updated tests to use the new API.

@gspencergoog gspencergoog requested a review from srawlins August 5, 2021 21:46
@google-cla google-cla bot added the cla: yes Google CLA check succeeded. label Aug 5, 2021
@gspencergoog gspencergoog changed the title Fix concurrency issues Fix concurrency issues in tool execution Aug 5, 2021
Copy link
Member

@srawlins srawlins left a comment

Choose a reason for hiding this comment

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

Great implementation. Just a few comments.

Copy link
Member

@srawlins srawlins left a comment

Choose a reason for hiding this comment

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

I love this, but I'd love a review from @jcollins-g as well before we merge.

Copy link
Contributor

@jcollins-g jcollins-g left a comment

Choose a reason for hiding this comment

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

This looks really good, a definite improvement on my original implementation. Function typedefs for the win, too!

class MultiFutureTracker<T> {
/// Approximate maximum number of simultaneous active Futures.
final int parallel;
typedef TaskQueueClosure<T> = Future<T> Function();
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, this makes things so much easier to read.


final Queue<_TaskQueueItem<T>> _pendingTasks = Queue<_TaskQueueItem<T>>();
final Set<_TaskQueueItem<T>> _activeTasks = <_TaskQueueItem<T>>{};
final Set<Completer<void>> _completeListeners = <Completer<void>>{};
Copy link
Contributor

Choose a reason for hiding this comment

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

yes, this structure is more easily verified than the queue-length original version for waiting on completion. nice.

Future<T> add(TaskQueueClosure<T> task) {
final completer = Completer<T>();
_pendingTasks.add(_TaskQueueItem<T>(task, completer));
_processTasks();
Copy link
Contributor

Choose a reason for hiding this comment

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

IIUC this seems like it will spawn a lot of _processTasks() that could potentially do "duplicate" work in the case where the _processTask() from the onComplete has it covered. This is probably fine though as the duplicates are written to collapse and do nothing. The old version probably got into trouble doing things cleverly to avoid this...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, possibly, but it would also make sure that when a new task is added, it starts running right away.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure. I was thinking of the case though when the maximum concurrency is reached and _processTasks() here doesn't have any useful effect. It seems like it should be OK just to inline _processTasks() here and eliminate an unawaited future that sometimes doesn't do anything. Your choice, and of course, only if it works just as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, good point, that makes sense. OK, I inlined it.

Copy link
Contributor

@jcollins-g jcollins-g left a comment

Choose a reason for hiding this comment

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

lgtm!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes Google CLA check succeeded.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Non-Dart Tool invocations aren't throttled
3 participants