362 lines
11 KiB
Dart
362 lines
11 KiB
Dart
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:sweepstore/structures.dart';
|
|
|
|
import 'helpers.dart';
|
|
|
|
int roundToNearest16(int value) {
|
|
int rounded = (value + 15) & ~15;
|
|
return rounded;
|
|
}
|
|
|
|
void initialiseSweepstoreHeader(RandomAccessFile file, {
|
|
int concurrentWorkers = 4,
|
|
}) {
|
|
|
|
SweepstoreHeaderWriter header = SweepstoreHeaderWriter(file);
|
|
|
|
if (header.magicNumber == 'SWPT') {
|
|
throw ArgumentError('Sweepstore file is already initialised.');
|
|
}
|
|
|
|
SweepstoreConcurrencyHeaderWriter concurrencyHeader = SweepstoreConcurrencyHeaderWriter(header);
|
|
|
|
header.magicNumber = 'SWPT';
|
|
header.version = "undefined";
|
|
header.addressTablePointer = SweepstorePointer.nullptr;
|
|
header.freeListCount = 0;
|
|
header.isFreeListLifted = false;
|
|
concurrencyHeader.masterIdentifier = 0;
|
|
concurrencyHeader.masterHeartbeat = 0;
|
|
concurrencyHeader.numberOfWorkers = concurrentWorkers;
|
|
concurrencyHeader.isReadAllowed = false;
|
|
|
|
for (int i = 0; i < concurrentWorkers; i++) {
|
|
SweepstoreWorkerTicket ticket = concurrencyHeader[i];
|
|
ticket.write(
|
|
identifier: 0,
|
|
workerHeartbeat: 0,
|
|
ticketState: SweepstoreTicketState.FREE,
|
|
ticketOperation: SweepstoreTicketOperation.NONE,
|
|
keyHash: 0,
|
|
writePointer: SweepstorePointer.nullptr,
|
|
writeSize: 0,
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
class SweepstoreHeader {
|
|
|
|
final RandomAccessFile _file;
|
|
|
|
SweepstoreHeader(this._file);
|
|
|
|
// Offset 0 - 4 bytes
|
|
String get magicNumber {
|
|
_file.setPositionSync(0);
|
|
final bytes = _file.readSync(4);
|
|
return String.fromCharCodes(bytes);
|
|
}
|
|
|
|
// Offset 4 - 12 bytes
|
|
String get version {
|
|
_file.setPositionSync(4);
|
|
String version = utf8.decode(_file.readSync(12)).trim();
|
|
return version;
|
|
}
|
|
|
|
// Offset 16 - 8 bytes
|
|
SweepstorePointer get addressTablePointer {
|
|
_file.setPositionSync(16);
|
|
final address = _file.readIntSync(8);
|
|
return SweepstorePointer(address);
|
|
}
|
|
|
|
// Offset 24 - 4 bytes
|
|
int get freeListCount {
|
|
_file.setPositionSync(24);
|
|
int count = _file.readIntSync(4);
|
|
return count;
|
|
}
|
|
|
|
// Offset 28 - 1 byte
|
|
bool get isFreeListLifted {
|
|
_file.setPositionSync(28);
|
|
int flag = _file.readIntSync(1);
|
|
return flag != 0;
|
|
}
|
|
|
|
SweepstoreConcurrencyHeader get concurrency => SweepstoreConcurrencyHeader(this);
|
|
}
|
|
|
|
class SweepstoreHeaderWriter extends SweepstoreHeader {
|
|
|
|
SweepstoreHeaderWriter(RandomAccessFile file) : super(file);
|
|
|
|
// Offset 0 - 4 bytes
|
|
void set magicNumber(String value) {
|
|
if (value.length != 4) {
|
|
throw ArgumentError('Magic number must be exactly 4 characters long');
|
|
}
|
|
_file.setPositionSync(0);
|
|
_file.writeFromSync(value.codeUnits);
|
|
}
|
|
|
|
// Offset 4 - 12 bytes
|
|
void set version(String value) {
|
|
if (value.length > 11) {
|
|
throw ArgumentError('Version string must be at most 11 characters long');
|
|
}
|
|
_file.setPositionSync(4);
|
|
_file.writeFromSync(utf8.encode(" " + value.padRight(11, ' ').substring(0, 11)));
|
|
}
|
|
|
|
// Offset 16 - 8 bytes
|
|
void set addressTablePointer(SweepstorePointer pointer) {
|
|
_file.setPositionSync(16);
|
|
_file.writeIntSync(pointer.address, 8);
|
|
}
|
|
|
|
// Offset 24 - 4 bytes
|
|
void set freeListCount(int value) {
|
|
_file.setPositionSync(24);
|
|
_file.writeIntSync(value, 4);
|
|
}
|
|
|
|
// Offset 28 - 1 byte
|
|
void set isFreeListLifted(bool lifted) {
|
|
_file.setPositionSync(28);
|
|
_file.writeIntSync(lifted ? 1 : 0, 1);
|
|
}
|
|
}
|
|
|
|
class SweepstoreConcurrencyHeader {
|
|
|
|
final SweepstoreHeader _header;
|
|
|
|
SweepstoreConcurrencyHeader(this._header);
|
|
|
|
// Offset 29 - 8 bytes
|
|
int get masterIdentifier {
|
|
_header._file.setPositionSync(29);
|
|
int id = _header._file.readIntSync(8);
|
|
return id;
|
|
}
|
|
|
|
// Offset 37 - 4 bytes
|
|
int get masterHeartbeat {
|
|
_header._file.setPositionSync(37);
|
|
int heartbeat = _header._file.readIntSync(4);
|
|
return heartbeat;
|
|
}
|
|
|
|
// Offset 41 - 4 bytes
|
|
int get numberOfWorkers {
|
|
_header._file.setPositionSync(41);
|
|
int numWorkers = _header._file.readIntSync(4);
|
|
return numWorkers;
|
|
}
|
|
|
|
// Offset 45 - 1 byte
|
|
bool get isReadAllowed {
|
|
_header._file.setPositionSync(45);
|
|
int flag = _header._file.readIntSync(1);
|
|
return flag != 0;
|
|
}
|
|
|
|
SweepstoreWorkerTicket operator [](int ticketIndex) {
|
|
if (ticketIndex < 0 || ticketIndex >= numberOfWorkers) {
|
|
throw RangeError.index(ticketIndex, this, 'ticketIndex', null, numberOfWorkers);
|
|
}
|
|
return SweepstoreWorkerTicket(ticketIndex, this);
|
|
}
|
|
|
|
}
|
|
|
|
class SweepstoreConcurrencyHeaderWriter extends SweepstoreConcurrencyHeader {
|
|
|
|
SweepstoreConcurrencyHeaderWriter(SweepstoreHeader header) : super(header);
|
|
|
|
// Offset 29 - 8 bytes
|
|
void set masterIdentifier(int id) {
|
|
_header._file.setPositionSync(29);
|
|
_header._file.writeIntSync(id, 8);
|
|
}
|
|
|
|
// Offset 37 - 4 bytes
|
|
void set masterHeartbeat(int heartbeat) {
|
|
_header._file.setPositionSync(37);
|
|
_header._file.writeIntSync(heartbeat, 4);
|
|
}
|
|
|
|
// Offset 41 - 4 bytes
|
|
void set numberOfWorkers(int numWorkers) {
|
|
_header._file.setPositionSync(41);
|
|
_header._file.writeIntSync(numWorkers, 4);
|
|
}
|
|
|
|
// Offset 45 - 1 byte
|
|
void set isReadAllowed(bool allowed) {
|
|
_header._file.setPositionSync(45);
|
|
_header._file.writeIntSync(allowed ? 1 : 0, 1);
|
|
}
|
|
|
|
}
|
|
|
|
final int endOfStaticHeaderOffset = roundToNearest16(46);
|
|
|
|
class SweepstoreWorkerTicket {
|
|
|
|
static final int ticketSize = roundToNearest16(29);
|
|
final SweepstoreConcurrencyHeader _concurrencyHeader;
|
|
final int ticketIndex;
|
|
|
|
SweepstoreWorkerTicket(this.ticketIndex, this._concurrencyHeader);
|
|
|
|
// All offsets are relative to the start of the workers ticket
|
|
int get _baseOffset => endOfStaticHeaderOffset + (ticketIndex * ticketSize);
|
|
|
|
// Offset 0 - 4 bytes
|
|
int get identifier {
|
|
_concurrencyHeader._header._file.lockSync(FileLock.blockingShared, _baseOffset, _baseOffset + 4);
|
|
_concurrencyHeader._header._file.setPositionSync(_baseOffset);
|
|
int id = _concurrencyHeader._header._file.readIntSync(4);
|
|
_concurrencyHeader._header._file.unlockSync(_baseOffset, _baseOffset + 4);
|
|
return id;
|
|
}
|
|
|
|
// Offset 4 - 4 bytes
|
|
int get workerHeartbeat {
|
|
_concurrencyHeader._header._file.lockSync(FileLock.blockingShared, _baseOffset + 4, _baseOffset + 8);
|
|
_concurrencyHeader._header._file.setPositionSync(_baseOffset + 4);
|
|
int heartbeat = _concurrencyHeader._header._file.readIntSync(4);
|
|
_concurrencyHeader._header._file.unlockSync(_baseOffset + 4, _baseOffset + 8);
|
|
return heartbeat;
|
|
}
|
|
|
|
// Offset 8 - 1 byte
|
|
SweepstoreTicketState get ticketState {
|
|
_concurrencyHeader._header._file.lockSync(FileLock.blockingShared, _baseOffset + 8, _baseOffset + 9);
|
|
_concurrencyHeader._header._file.setPositionSync(_baseOffset + 8);
|
|
int stateValue = _concurrencyHeader._header._file.readIntSync(1);
|
|
_concurrencyHeader._header._file.unlockSync(_baseOffset + 8, _baseOffset + 9);
|
|
return SweepstoreTicketState.values[stateValue];
|
|
}
|
|
|
|
// Offset 9 - 1 byte
|
|
SweepstoreTicketOperation get ticketOperation {
|
|
_concurrencyHeader._header._file.lockSync(FileLock.blockingShared, _baseOffset + 9, _baseOffset + 10);
|
|
_concurrencyHeader._header._file.setPositionSync(_baseOffset + 9);
|
|
int operationValue = _concurrencyHeader._header._file.readIntSync(1);
|
|
_concurrencyHeader._header._file.unlockSync(_baseOffset + 9, _baseOffset + 10);
|
|
return SweepstoreTicketOperation.values[operationValue];
|
|
}
|
|
|
|
// Offset 10 - 8 bytes
|
|
int get keyHash {
|
|
_concurrencyHeader._header._file.lockSync(FileLock.blockingShared, _baseOffset + 10, _baseOffset + 18);
|
|
_concurrencyHeader._header._file.setPositionSync(_baseOffset + 10);
|
|
int hash = _concurrencyHeader._header._file.readIntSync(8);
|
|
_concurrencyHeader._header._file.unlockSync(_baseOffset + 10, _baseOffset + 18);
|
|
return hash;
|
|
}
|
|
|
|
// Offset 18 - 8 bytes
|
|
SweepstorePointer get writePointer {
|
|
_concurrencyHeader._header._file.lockSync(FileLock.blockingShared, _baseOffset + 18, _baseOffset + 26);
|
|
_concurrencyHeader._header._file.setPositionSync(_baseOffset + 18);
|
|
int address = _concurrencyHeader._header._file.readIntSync(8);
|
|
_concurrencyHeader._header._file.unlockSync(_baseOffset + 18, _baseOffset + 26);
|
|
return SweepstorePointer(address);
|
|
}
|
|
|
|
// Offset 26 - 4 bytes
|
|
int get writeSize {
|
|
_concurrencyHeader._header._file.lockSync(FileLock.blockingShared, _baseOffset + 26, _baseOffset + 30);
|
|
_concurrencyHeader._header._file.setPositionSync(_baseOffset + 26);
|
|
int size = _concurrencyHeader._header._file.readIntSync(4);
|
|
_concurrencyHeader._header._file.unlockSync(_baseOffset + 26, _baseOffset + 30);
|
|
return size;
|
|
}
|
|
|
|
// Writer
|
|
void write({
|
|
int? identifier,
|
|
int? workerHeartbeat,
|
|
SweepstoreTicketState? ticketState,
|
|
SweepstoreTicketOperation? ticketOperation,
|
|
int? keyHash,
|
|
SweepstorePointer? writePointer,
|
|
int? writeSize,
|
|
}) {
|
|
|
|
try {
|
|
|
|
_concurrencyHeader._header._file.lockSync(FileLock.blockingExclusive, _baseOffset, _baseOffset + ticketSize);
|
|
|
|
_concurrencyHeader._header._file.setPositionSync(_baseOffset);
|
|
List<int> existingBuffer = _concurrencyHeader._header._file.readSync(ticketSize);
|
|
RandomAccessMemory buffer = RandomAccessMemory(existingBuffer);
|
|
|
|
if (identifier != null) {
|
|
buffer.setPositionSync(0);
|
|
buffer.writeIntSync(identifier, 4);
|
|
}
|
|
if (workerHeartbeat != null) {
|
|
buffer.setPositionSync(4);
|
|
buffer.writeIntSync(workerHeartbeat, 4);
|
|
}
|
|
if (ticketState != null) {
|
|
buffer.setPositionSync(8);
|
|
buffer.writeIntSync(ticketState.index, 1);
|
|
}
|
|
if (ticketOperation != null) {
|
|
buffer.setPositionSync(9);
|
|
buffer.writeIntSync(ticketOperation.index, 1);
|
|
}
|
|
if (keyHash != null) {
|
|
buffer.setPositionSync(10);
|
|
buffer.writeIntSync(keyHash, 8);
|
|
}
|
|
if (writePointer != null) {
|
|
buffer.setPositionSync(18);
|
|
buffer.writeIntSync(writePointer.address, 8);
|
|
}
|
|
if (writeSize != null) {
|
|
buffer.setPositionSync(26);
|
|
buffer.writeIntSync(writeSize, 4);
|
|
}
|
|
|
|
// Pad the rest of the ticket with zeros if necessary
|
|
buffer.setPositionSync(30);
|
|
while (buffer.positionSync() < ticketSize) {
|
|
buffer.writeIntSync(0, 1);
|
|
}
|
|
|
|
_concurrencyHeader._header._file.setPositionSync(_baseOffset);
|
|
_concurrencyHeader._header._file.writeFromSync(buffer.toUint8List());
|
|
_concurrencyHeader._header._file.flushSync();
|
|
|
|
} finally {
|
|
_concurrencyHeader._header._file.unlockSync(_baseOffset, _baseOffset + ticketSize);
|
|
}
|
|
}
|
|
|
|
bool writable() {
|
|
try {
|
|
_concurrencyHeader._header._file.lockSync(
|
|
FileLock.blockingExclusive,
|
|
_baseOffset,
|
|
_baseOffset + ticketSize
|
|
);
|
|
// Successfully locked - immediately unlock and return true
|
|
_concurrencyHeader._header._file.unlockSync(_baseOffset, _baseOffset + ticketSize);
|
|
return true;
|
|
} catch (e) {
|
|
// Lock failed - already held by another process
|
|
return false;
|
|
}
|
|
}
|
|
} |