-
Notifications
You must be signed in to change notification settings - Fork 125
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
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ | |
/// This is a helper library to make working with io easier. | ||
library dartdoc.io_utils; | ||
|
||
import 'dart:async'; | ||
import 'dart:collection'; | ||
import 'dart:convert'; | ||
import 'dart:io' as io; | ||
|
||
|
@@ -118,40 +120,91 @@ final RegExp partOfRegexp = RegExp('part of '); | |
'as Dartdoc 1.0.0') | ||
final RegExp newLinePartOfRegexp = RegExp('\npart of '); | ||
|
||
/// Best used with Future<void>. | ||
class MultiFutureTracker<T> { | ||
/// Approximate maximum number of simultaneous active Futures. | ||
final int parallel; | ||
typedef TaskQueueClosure<T> = Future<T> Function(); | ||
|
||
final Set<Future<T>> _trackedFutures = {}; | ||
class _TaskQueueItem<T> { | ||
_TaskQueueItem(this._closure, this._completer, {this.onComplete}); | ||
|
||
MultiFutureTracker(this.parallel); | ||
final TaskQueueClosure<T> _closure; | ||
final Completer<T> _completer; | ||
void Function() onComplete; | ||
|
||
/// Wait until fewer or equal to this many Futures are outstanding. | ||
Future<void> _waitUntil(int max) async { | ||
while (_trackedFutures.length > max) { | ||
await Future.any(_trackedFutures); | ||
Future<void> run() async { | ||
try { | ||
_completer.complete(await _closure()); | ||
} catch (e) { | ||
_completer.completeError(e); | ||
} finally { | ||
onComplete?.call(); | ||
} | ||
} | ||
} | ||
|
||
/// A task queue of Futures to be completed in parallel, throttling | ||
/// the number of simultaneous tasks. | ||
/// | ||
/// The tasks return results of type T. | ||
class TaskQueue<T> { | ||
/// Creates a task queue with a maximum number of simultaneous jobs. | ||
/// The [maxJobs] parameter defaults to the number of CPU cores on the | ||
/// system. | ||
TaskQueue({int maxJobs}) | ||
: maxJobs = maxJobs ?? io.Platform.numberOfProcessors; | ||
gspencergoog marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// The maximum number of jobs that this queue will run simultaneously. | ||
final int maxJobs; | ||
|
||
final Queue<_TaskQueueItem<T>> _pendingTasks = Queue<_TaskQueueItem<T>>(); | ||
final Set<_TaskQueueItem<T>> _activeTasks = <_TaskQueueItem<T>>{}; | ||
final Set<Completer<void>> _completeListeners = <Completer<void>>{}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
||
/// Returns a future that completes when all tasks in the [TaskQueue] are | ||
/// complete. | ||
Future<void> get tasksComplete { | ||
// In case this is called when there are no tasks, we want it to | ||
// signal complete immediately. | ||
if (_activeTasks.isEmpty && _pendingTasks.isEmpty) { | ||
return Future<void>.value(); | ||
} | ||
final completer = Completer<void>(); | ||
_completeListeners.add(completer); | ||
return completer.future; | ||
} | ||
|
||
/// Adds a single closure to the task queue, returning a future that | ||
/// completes when the task completes. | ||
Future<T> add(TaskQueueClosure<T> task) { | ||
final completer = Completer<T>(); | ||
_pendingTasks.add(_TaskQueueItem<T>(task, completer)); | ||
if (_activeTasks.length < maxJobs) { | ||
_processTask(); | ||
} | ||
return completer.future; | ||
} | ||
|
||
/// Generates a [Future] from the given closure and adds it to the queue, | ||
/// once the queue is sufficiently empty. The returned future completes | ||
/// when the generated [Future] has been added to the queue. | ||
/// | ||
/// If the closure does not handle its own exceptions, other calls to | ||
/// [addFutureFromClosure] or [wait] may trigger an exception. | ||
Future<void> addFutureFromClosure(Future<T> Function() closure) async { | ||
await _waitUntil(parallel - 1); | ||
Future<void> future = closure(); | ||
_trackedFutures.add(future); | ||
// ignore: unawaited_futures | ||
future.then((f) { | ||
_trackedFutures.remove(future); | ||
}, onError: (s, e) { | ||
_trackedFutures.remove(future); | ||
}); | ||
// Process a single task. | ||
void _processTask() { | ||
if (_pendingTasks.isNotEmpty && _activeTasks.length <= maxJobs) { | ||
final item = _pendingTasks.removeFirst(); | ||
_activeTasks.add(item); | ||
item.onComplete = () { | ||
_activeTasks.remove(item); | ||
_processTask(); | ||
}; | ||
item.run(); | ||
} else { | ||
_checkForCompletion(); | ||
} | ||
} | ||
|
||
/// Wait until all futures added so far have completed. | ||
Future<void> wait() async => await _waitUntil(0); | ||
void _checkForCompletion() { | ||
if (_activeTasks.isEmpty && _pendingTasks.isEmpty) { | ||
for (final completer in _completeListeners) { | ||
if (!completer.isCompleted) { | ||
completer.complete(); | ||
} | ||
} | ||
_completeListeners.clear(); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
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.
Oh, this makes things so much easier to read.