-
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 12 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,159 @@ | ||
// 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:async/async.dart"; | ||
|
||
/// Handles rate-limited scheduling of tasks. | ||
/// | ||
/// Tasks are named with a key of type [K] (should be useful as a Hash-key) and | ||
/// run with a supplied function producing a CancelableOperation. | ||
/// | ||
/// Designed to allow prefetching of tasks that will likely be needed | ||
/// later with [prefetch]. | ||
/// | ||
/// All current operations can be cancelled and future operations removed from | ||
/// the queue with [stop]. | ||
/// | ||
/// Errors thrown by tasks scheduled with [prefetch] will only be triggered when | ||
/// you await the Future returned by [fetch]. | ||
/// | ||
/// The operation will run in the [Zone] that the task was in when enqueued. | ||
/// If a task if [prefetch]ed and later [fetch]ed before the operation is | ||
/// started, the task will go in front of the queue with the zone of the [fetch] | ||
/// operation. | ||
/// | ||
/// Example: | ||
/// | ||
/// ```dart | ||
/// // A retriever that, given a uri, gets that page and returns the body | ||
/// final retriever = Retriever( | ||
/// (Uri uri, _) => return CancelableOperation.fromFuture(http.read(uri))); | ||
/// // Start fetching `pub.dev` in the background. | ||
/// retriever.prefetch(Uri.parse('https://pub.dev/')); | ||
/// // ... do time-consuming task. | ||
/// | ||
/// // Now we actually need `pub.dev`. | ||
/// final pubDevBody = await retriever.fetch(Uri.parse('https://pub.dev/')); | ||
/// ``` | ||
class Retriever<K, V> { | ||
final CancelableOperation<V> Function(K, Retriever) _run; | ||
sigurdm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// The results of ongoing and finished computations. | ||
final Map<K, Completer<V>> _cache = <K, Completer<V>>{}; | ||
|
||
/// Operations that are waiting to run. | ||
final Queue<_TaskWithZone<K>> _queue = Queue<_TaskWithZone<K>>(); | ||
|
||
/// Rate limits the downloads. | ||
final Pool _pool; | ||
|
||
/// The currently active operations. | ||
final Map<K, CancelableOperation<V>> _active = <K, CancelableOperation<V>>{}; | ||
|
||
/// True when the processing loop is running. | ||
bool _started = false; | ||
sigurdm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Retriever(CancelableOperation<V> Function(K, Retriever) run, | ||
{maxConcurrentOperations = 10}) | ||
: _run = run, | ||
_pool = Pool(maxConcurrentOperations); | ||
|
||
Retriever.nonCancelable(Future<V> Function(K, Retriever) run, | ||
{maxConcurrentOperations = 10}) | ||
: this( | ||
(key, retriever) => | ||
CancelableOperation.fromFuture(run(key, retriever)), | ||
maxConcurrentOperations: maxConcurrentOperations); | ||
|
||
/// Starts running operations from the queue. Taking the first items first. | ||
void _process() async { | ||
assert(!_started); | ||
_started = true; | ||
while (_queue.isNotEmpty) { | ||
final resource = await _pool.request(); | ||
// This checks if [stop] has been called while waiting for a resource. | ||
jonasfj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!_started) { | ||
resource.release(); | ||
break; | ||
} | ||
// Take the highest priority task from the queue. | ||
final taskWithZone = _queue.removeFirst(); | ||
jonasfj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
final task = taskWithZone.task; | ||
// Create or get the completer to deliver the result to. | ||
final completer = _cache.putIfAbsent( | ||
task, | ||
() => Completer() | ||
// Listen to errors: this will make errors thrown by [_get] not | ||
// become uncaught. | ||
// They will still show up for other listeners of the future. | ||
..future.catchError((error) {})); | ||
// Already done or already scheduled => do nothing. | ||
if (completer.isCompleted || _active.containsKey(task)) { | ||
resource.release(); | ||
continue; | ||
} | ||
|
||
// Start running the operation for [task] in the original [Zone]. | ||
final zone = taskWithZone.zone; | ||
final operation = zone.runBinary(_run, task, this); | ||
_active[task] = operation; | ||
operation | ||
.then(completer.complete, onError: completer.completeError) | ||
.value | ||
.whenComplete(() { | ||
resource.release(); | ||
_active.remove(task); | ||
}); | ||
} | ||
_started = false; | ||
} | ||
|
||
/// Cancels all active computations, and clears the queue. | ||
void stop() { | ||
// Stop the processing loop. | ||
_started = false; | ||
// Cancel all active operations. | ||
for (final operation in _active.values) { | ||
operation.cancel(); | ||
} | ||
// Do not process the rest of the queue. | ||
_queue.clear(); | ||
} | ||
|
||
/// Puts [task] in the back of the work queue. | ||
/// | ||
/// Task will be processed when there are free resources, and other already | ||
/// queued tasks are done. | ||
void prefetch(K task) { | ||
_queue.addLast(_TaskWithZone.current(task)); | ||
if (!_started) _process(); | ||
} | ||
|
||
/// Returns the result of running [task]. | ||
/// | ||
/// If [task] is already done, the cached result will be returned. | ||
/// If [task] is not yet active, it will go to the front of the work queue | ||
/// to be scheduled next when there are free resources. | ||
Future<V> fetch(K task) { | ||
final completer = _cache.putIfAbsent(task, () => Completer()); | ||
if (!completer.isCompleted) { | ||
// We allow adding the same task twice to the queue. | ||
// It will get dedupped by the [_process] loop. | ||
_queue.addFirst(_TaskWithZone.current(task)); | ||
if (!_started) _process(); | ||
} | ||
return completer.future; | ||
} | ||
} | ||
|
||
class _TaskWithZone<K> { | ||
final K task; | ||
final Zone zone; | ||
_TaskWithZone(this.task, this.zone); | ||
_TaskWithZone.current(K task) : this(task, Zone.current); | ||
} |
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
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.
Uh oh!
There was an error while loading. Please reload this page.