Skip to content

Commit e7e00e4

Browse files
committed
added a start for a checkpointing implementation
1 parent 74bdc26 commit e7e00e4

File tree

6 files changed

+125
-10
lines changed

6 files changed

+125
-10
lines changed

src/data/history/HistoryLog.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ class StateTransition<T extends MutableObject> extends MutationOp {
1717
super(log);
1818

1919
this.mutableHash = mutableHash;
20-
this.start = start;
21-
this.end = end;
20+
this.start = start;
21+
this.end = end;
2222

2323
this.info = info;
2424
}

src/data/model/mutable/MutableObject.ts

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,24 @@ enum MutableContentEvents {
2828

2929
const ContentChangeEventActions: Array<string> = [MutableContentEvents.AddObject, MutableContentEvents.RemoveObject];
3030

31-
type MutableObjectConfig = {supportsUndo?: boolean};
31+
type MutableObjectConfig = {supportsUndo?: boolean, supportsCheckpoints?: boolean, checkpointFreq?: number};
32+
33+
type StateCheckpoint = {
34+
mutableObject: Hash,
35+
terminalOpHashes: Array<Hash>,
36+
allAppliedOps: Array<Hash>,
37+
activeCascInvsPerOp: Array<[Hash, Array<Hash>]>,
38+
exportedState: any
39+
};
3240

3341
abstract class MutableObject extends HashedObject {
3442

3543
static controlLog = new Logger(MutableObject.name, LogLevel.INFO)
3644
static prevOpsComputationLog = new Logger(MutableObject.name, LogLevel.INFO);
3745

3846
readonly _acceptedMutationOpClasses : Array<string>;
39-
47+
readonly _supportsCheckpoints: boolean;
48+
4049
_allAppliedOps : Set<Hash>;
4150
_terminalOps : Map<Hash, MutationOp>;
4251

@@ -68,6 +77,8 @@ abstract class MutableObject extends HashedObject {
6877
acceptedOpClasses.push(CascadedInvalidateOp.className);
6978
}
7079
}
80+
81+
this._supportsCheckpoints = config?.supportsCheckpoints || false;
7182

7283
this._acceptedMutationOpClasses = acceptedOpClasses;
7384
this._boundToStore = false;
@@ -112,6 +123,17 @@ abstract class MutableObject extends HashedObject {
112123
abstract getMutableContents(): MultiMap<Hash, HashedObject>;
113124
abstract getMutableContentByHash(hash: Hash): Set<HashedObject>;
114125

126+
// override the following two to support checkpointing (and pass apropriate params in config to constructor)
127+
128+
exportMutableState(): any {
129+
throw new Error(this.getClassName() + ': this class does not support state packing')
130+
}
131+
132+
importMutableState(state: any) {
133+
state;
134+
throw new Error(this.getClassName() + ': this class does not support applying packed state')
135+
}
136+
115137
/*
116138
// override if appropiate
117139
async undo(op: MutationOp): Promise<boolean> {
@@ -224,6 +246,17 @@ abstract class MutableObject extends HashedObject {
224246

225247
await super.loadAllChanges(batchSize, context);
226248

249+
if (this._supportsCheckpoints) {
250+
const checkpoint = await this.getStore().loadLastCheckpoint(this.getLastHash());
251+
252+
if (checkpoint !== undefined) {
253+
this.restoreCheckpoint(checkpoint);
254+
255+
// TODO: find a way to get the correct "start" parameter for loadByReference below
256+
// to make it ignore all the ops in the checkpoint
257+
}
258+
}
259+
227260
let results = await this.getStore()
228261
.loadByReference(
229262
'targetObject',
@@ -510,6 +543,55 @@ abstract class MutableObject extends HashedObject {
510543
this._unsavedOps.push(op);
511544
}
512545

546+
// checkpointing
547+
548+
async saveCheckpoint() {
549+
550+
await this.saveQueuedOps();
551+
552+
const check: StateCheckpoint = {
553+
mutableObject: this.getLastHash(),
554+
terminalOpHashes: Array.from(this._terminalOps.keys()),
555+
allAppliedOps: Array.from(this._allAppliedOps),
556+
activeCascInvsPerOp: Array.from(this._activeCascInvsPerOp.entries()).map((v: [Hash, Set<Hash>]) => [v[0], Array.from(v[1].values())]),
557+
exportedState: this.exportMutableState()
558+
};
559+
560+
await this.getStore().saveCheckpoint(check);
561+
562+
}
563+
564+
async restoreCheckpoint(checkpoint: StateCheckpoint) {
565+
if (this.getLastHash() !== checkpoint.mutableObject) {
566+
throw new Error('Trying to apply a state checkpoint to ' + this.getLastHash() + ', but the checkpoint is for ' + checkpoint.mutableObject);
567+
}
568+
569+
const terminalOps = new Map<Hash, MutationOp>();
570+
571+
for (const opHash of checkpoint.terminalOpHashes.values()) {
572+
const op = await this.loadOp(opHash);
573+
574+
if (op === undefined) {
575+
throw new Error('Cannot apply checkpoint to ' + this.getLastHash() + ', missing op: ' + opHash);
576+
}
577+
578+
terminalOps.set(opHash, op);
579+
}
580+
581+
this._terminalOps = terminalOps;
582+
this._allAppliedOps = new Set(checkpoint.allAppliedOps.values());
583+
this._activeCascInvsPerOp = new MultiMap();
584+
585+
for (const [k, vs] of checkpoint.activeCascInvsPerOp) {
586+
for (const v of vs) {
587+
this._activeCascInvsPerOp.add(k, v);
588+
}
589+
}
590+
591+
this.importMutableState(checkpoint.exportedState);
592+
}
593+
594+
513595
literalizeInContext(context: Context, path: string, flags?: Array<string>) : Hash {
514596

515597
if (flags === undefined) {
@@ -710,4 +792,5 @@ abstract class MutableObject extends HashedObject {
710792
}
711793

712794
export { MutableObject, MutableContentEvents };
713-
export type { MutableObjectConfig };
795+
export type { MutableObjectConfig };
796+
export type { StateCheckpoint };

src/storage/backends/Backend.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Literal } from 'data/model/literals/LiteralUtils';
22
import { Hash } from 'data/model/hashing/Hashing';
33
import { StoredOpHeader } from '../store/Store';
4+
import { StateCheckpoint } from 'data/model';
45

56
type BackendSearchParams = {order?: 'asc'|'desc'|undefined, start?: string, limit?: number};
67
type BackendSearchResults = {items : Array<Literal>, start?: string, end?: string };
@@ -23,6 +24,9 @@ interface Backend {
2324
store(literal : Literal, history?: StoredOpHeader) : Promise<void>;
2425
load(hash: Hash) : Promise<Storable | undefined>;
2526

27+
storeCheckpoint(checkpoint: StateCheckpoint): Promise<void>;
28+
loadLastCheckpoint(mutableObject: Hash): Promise<StateCheckpoint|undefined>;
29+
2630
loadOpHeader(opHash: Hash) : Promise<StoredOpHeader | undefined>;
2731
loadOpHeaderByHeaderHash(causalHistoryHash: Hash) : Promise<StoredOpHeader | undefined>;
2832

src/storage/backends/IdbBackend.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { openDB, IDBPDatabase } from 'idb';
33

44
import { Logger, LogLevel } from 'util/logging';
55

6-
import { Literal, Hash, HashedSet, HashReference } from 'data/model';
6+
import { Literal, Hash, HashedSet, HashReference, StateCheckpoint } from 'data/model';
77

88
import { Backend, BackendSearchParams, BackendSearchResults, Storable } from './Backend';
99
import { Store, StoredOpHeader } from 'storage/store/Store';
@@ -403,6 +403,17 @@ class IdbBackend implements Backend {
403403
return false;
404404
}
405405
}
406+
407+
// TODO: add a STORE for the checkpoints, for now limited to max idb value size
408+
409+
async storeCheckpoint(checkpoint: StateCheckpoint): Promise<void> {
410+
checkpoint;
411+
throw new Error('Method not implemented.');
412+
}
413+
414+
async loadLastCheckpoint(): Promise<StateCheckpoint|undefined> {
415+
throw new Error('Method not implemented.');
416+
}
406417
}
407418

408419
Store.registerBackend(IdbBackend.backendName, (dbName: string) => new IdbBackend(dbName));

src/storage/backends/MemoryBackend.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Backend, BackendSearchParams, BackendSearchResults, Storable } from './Backend';
2-
import { Literal, Hash, HashReference, HashedSet } from 'data/model';
2+
import { Literal, Hash, HashReference, HashedSet, StateCheckpoint } from 'data/model';
33
import { MultiMap } from 'util/multimap';
44
import { Store, StoredOpHeader } from 'storage/store/Store';
55
import { LiteralUtils } from 'data/model/literals/LiteralUtils';
@@ -24,7 +24,8 @@ type MemoryRepr = {
2424
terminalOps: MultiMap<Hash, Hash>,
2525
lastOps: Map<Hash, Hash>,
2626
opCausalHistories: Map<Hash, MemOpCausalHistoryFormat>,
27-
opCausalHistoriesByHash: Map<Hash, MemOpCausalHistoryFormat>
27+
opCausalHistoriesByHash: Map<Hash, MemOpCausalHistoryFormat>,
28+
checkpoints: Map<Hash, StateCheckpoint>
2829
}
2930

3031
class MemoryBackend implements Backend {
@@ -75,7 +76,8 @@ class MemoryBackend implements Backend {
7576
terminalOps: new MultiMap(),
7677
lastOps: new Map(),
7778
opCausalHistories: new Map(),
78-
opCausalHistoriesByHash: new Map()
79+
opCausalHistoriesByHash: new Map(),
80+
checkpoints: new Map()
7981
}
8082
}
8183

@@ -260,6 +262,14 @@ class MemoryBackend implements Backend {
260262

261263
}
262264

265+
async storeCheckpoint(checkpoint: StateCheckpoint): Promise<void> {
266+
this.repr.checkpoints.set(checkpoint.mutableObject, checkpoint);
267+
}
268+
269+
async loadLastCheckpoint(mutableObject: Hash): Promise<StateCheckpoint|undefined> {
270+
return this.repr.checkpoints.get(mutableObject);
271+
}
272+
263273
async ready(): Promise<void> {
264274

265275
}

src/storage/store/Store.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Backend, BackendSearchParams, BackendSearchResults, Storable } from '../backends/Backend';
2-
import { HashedObject, MutableObject, Literal, Context, HashReference, MutationOp, HashedSet, LiteralUtils } from 'data/model';
2+
import { HashedObject, MutableObject, Literal, Context, HashReference, MutationOp, HashedSet, LiteralUtils, StateCheckpoint } from 'data/model';
33
import { Hash } from 'data/model/hashing/Hashing';
44

55
import { MultiMap } from 'util/multimap';
@@ -921,6 +921,13 @@ class Store {
921921
this.backend.close();
922922
}
923923

924+
async saveCheckpoint(checkpoint: StateCheckpoint) {
925+
await this.backend.storeCheckpoint(checkpoint);
926+
}
927+
928+
async loadLastCheckpoint(mutableObject: Hash) {
929+
return this.backend.loadLastCheckpoint(mutableObject);
930+
}
924931
}
925932

926933
export { Store, StoredOpHeader, LoadResults };

0 commit comments

Comments
 (0)