Skip to content

Commit b2689ff

Browse files
authored
Use synchronous file writing in BlobStreamReference (#24)
Doing so allows us to avoid dealing in futures all the way back to `encode()`. This becomes important when implementing replay since replay uses `noSuchMethod`, which can't await futures. This also extracts out a few constants in preparation for their use in replay. Part of flutter#11
1 parent 2c08bc1 commit b2689ff

File tree

6 files changed

+97
-103
lines changed

6 files changed

+97
-103
lines changed

lib/src/backends/record_replay/common.dart

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,64 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'events.dart';
6+
57
/// Encoded value of the file system in a recording.
68
const String kFileSystemEncodedValue = '__fs__';
79

810
/// The name of the recording manifest file.
911
const String kManifestName = 'MANIFEST.txt';
1012

13+
/// The key in a serialized [InvocationEvent] map that is used to store the
14+
/// type of invocation.
15+
///
16+
/// See also:
17+
/// - [kGetType]
18+
/// - [kSetType]
19+
/// - [kInvokeType]
20+
const String kManifestTypeKey = 'type';
21+
22+
/// The key in a serialized [InvocationEvent] map that is used to store the
23+
/// target of the invocation.
24+
const String kManifestObjectKey = 'object';
25+
26+
/// The key in a serialized [InvocationEvent] map that is used to store the
27+
/// result (return value) of the invocation.
28+
const String kManifestResultKey = 'result';
29+
30+
/// The key in a serialized [InvocationEvent] map that is used to store the
31+
/// timestamp of the invocation.
32+
const String kManifestTimestampKey = 'timestamp';
33+
34+
/// The key in a serialized [PropertyGetEvent] or [PropertySetEvent] map that
35+
/// is used to store the property that was accessed or mutated.
36+
const String kManifestPropertyKey = 'property';
37+
38+
/// The key in a serialized [PropertySetEvent] map that is used to store the
39+
/// value to which the property was set.
40+
const String kManifestValueKey = 'value';
41+
42+
/// The key in a serialized [MethodEvent] map that is used to store the name of
43+
/// the method that was invoked.
44+
const String kManifestMethodKey = 'method';
45+
46+
/// The key in a serialized [MethodEvent] map that is used to store the
47+
/// positional arguments that were passed to the method.
48+
const String kManifestPositionalArgumentsKey = 'positionalArguments';
49+
50+
/// The key in a serialized [MethodEvent] map that is used to store the
51+
/// named arguments that were passed to the method.
52+
const String kManifestNamedArgumentsKey = 'namedArguments';
53+
54+
/// The serialized [kManifestTypeKey] for property retrievals.
55+
const String kGetType = 'get';
56+
57+
/// The serialized [kManifestTypeKey] for property mutations.
58+
const String kSetType = 'set';
59+
60+
/// The serialized [kManifestTypeKey] for method invocations.
61+
const String kInvokeType = 'invoke';
62+
1163
/// Gets an id guaranteed to be unique on this isolate for objects within this
1264
/// library.
1365
int newUid() => _nextUid++;

lib/src/backends/record_replay/encoding.dart

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:async';
65
import 'dart:convert';
76

87
import 'package:file/file.dart';
@@ -20,7 +19,7 @@ import 'result_reference.dart';
2019

2120
/// Encodes an object into a JSON-ready representation.
2221
///
23-
/// It is legal for an encoder to return a future value.
22+
/// Must return one of {number, boolean, string, null, list, or map}.
2423
typedef dynamic _Encoder(dynamic object);
2524

2625
/// Known encoders. Types not covered here will be encoded using
@@ -35,8 +34,8 @@ const Map<TypeMatcher<dynamic>, _Encoder> _kEncoders =
3534
const TypeMatcher<bool>(): _encodeRaw,
3635
const TypeMatcher<String>(): _encodeRaw,
3736
const TypeMatcher<Null>(): _encodeRaw,
38-
const TypeMatcher<Iterable<dynamic>>(): encodeIterable,
39-
const TypeMatcher<Map<dynamic, dynamic>>(): encodeMap,
37+
const TypeMatcher<Iterable<dynamic>>(): _encodeIterable,
38+
const TypeMatcher<Map<dynamic, dynamic>>(): _encodeMap,
4039
const TypeMatcher<Symbol>(): getSymbolName,
4140
const TypeMatcher<DateTime>(): _encodeDateTime,
4241
const TypeMatcher<Uri>(): _encodeUri,
@@ -59,47 +58,41 @@ const Map<TypeMatcher<dynamic>, _Encoder> _kEncoders =
5958
/// Encodes an arbitrary [object] into a JSON-ready representation (a number,
6059
/// boolean, string, null, list, or map).
6160
///
62-
/// Returns a future that completes with a value suitable for conversion into
63-
/// JSON using [JsonEncoder] without the need for a `toEncodable` argument.
64-
Future<dynamic> encode(dynamic object) async {
61+
/// Returns a value suitable for conversion into JSON using [JsonEncoder]
62+
/// without the need for a `toEncodable` argument.
63+
dynamic encode(dynamic object) {
6564
_Encoder encoder = _encodeDefault;
6665
for (TypeMatcher<dynamic> matcher in _kEncoders.keys) {
6766
if (matcher.matches(object)) {
6867
encoder = _kEncoders[matcher];
6968
break;
7069
}
7170
}
72-
return await encoder(object);
71+
return encoder(object);
7372
}
7473

7574
/// Default encoder (used for types not covered in [_kEncoders]).
7675
String _encodeDefault(dynamic object) => object.runtimeType.toString();
7776

78-
/// Pass-through encoder.
77+
/// Pass-through encoder (used on `num`, `bool`, `String`, and `Null`).
7978
dynamic _encodeRaw(dynamic object) => object;
8079

8180
/// Encodes the specified [iterable] into a JSON-ready list of encoded items.
82-
///
83-
/// Returns a future that completes with a list suitable for conversion into
84-
/// JSON using [JsonEncoder] without the need for a `toEncodable` argument.
85-
Future<List<dynamic>> encodeIterable(Iterable<dynamic> iterable) async {
81+
List<dynamic> _encodeIterable(Iterable<dynamic> iterable) {
8682
List<dynamic> encoded = <dynamic>[];
8783
for (dynamic element in iterable) {
88-
encoded.add(await encode(element));
84+
encoded.add(encode(element));
8985
}
9086
return encoded;
9187
}
9288

9389
/// Encodes the specified [map] into a JSON-ready map of encoded key/value
9490
/// pairs.
95-
///
96-
/// Returns a future that completes with a map suitable for conversion into
97-
/// JSON using [JsonEncoder] without the need for a `toEncodable` argument.
98-
Future<Map<String, dynamic>> encodeMap(Map<dynamic, dynamic> map) async {
91+
Map<String, dynamic> _encodeMap(Map<dynamic, dynamic> map) {
9992
Map<String, dynamic> encoded = <String, dynamic>{};
10093
for (dynamic key in map.keys) {
101-
String encodedKey = await encode(key);
102-
encoded[encodedKey] = await encode(map[key]);
94+
String encodedKey = encode(key);
95+
encoded[encodedKey] = encode(map[key]);
10396
}
10497
return encoded;
10598
}
@@ -115,10 +108,10 @@ Map<String, String> _encodePathContext(p.Context context) {
115108
};
116109
}
117110

118-
Future<dynamic> _encodeResultReference(ResultReference<dynamic> reference) =>
111+
dynamic _encodeResultReference(ResultReference<dynamic> reference) =>
119112
reference.serializedValue;
120113

121-
Future<Map<String, dynamic>> _encodeEvent(LiveInvocationEvent<dynamic> event) =>
114+
Map<String, dynamic> _encodeEvent(LiveInvocationEvent<dynamic> event) =>
122115
event.serialize();
123116

124117
String _encodeFileSystem(FileSystem fs) => kFileSystemEncodedValue;

lib/src/backends/record_replay/events.dart

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,11 @@ abstract class LiveInvocationEvent<T> implements InvocationEvent<T> {
103103
}
104104

105105
/// Returns this event as a JSON-serializable object.
106-
Future<Map<String, dynamic>> serialize() async {
106+
Map<String, dynamic> serialize() {
107107
return <String, dynamic>{
108-
'object': await encode(object),
109-
'result': await encode(_result),
110-
'timestamp': timestamp,
108+
kManifestObjectKey: encode(object),
109+
kManifestResultKey: encode(_result),
110+
kManifestTimestampKey: timestamp,
111111
};
112112
}
113113

@@ -126,11 +126,11 @@ class LivePropertyGetEvent<T> extends LiveInvocationEvent<T>
126126
final Symbol property;
127127

128128
@override
129-
Future<Map<String, dynamic>> serialize() async {
129+
Map<String, dynamic> serialize() {
130130
return <String, dynamic>{
131-
'type': 'get',
132-
'property': getSymbolName(property),
133-
}..addAll(await super.serialize());
131+
kManifestTypeKey: kGetType,
132+
kManifestPropertyKey: getSymbolName(property),
133+
}..addAll(super.serialize());
134134
}
135135
}
136136

@@ -148,12 +148,12 @@ class LivePropertySetEvent<T> extends LiveInvocationEvent<Null>
148148
final T value;
149149

150150
@override
151-
Future<Map<String, dynamic>> serialize() async {
151+
Map<String, dynamic> serialize() {
152152
return <String, dynamic>{
153-
'type': 'set',
154-
'property': getSymbolName(property),
155-
'value': await encode(value),
156-
}..addAll(await super.serialize());
153+
kManifestTypeKey: kSetType,
154+
kManifestPropertyKey: getSymbolName(property),
155+
kManifestValueKey: encode(value),
156+
}..addAll(super.serialize());
157157
}
158158
}
159159

@@ -185,12 +185,12 @@ class LiveMethodEvent<T> extends LiveInvocationEvent<T>
185185
final Map<Symbol, dynamic> namedArguments;
186186

187187
@override
188-
Future<Map<String, dynamic>> serialize() async {
188+
Map<String, dynamic> serialize() {
189189
return <String, dynamic>{
190-
'type': 'invoke',
191-
'method': getSymbolName(method),
192-
'positionalArguments': await encodeIterable(positionalArguments),
193-
'namedArguments': await encodeMap(namedArguments),
194-
}..addAll(await super.serialize());
190+
kManifestTypeKey: kInvokeType,
191+
kManifestMethodKey: getSymbolName(method),
192+
kManifestPositionalArgumentsKey: encode(positionalArguments),
193+
kManifestNamedArgumentsKey: encode(namedArguments),
194+
}..addAll(super.serialize());
195195
}
196196
}

lib/src/backends/record_replay/mutable_recording.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ class MutableRecording implements LiveRecording {
4646
.timeout(awaitPendingResults, onTimeout: () {});
4747
}
4848
Directory dir = destination;
49-
List<dynamic> encodedEvents = await encode(_events);
50-
String json = new JsonEncoder.withIndent(' ').convert(encodedEvents);
49+
String json = new JsonEncoder.withIndent(' ').convert(encode(_events));
5150
String filename = dir.fileSystem.path.join(dir.path, kManifestName);
5251
await dir.fileSystem.file(filename).writeAsString(json, flush: true);
5352
} finally {

lib/src/backends/record_replay/recording_file.dart

Lines changed: 9 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import 'result_reference.dart';
2020
///
2121
/// See also:
2222
/// - [_BlobReference]
23+
/// - [_BlobStreamReference]
2324
typedef void _BlobDataSyncWriter<T>(File file, T data);
2425

2526
/// Callback responsible for asynchronously writing result [data] to the
@@ -29,13 +30,6 @@ typedef void _BlobDataSyncWriter<T>(File file, T data);
2930
/// - [_BlobFutureReference]
3031
typedef Future<Null> _BlobDataAsyncWriter<T>(File file, T data);
3132

32-
/// Callback responsible writing streaming result [data] to the specified
33-
/// [sink].
34-
///
35-
/// See also:
36-
/// - [_BlobStreamReference]
37-
typedef void _BlobDataStreamWriter<T>(IOSink sink, T data);
38-
3933
/// [File] implementation that records all invocation activity to its file
4034
/// system's recording.
4135
class RecordingFile extends RecordingFileSystemEntity<File> implements File {
@@ -93,8 +87,8 @@ class RecordingFile extends RecordingFileSystemEntity<File> implements File {
9387
return new _BlobStreamReference<List<int>>(
9488
file: _newRecordingFile(),
9589
stream: delegate.openRead(start, end),
96-
writer: (IOSink sink, List<int> bytes) {
97-
sink.add(bytes);
90+
writer: (File file, List<int> bytes) {
91+
file.writeAsBytesSync(bytes, mode: FileMode.APPEND, flush: true);
9892
},
9993
);
10094
}
@@ -209,7 +203,7 @@ class _BlobReference<T> extends ResultReference<T> {
209203
T get recordedValue => _value;
210204

211205
@override
212-
Future<String> get serializedValue async => '!${_file.basename}';
206+
String get serializedValue => '!${_file.basename}';
213207
}
214208

215209
/// A [FutureReference] that serializes its value data to a separate file.
@@ -235,64 +229,28 @@ class _BlobFutureReference<T> extends FutureReference<T> {
235229
}
236230

237231
@override
238-
Future<String> get serializedValue async => '!${_file.basename}';
232+
String get serializedValue => '!${_file.basename}';
239233
}
240234

241235
/// A [StreamReference] that serializes its value data to a separate file.
242236
class _BlobStreamReference<T> extends StreamReference<T> {
243237
final File _file;
244-
final _BlobDataStreamWriter<T> _writer;
245-
IOSink _sink;
246-
Future<dynamic> _pendingFlush;
238+
final _BlobDataSyncWriter<T> _writer;
247239

248240
_BlobStreamReference({
249241
@required File file,
250242
@required Stream<T> stream,
251-
@required _BlobDataStreamWriter<T> writer,
243+
@required _BlobDataSyncWriter<T> writer,
252244
})
253245
: _file = file,
254246
_writer = writer,
255-
_sink = file.openWrite(),
256247
super(stream);
257248

258249
@override
259250
void onData(T event) {
260-
if (_pendingFlush == null) {
261-
_writer(_sink, event);
262-
} else {
263-
// It's illegal to write to an IOSink while a flush is pending.
264-
// https://github.com/dart-lang/sdk/issues/28635
265-
_pendingFlush.whenComplete(() {
266-
_writer(_sink, event);
267-
});
268-
}
269-
}
270-
271-
@override
272-
void onDone() {
273-
if (_sink != null) {
274-
_sink.close();
275-
}
276-
}
277-
278-
@override
279-
Future<String> get serializedValue async {
280-
if (_pendingFlush != null) {
281-
await _pendingFlush;
282-
} else {
283-
_pendingFlush = _sink.flush();
284-
try {
285-
await _pendingFlush;
286-
} finally {
287-
_pendingFlush = null;
288-
}
289-
}
290-
291-
return '!${_file.basename}';
251+
_writer(_file, event);
292252
}
293253

294-
// TODO(tvolkert): remove `.then()` once Dart 1.22 is in stable
295254
@override
296-
Future<Null> get complete =>
297-
Future.wait(<Future<dynamic>>[super.complete, _sink.done]).then((_) {});
255+
String get serializedValue => '!${_file.basename}';
298256
}

lib/src/backends/record_replay/result_reference.dart

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ abstract class ResultReference<T> {
4949
/// actually a byte array that was read from a file). In this case, the
5050
/// method can return a `ResultReference` to the list, and it will have a
5151
/// hook into the serialization process.
52-
Future<dynamic> get serializedValue => encode(recordedValue);
52+
dynamic get serializedValue => encode(recordedValue);
5353

5454
/// A [Future] that completes when [value] has completed.
5555
///
@@ -139,7 +139,6 @@ class StreamReference<T> extends ResultReference<Stream<T>> {
139139
_controller.addError(error, stackTrace);
140140
},
141141
onDone: () {
142-
onDone();
143142
_completer.complete();
144143
_controller.close();
145144
},
@@ -153,13 +152,6 @@ class StreamReference<T> extends ResultReference<Stream<T>> {
153152
@protected
154153
void onData(T event) {}
155154

156-
/// Called when the underlying delegate stream fires a "done" event.
157-
///
158-
/// Subclasses may override this method to be notified when the underlying
159-
/// stream is done.
160-
@protected
161-
void onDone() {}
162-
163155
@override
164156
Stream<T> get value => _controller.stream;
165157

0 commit comments

Comments
 (0)