update method
Read, mutate and write inside one hold of the lock.
flush alone cannot close the window this closes: a caller that reads outside the lock and flushes inside it has already taken its snapshot by the time the lock is acquired, so a sibling that flushed in between is missing from its candidate and assurance refuses the write. Holding the lock across the read is what makes the mutation see the state it is about to replace. The lock is a lock file, so this serialises coroutines within one process as well as separate processes — which is the case that bites, since a client fires the namespace-key seeding and the conveyed-key filing as sibling unawaited tasks.
Implementation
@override
Future<void> update(
Atsign atsign, FutureOr<bool> Function(AtKeys keys) mutate) async {
final file = File(filePath!(atsign));
await AtKeysFileLock(file.path).synchronized(() async {
final keys = await read(atsign.toString());
if (await mutate(keys) == false) return;
await _writeValidated(file, atsign, keys, requireExisting: true);
});
}