Skip to content

Commit 1cb2b25

Browse files
committed
Add spec annotations for tombstones and OBJECT_DELETE op for Objects
Spec IDs from [1]. [1] ably/specification#350
1 parent db00eea commit 1cb2b25

File tree

6 files changed

+53
-37
lines changed

6 files changed

+53
-37
lines changed

src/plugins/objects/defaults.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const DEFAULTS = {
2-
gcInterval: 1000 * 60 * 5, // 5 minutes
2+
gcInterval: 1000 * 60 * 5, // RTO10a, 5 minutes
33
/**
44
* The SDK will attempt to use the `objectsGCGracePeriod` value provided by the server in the `connectionDetails` object of the `CONNECTED` event.
55
* If the server does not provide this value, the SDK will fall back to this default value.
@@ -9,5 +9,5 @@ export const DEFAULTS = {
99
*
1010
* Applies both for map entries tombstones and object tombstones.
1111
*/
12-
gcGracePeriod: 1000 * 60 * 60 * 24, // 24 hours
12+
gcGracePeriod: 1000 * 60 * 60 * 24, // RTO10b3, 24 hours
1313
};

src/plugins/objects/livecounter.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
196196
this._siteTimeserials[opSiteCode] = opSerial; // RTLC7c
197197

198198
if (this.isTombstoned()) {
199-
// this object is tombstoned so the operation cannot be applied
199+
// RTLC7e - this object is tombstoned so the operation cannot be applied
200200
return;
201201
}
202202

@@ -218,7 +218,7 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
218218
break;
219219

220220
case ObjectOperationAction.OBJECT_DELETE:
221-
update = this._applyObjectDelete(msg);
221+
update = this._applyObjectDelete(msg); // RTLC7d4, RTLC7d4a
222222
break;
223223

224224
default:
@@ -275,14 +275,14 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
275275
this._siteTimeserials = objectState.siteTimeserials ?? {}; // RTLC6a
276276

277277
if (this.isTombstoned()) {
278-
// this object is tombstoned. this is a terminal state which can't be overridden. skip the rest of object state message processing
279-
return { noop: true };
278+
// RTLC6e - this object is tombstoned. this is a terminal state which can't be overridden. skip the rest of object state message processing
279+
return { noop: true }; // RTLC6e1
280280
}
281281

282282
const previousDataRef = this._dataRef;
283283
if (objectState.tombstone) {
284284
// tombstone this object and ignore the data from the object state message
285-
this.tombstone(objectMessage);
285+
this.tombstone(objectMessage); // RTLC6f
286286
} else {
287287
// override data for this object with data from the object state
288288
this._createOperationIsMerged = false; // RTLC6b
@@ -294,7 +294,7 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
294294

295295
// if object got tombstoned, the update object will include all data that got cleared.
296296
// otherwise it is a diff between previous value and new value from object state.
297-
return this._updateFromDataDiff(previousDataRef, this._dataRef);
297+
return this._updateFromDataDiff(previousDataRef, this._dataRef); // RTLC6f1
298298
}
299299

