Skip to content
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

Removing async code from file writes #94

Merged
merged 6 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 22 additions & 19 deletions packages/core/lib/state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -165,23 +165,34 @@ abstract class PersistedState<T> implements AsyncStateNotifier<T> {
if (_persistance != null) {
_hasUpdated = true;
} else {
_persistance = storageJson
_persistance = storageJson
? _store
.setPersisted(_key, toJson(state))
.whenComplete(_whenPersistenceComplete)
.setPersisted(_key, toJson(state))
.whenComplete(_whenPersistenceComplete)
: null;
}
});
_store.ready.then<void>((_) async {
final rawV = await _store.getPersisted(_key);
Map<String, dynamic>? rawV;
try {
rawV = await _store.getPersisted(_key);
} on FormatException catch (e) {
// Addressing https://github.com/segmentio/analytics_flutter/issues/74
// File corruption should be less likely with removal of async code in writes
// Existing corrupted files are cleaned up here without failing initialization
_store.setPersisted(_key, {});
log("Clean file $_key with format error", kind: LogFilterKind.warning);
final wrappedError = ErrorLoadingStorage(e);
errorHandler(wrappedError);
}
T v;

if (rawV == null) {
final init = await _initialiser();
_persistance = storageJson
_persistance = storageJson
? _store
.setPersisted(_key, toJson(init))
.whenComplete(_whenPersistenceComplete)
.setPersisted(_key, toJson(init))
.whenComplete(_whenPersistenceComplete)
: null;
_notifier.nonNullState = init;
v = init;
Expand All @@ -201,16 +212,9 @@ abstract class PersistedState<T> implements AsyncStateNotifier<T> {
return;
}).catchError((e) {
_error = e;
// Clean file if exist a format error
if(_error.toString().contains("FormatException")) {
_store.setPersisted(_key, {});
log("Clean file $_key with format error",
kind: LogFilterKind.warning);
} else {
final wrappedError = ErrorLoadingStorage(e);
errorHandler(wrappedError);
throw wrappedError;
}
final wrappedError = ErrorLoadingStorage(e);
errorHandler(wrappedError);
throw wrappedError;
});
}

Expand Down Expand Up @@ -552,8 +556,7 @@ class Configuration {
this.debug = false,
this.maxBatchSize,
this.storageJson = true,
this.token
});
this.token});
}

typedef ErrorHandler = void Function(Exception);
Expand Down
20 changes: 12 additions & 8 deletions packages/core/lib/utils/store/io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,26 @@ class StoreImpl with Store {
final serialized = json.encode(data);
final buffer = utf8.encode(serialized);

file = await file.lock();
file = await file.setPosition(0);
file = await file.writeFrom(buffer);
file = await file.truncate(buffer.length);
await file.unlock();
file.lockSync(FileLock.blockingExclusive);
file.setPositionSync(0);
file.writeFromSync(buffer);
file.truncateSync(buffer.length);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about security out-loud; I wonder if this leaves the file contents in memory or overwrites them with 0s.

file.unlockSync();
file.closeSync();
}

Future<Map<String, dynamic>?> _readFile(String fileKey) async {
RandomAccessFile? file = await _getFile(fileKey);
if (file == null) {
return null;
}
final length = await file.length();
file = await file.setPosition(0);
file = await file.lock(FileLock.blockingShared);
final length = file.lengthSync();
file.setPositionSync(0);
final buffer = Uint8List(length);
await file.readInto(buffer);
file.readIntoSync(buffer);
file.unlockSync();
file.closeSync();
final contentText = utf8.decode(buffer);
if (contentText == "{}") {
return null; // Prefer null to empty map, because we'll want to initialise a valid empty value.
Expand Down
Loading