-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from segmentio/oscb/concurrencyFix
fix: change the concurrency queue locking mechanism, example readme
- Loading branch information
Showing
5 changed files
with
115 additions
and
13 deletions.
There are no files selected for viewing
This file contains 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 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 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 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,55 @@ | ||
import 'dart:async'; | ||
import 'dart:collection'; | ||
|
||
import 'package:flutter/foundation.dart'; | ||
import 'package:uuid/uuid.dart'; | ||
|
||
class Reducer<T> { | ||
final Future<T> Function() operation; | ||
final Completer<T> completer = Completer<T>(); | ||
|
||
Reducer({required this.operation}); | ||
} | ||
|
||
class ConcurrencyQueue<T> { | ||
final List<Reducer<T>> _queue = []; | ||
bool _lock = false; | ||
|
||
bool get _isLocked => (_lock == true); | ||
|
||
bool _tryUnlock(Reducer<T> op) { | ||
// We process and lock right away if the file is unlocked | ||
if (!_isLocked) { | ||
_lock = true; | ||
_process(op); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
Future<T> _process(Reducer<T> op) async { | ||
final result = await op.operation(); | ||
// The actual operation future is resolved here so the caller doesn't await for the full queue | ||
op.completer.complete(result); | ||
_lock = false; | ||
|
||
// When the process completes we trigger the next one right away if there's already a queue | ||
if (_queue.isNotEmpty) { | ||
final nextOp = _queue.first; | ||
if (_tryUnlock(nextOp)) { | ||
_queue.removeAt(0); | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
Future<T> enqueue(Future<T> Function() fun) async { | ||
final op = Reducer<T>(operation: fun); | ||
|
||
if (!_tryUnlock(op)) { | ||
_queue.add(op); | ||
} | ||
|
||
return op.completer.future; | ||
} | ||
} |
This file contains 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,35 @@ | ||
import 'package:analytics/utils/queue.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
|
||
class SimpleState { | ||
String one; | ||
String two; | ||
|
||
SimpleState({required this.one, required this.two}); | ||
} | ||
|
||
void main() { | ||
test('handles multiple operations in a sync queue', () async { | ||
final queue = ConcurrencyQueue<int>(); | ||
var counter = 0; | ||
|
||
final future1 = | ||
queue.enqueue(() => Future.delayed(const Duration(seconds: 2), () { | ||
counter++; | ||
return counter; | ||
})); | ||
|
||
final future2 = | ||
queue.enqueue(() => Future.delayed(const Duration(seconds: 1), () { | ||
counter++; | ||
return counter; | ||
})); | ||
|
||
// Future2 will execute after future1 regardless if we await for it first | ||
final result2 = await future2; | ||
expect(result2, 2); | ||
|
||
final result1 = await future1; | ||
expect(result1, 1); | ||
}); | ||
} |