300300
/**

src/plugins/objects/livemap.ts

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export type LiveMapObjectData = ObjectIdObjectData | ValueObjectData;
4040

4141
export interface LiveMapEntry {
4242
tombstone: boolean;
43-
tombstonedAt: number | undefined;
43+
tombstonedAt: number | undefined; // RTLM3a1
4444
timeserial: string | undefined;
4545
data: LiveMapObjectData | undefined;
4646
}
@@ -300,6 +300,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
300300
get<TKey extends keyof T & string>(key: TKey): T[TKey] | undefined {
301301
this._objects.throwIfInvalidAccessApiConfiguration(); // RTLM5b, RTLM5c
302302

303+
// RTLM5e
303304
if (this.isTombstoned()) {
304305
return undefined as T[TKey];
305306
}
@@ -429,7 +430,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
429430
this._siteTimeserials[opSiteCode] = opSerial; // RTLM15c
430431

431432
if (this.isTombstoned()) {
432-
// this object is tombstoned so the operation cannot be applied
433+
// RTLM15e - this object is tombstoned so the operation cannot be applied
433434
return;
434435
}
435436

@@ -461,7 +462,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
461462
break;
462463

463464
case ObjectOperationAction.OBJECT_DELETE:
464-
update = this._applyObjectDelete(msg);
465+
update = this._applyObjectDelete(msg); // RTLM15d5, RTLM15d5a
465466
break;
466467

467468
default:
@@ -534,14 +535,14 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
534535
this._siteTimeserials = objectState.siteTimeserials ?? {}; // RTLM6a
535536

536537
if (this.isTombstoned()) {
537-
// this object is tombstoned. this is a terminal state which can't be overridden. skip the rest of object state message processing
538-
return { noop: true };
538+
// RTLM6e - this object is tombstoned. this is a terminal state which can't be overridden. skip the rest of object state message processing
539+
return { noop: true }; // RTLM6e1
539540
}
540541

541542
const previousDataRef = this._dataRef;
542543
if (objectState.tombstone) {
543544
// tombstone this object and ignore the data from the object state message
544-
this.tombstone(objectMessage);
545+
this.tombstone(objectMessage); // RTLM6f
545546
} else {
546547
// override data for this object with data from the object state
547548
this._createOperationIsMerged = false; // RTLM6b
@@ -553,19 +554,21 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
553554

554555
// if object got tombstoned, the update object will include all data that got cleared.
555556
// otherwise it is a diff between previous value and new value from object state.
556-
return this._updateFromDataDiff(previousDataRef, this._dataRef);
557+
return this._updateFromDataDiff(previousDataRef, this._dataRef); // RTLM6f1
557558
}
558559

559560
/**
560561
* @internal
562+
* @spec RTLM19
561563
*/
562564
onGCInterval(): void {
563565
// should remove any tombstoned entries from the underlying map data that have exceeded the GC grace period
564566

567+
// RTLM19a
565568
const keysToDelete: string[] = [];
566569
for (const [key, value] of this._dataRef.data.entries()) {
567570
if (value.tombstone === true && Date.now() - value.tombstonedAt! >= this._objects.gcGracePeriod) {
568-
keysToDelete.push(key);
571+
keysToDelete.push(key); // RTLM19a1
569572
}
570573
}
571574

@@ -764,14 +767,14 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
764767
if (existingEntry) {
765768
// RTLM7a2
766769
existingEntry.tombstone = false; // RTLM7a2c
767-
existingEntry.tombstonedAt = undefined;
770+
existingEntry.tombstonedAt = undefined; // RTLM7a2d
768771
existingEntry.timeserial = opSerial; // RTLM7a2b
769772
existingEntry.data = liveData; // RTLM7a2a
770773
} else {
771774
// RTLM7b, RTLM7b1
772775
const newEntry: LiveMapEntry = {
773776
tombstone: false, // RTLM7b2
774-
tombstonedAt: undefined,
777+
tombstonedAt: undefined, // RTLM7b3
775778
timeserial: opSerial,
776779
data: liveData,
777780
};
@@ -789,7 +792,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
789792
private _applyMapRemove(
790793
op: ObjectsMapOp<ObjectData>, // RTLM8c1
791794
opSerial: string | undefined, // RTLM8c2
792-
opTimestamp: number | undefined,
795+
opTimestamp: number | undefined, // RTLM8c3
793796
): LiveMapUpdate<T> | LiveObjectUpdateNoop {
794797
const existingEntry = this._dataRef.data.get(op.key);
795798
// RTLM8a
@@ -804,30 +807,33 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
804807
return { noop: true };
805808
}
806809

810+
// RTLM8f
807811
let tombstonedAt: number;
808812
if (opTimestamp != null) {
809-
tombstonedAt = opTimestamp;
813+
tombstonedAt = opTimestamp; // RTLM8f1
810814
} else {
815+
// RTLM8f2a
811816
this._client.Logger.logAction(
812817
this._client.logger,
813818
this._client.Logger.LOG_MINOR,
814819
'LiveMap._applyMapRemove()',
815820
`map key has been removed but no "serialTimestamp" found in the message, using local clock instead; key="${op.key}", objectId=${this.getObjectId()}`,
816821
);
822+
// RTLM8f2
817823
tombstonedAt = Date.now(); // best-effort estimate since no timestamp provided by the server
818824
}
819825

820826
if (existingEntry) {
821827
// RTLM8a2
822828
existingEntry.tombstone = true; // RTLM8a2c
823-
existingEntry.tombstonedAt = tombstonedAt;
829+
existingEntry.tombstonedAt = tombstonedAt; // RTLM8a2d
824830
existingEntry.timeserial = opSerial; // RTLM8a2b
825831
existingEntry.data = undefined; // RTLM8a2a
826832
} else {
827833
// RTLM8b, RTLM8b1
828834
const newEntry: LiveMapEntry = {
829835
tombstone: true, // RTLM8b2
830-
tombstonedAt: tombstonedAt,
836+
tombstonedAt: tombstonedAt, // RTLM8b3
831837
timeserial: opSerial,
832838
data: undefined,
833839
};
@@ -896,14 +902,16 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
896902
let tombstonedAt: number | undefined;
897903
if (entry.tombstone === true) {
898904
if (entry.serialTimestamp != null) {
899-
tombstonedAt = entry.serialTimestamp;
905+
tombstonedAt = entry.serialTimestamp; // RTLM6c1a
900906
} else {
907+
// RTLM6c1b1
901908
this._client.Logger.logAction(
902909
this._client.logger,
903910
this._client.Logger.LOG_MINOR,
904911
'LiveMap._liveMapDataFromMapEntries()',
905912
`map key is removed but no "serialTimestamp" found, using local clock instead; key="${key}", objectId=${this.getObjectId()}`,
906913
);
914+
// RTLM6c1b
907915
tombstonedAt = Date.now(); // best-effort estimate since no timestamp provided by the server
908916
}
909917
}
@@ -913,7 +921,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
913921
data: liveData,
914922
// consider object as tombstoned only if we received an explicit flag stating that. otherwise it exists
915923
tombstone: entry.tombstone === true,
916-
tombstonedAt,
924+
tombstonedAt, // RTLM6c1
917925
};
918926

919927
liveMapData.data.set(key, liveDataEntry);
@@ -950,7 +958,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
950958
}
951959

952960
if (refObject.isTombstoned()) {
953-
// tombstoned objects must not be surfaced to the end users
961+
// RTLM5d2f3 - tombstoned objects must not be surfaced to the end users
954962
return undefined;
955963
}
956964

@@ -973,7 +981,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
973981

974982
if (refObject?.isTombstoned()) {
975983
// entry that points to tombstoned object should be considered tombstoned as well
976-
return true;
984+
return true; // RTLM14c
977985
}
978986
}
979987

