-
Notifications
You must be signed in to change notification settings - Fork 232
Parallel fetching of available versions #2280
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
31 commits
Select commit
Hold shift + click to select a range
e9869e5
First try
sigurdm 0fd682e
Remove spurious newline
sigurdm 3a86142
Tidy retriever
sigurdm 9a284a1
Handle no latest version in server response
sigurdm dc0b904
Handle no latest version in server response, take 2
sigurdm 65c1ace
Make retriever private
sigurdm bc179d8
Add test for retriever error handling
sigurdm fb5d23b
Make minByAsync stable
sigurdm 21ddd5c
Handle getting pubspec of missing package
sigurdm c6b4533
Store current zone in Retriever
sigurdm 7b8249b
Fox doccomment
sigurdm 72ac701
Address reviews
sigurdm 670755a
Only run prefetching when 'in the zone'
sigurdm 6bdcf53
Address review
sigurdm 49d165e
Fix typecheck
sigurdm baedad2
Reuse parsed versions
sigurdm de6f1ad
Adfress review
sigurdm d33c74a
Improve scheduler
sigurdm 618c822
Simplify processing loop
sigurdm 9a4fdc9
Use correct symbol as zone-key
sigurdm 893ffc2
Remove unused variable
sigurdm 3652db6
Merge branch 'master' into parallel_get
sigurdm 5c39d4c
Fix lints
sigurdm af10ead
Merge branch 'parallel_get' of github.com:sigurdm/pub into parallel_get
sigurdm de1905f
More lints
sigurdm 7155aba
Improve docs
sigurdm 2060d9c
Merge branch 'master' into parallel_get
sigurdm 4bbf3b5
Address review
sigurdm fb122eb
Improve doc
sigurdm 57b7e94
Only do one git operation at a time
sigurdm 42d0ce5
Also restrict access from describeUncached
sigurdm 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
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 |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// Copyright (c) 2019, 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. | ||
|
||
import 'dart:async'; | ||
import 'dart:collection'; | ||
|
||
import 'package:pool/pool.dart'; | ||
import 'package:pedantic/pedantic.dart'; | ||
|
||
/// Handles rate-limited scheduling of tasks. | ||
/// | ||
/// Tasks are identified by a jobId of type [J] (should be useful as a Hash-key) | ||
/// and run with a supplied async function. | ||
/// | ||
/// Designed to allow speculatively running tasks that will likely be needed | ||
/// later with [withPrescheduling]. | ||
/// | ||
/// Errors thrown by tasks scheduled with the `preschedule` callback will only | ||
/// be triggered when you await the [Future] returned by [schedule]. | ||
/// | ||
/// The operation will run in the [Zone] that the task was in when enqueued. | ||
/// | ||
/// If a task if [preschedule]d and later [schedule]d before the operation is | ||
/// started, the task will go in front of the queue with the zone of the | ||
/// [schedule] operation. | ||
/// | ||
/// Example: | ||
/// | ||
/// ```dart | ||
/// // A scheduler that, given a uri, gets that page and returns the body | ||
/// final scheduler = RateLimitedScheduler(http.read); | ||
/// | ||
/// scheduler.withPresceduling((preschedule) { | ||
/// // Start fetching `pub.dev` and `dart.dev` in the background. | ||
/// scheduler.preschedule(Uri.parse('https://pub.dev/')); | ||
/// scheduler.preschedule(Uri.parse('https://dart.dev/')); | ||
/// // ... do time-consuming task. | ||
/// // Now we actually need `pub.dev`. | ||
/// final pubDevBody = | ||
/// await scheduler.schedule(Uri.parse('https://pub.dev/')); | ||
/// // if the `dart.dev` task has not started yet, it will be canceled when | ||
/// // leaving `withPresceduling`. | ||
/// }); | ||
/// ``` | ||
class RateLimitedScheduler<J, V> { | ||
final Future<V> Function(J) _runJob; | ||
|
||
/// The results of ongoing and finished jobs. | ||
final Map<J, Completer<V>> _cache = <J, Completer<V>>{}; | ||
|
||
/// Tasks that are waiting to be run. | ||
final Queue<_Task<J>> _queue = Queue<_Task<J>>(); | ||
|
||
/// Rate limits the number of concurrent jobs. | ||
final Pool _pool; | ||
|
||
/// Jobs that have started running. | ||
final Set<J> _started = {}; | ||
|
||
RateLimitedScheduler(Future<V> Function(J) runJob, | ||
{maxConcurrentOperations = 10}) | ||
: _runJob = runJob, | ||
_pool = Pool(maxConcurrentOperations); | ||
|
||
/// Pick the next task in [_queue] and run it. | ||
/// | ||
/// If the task is already in [_started] it will not be run again. | ||
Future<void> _processNextTask() async { | ||
if (_queue.isEmpty) { | ||
return; | ||
} | ||
final task = _queue.removeFirst(); | ||
final completer = _cache[task.jobId]; | ||
|
||
if (!_started.add(task.jobId)) { | ||
return; | ||
} | ||
|
||
// Use an async function to catch sync exceptions from _runJob. | ||
Future<V> runJob() async { | ||
return await task.zone.runUnary(_runJob, task.jobId); | ||
} | ||
|
||
completer.complete(runJob()); | ||
// Listen to errors on the completer: | ||
// this will make errors thrown by [_run] not | ||
// become uncaught. | ||
// | ||
// They will still show up for other listeners of the future. | ||
await completer.future.catchError((_) {}); | ||
} | ||
|
||
/// Calls [callback] with a function that can pre-schedule jobs. | ||
/// | ||
/// When [callback] returns, all jobs that where prescheduled by [callback] | ||
/// that have not started running will be removed from the work queue | ||
/// (if they have been added seperately by [schedule] they will still be | ||
/// executed). | ||
Future<R> withPrescheduling<R>( | ||
FutureOr<R> Function(void Function(J) preschedule) callback, | ||
) async { | ||
final prescheduled = <_Task>{}; | ||
try { | ||
return await callback((jobId) { | ||
if (_started.contains(jobId)) return; | ||
final task = _Task(jobId, Zone.current); | ||
_cache.putIfAbsent(jobId, () => Completer()); | ||
_queue.addLast(task); | ||
prescheduled.add(task); | ||
|
||
unawaited(_pool.withResource(_processNextTask)); | ||
}); | ||
} finally { | ||
_queue.removeWhere(prescheduled.contains); | ||
} | ||
} | ||
|
||
/// Returns a future that completed with the result of running [jobId]. | ||
/// | ||
/// If [jobId] has already run, the cached result will be returned. | ||
/// If [jobId] is not yet running, it will go to the front of the work queue | ||
/// to be scheduled next when there are free resources. | ||
Future<V> schedule(J jobId) { | ||
final completer = _cache.putIfAbsent(jobId, () => Completer()); | ||
if (!_started.contains(jobId)) { | ||
final task = _Task(jobId, Zone.current); | ||
_queue.addFirst(task); | ||
scheduleMicrotask(() => _pool.withResource(_processNextTask)); | ||
sigurdm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
return completer.future; | ||
} | ||
} | ||
|
||
class _Task<J> { | ||
final J jobId; | ||
final Zone zone; | ||
_Task(this.jobId, this.zone); | ||
|
||
@override | ||
String toString() => jobId.toString(); | ||
} |
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
Oops, something went wrong.
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.
Why do we need a separate function for this? Can we instead do
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.
It is to handle the case that _runJob throws sync exceptions
Added an explanatory comment