src/plugins/objects/liveobject.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ export abstract class LiveObject<
5151
protected _dataRef: TData;
5252
protected _siteTimeserials: Record<string, string>; // RTLO3b
5353
protected _createOperationIsMerged: boolean; // RTLO3c
54-
private _tombstone: boolean;
55-
private _tombstonedAt: number | undefined;
54+
private _tombstone: boolean; // RTLO3d
55+
private _tombstonedAt: number | undefined; // RTLO3e, RTLO3e1
5656

5757
protected constructor(
5858
protected _objects: Objects,
@@ -66,7 +66,7 @@ export abstract class LiveObject<
6666
// use empty map of serials by default, so any future operation can be applied to this object
6767
this._siteTimeserials = {}; // RTLO3b1
6868
this._createOperationIsMerged = false; // RTLO3c1
69-
this._tombstone = false;
69+
this._tombstone = false; // RTLO3d1
7070
}
7171

7272
subscribe(listener: (update: TUpdate) => void): SubscribeResponse {
@@ -151,21 +151,25 @@ export abstract class LiveObject<
151151
* Clears the object's data, cancels any buffered operations and sets the tombstone flag to `true`.
152152
*
153153
* @internal
154+
* @spec RTLO4e
154155
*/
155156
tombstone(objectMessage: ObjectMessage): TUpdate {
156-
this._tombstone = true;
157+
this._tombstone = true; // RTLO4e2
158+
// RTLO4e3
157159
if (objectMessage.serialTimestamp != null) {
158-
this._tombstonedAt = objectMessage.serialTimestamp;
160+
this._tombstonedAt = objectMessage.serialTimestamp; // RTLO4e3a
159161
} else {
162+
// RTLO4e3b1
160163
this._client.Logger.logAction(
161164
this._client.logger,
162165
this._client.Logger.LOG_MINOR,
163166
'LiveObject.tombstone()',
164167
`object has been tombstoned but no "serialTimestamp" found in the message, using local clock instead; objectId=${this.getObjectId()}`,
165168
);
169+
// RTLO4e3b
166170
this._tombstonedAt = Date.now(); // best-effort estimate since no timestamp provided by the server
167171
}
168-
const update = this.clearData();
172+
const update = this.clearData(); // RTLO4e4
169173
this._lifecycleEvents.emit(LiveObjectLifecycleEvent.deleted);
170174

171175
return update;
@@ -215,8 +219,9 @@ export abstract class LiveObject<
215219
return !siteSerial || opSerial > siteSerial; // RTLO4a5, RTLO4a6
216220
}
217221

222+
/** @spec RTLO5 */
218223
protected _applyObjectDelete(objectMessage: ObjectMessage): TUpdate {
219-
return this.tombstone(objectMessage);
224+
return this.tombstone(objectMessage); // RTLO5b
220225
}
221226

222227
/**

src/plugins/objects/objects.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ export class Objects {
6767
this._bufferedObjectOperations = []; // RTO7a1
6868
// use server-provided objectsGCGracePeriod if available, and subscribe to new connectionDetails that can be emitted as part of the RTN24
6969
this.gcGracePeriod =
70-
this._channel.connectionManager.connectionDetails?.objectsGCGracePeriod ?? DEFAULTS.gcGracePeriod;
70+
this._channel.connectionManager.connectionDetails?.objectsGCGracePeriod ?? DEFAULTS.gcGracePeriod; // RTO10b1
7171
this._channel.connectionManager.on('connectiondetails', (details: Record<string, any>) => {
72-
this.gcGracePeriod = details.objectsGCGracePeriod ?? DEFAULTS.gcGracePeriod;
72+
this.gcGracePeriod = details.objectsGCGracePeriod ?? DEFAULTS.gcGracePeriod; // RTO10b2
7373
});
7474
}
7575

src/plugins/objects/objectspool.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class ObjectsPool {
2121
this._client = this._objects.getClient();
2222
this._pool = this._createInitialPool();
2323
this._gcInterval = setInterval(() => {
24+
// RTO10
2425
this._onGCInterval();
2526
}, DEFAULTS.gcInterval);
2627
// call nodejs's Timeout.unref to not require Node.js event loop to remain active due to this interval. see https://nodejs.org/api/timers.html#timeoutunref
@@ -103,18 +104,20 @@ export class ObjectsPool {
103104
return pool;
104105
}
105106

107+
/** @spec RTO10c */
106108
private _onGCInterval(): void {
107109
const toDelete: string[] = [];
108110
for (const [objectId, obj] of this._pool.entries()) {
111+
// RTO10c1
109112
// tombstoned objects should be removed from the pool if they have been tombstoned for longer than grace period.
110113
// by removing them from the local pool, Objects plugin no longer keeps a reference to those objects, allowing JS's
111114
// Garbage Collection to eventually free the memory for those objects, provided the user no longer references them either.
112115
if (obj.isTombstoned() && Date.now() - obj.tombstonedAt()! >= this._objects.gcGracePeriod) {
113-
toDelete.push(objectId);
116+
toDelete.push(objectId); // RTO10c1b
114117
continue;
115118
}
116119

117-
obj.onGCInterval();
120+
obj.onGCInterval(); // RTO10c1a
118121
}
119122

120123
toDelete.forEach((x) => this._pool.delete(x));

0 commit comments

Comments
 (0)