Skip to content

Commit 3f2a6d6

Browse files
committed
fix(sds): avoid overflow in message history storage
1 parent fcb9082 commit 3f2a6d6

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { expect } from "chai";
2+
3+
import { MemLocalHistory } from "./mem_local_history.js";
4+
import { ContentMessage } from "./message.js";
5+
6+
describe("MemLocalHistory", () => {
7+
it("Cap max size when messages are pushed one at a time", () => {
8+
const maxSize = 2;
9+
10+
const hist = new MemLocalHistory(maxSize);
11+
12+
hist.push(
13+
new ContentMessage("1", "c", "a", [], 1n, undefined, new Uint8Array([1]))
14+
);
15+
expect(hist.length).to.eq(1);
16+
hist.push(
17+
new ContentMessage("2", "c", "a", [], 2n, undefined, new Uint8Array([2]))
18+
);
19+
expect(hist.length).to.eq(2);
20+
21+
hist.push(
22+
new ContentMessage("3", "c", "a", [], 3n, undefined, new Uint8Array([3]))
23+
);
24+
expect(hist.length).to.eq(2);
25+
26+
expect(hist.findIndex((m) => m.messageId === "1")).to.eq(-1);
27+
expect(hist.findIndex((m) => m.messageId === "2")).to.not.eq(-1);
28+
expect(hist.findIndex((m) => m.messageId === "3")).to.not.eq(-1);
29+
});
30+
31+
it("Cap max size when a pushed array is exceeding the cap", () => {
32+
const maxSize = 2;
33+
34+
const hist = new MemLocalHistory(maxSize);
35+
36+
hist.push(
37+
new ContentMessage("1", "c", "a", [], 1n, undefined, new Uint8Array([1]))
38+
);
39+
expect(hist.length).to.eq(1);
40+
hist.push(
41+
new ContentMessage("2", "c", "a", [], 2n, undefined, new Uint8Array([2])),
42+
new ContentMessage("3", "c", "a", [], 3n, undefined, new Uint8Array([3]))
43+
);
44+
expect(hist.length).to.eq(2);
45+
46+
expect(hist.findIndex((m) => m.messageId === "1")).to.eq(-1);
47+
expect(hist.findIndex((m) => m.messageId === "2")).to.not.eq(-1);
48+
expect(hist.findIndex((m) => m.messageId === "3")).to.not.eq(-1);
49+
});
50+
});

packages/sds/src/message_channel/mem_local_history.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,31 @@ import _ from "lodash";
22

33
import { ContentMessage, isContentMessage } from "./message.js";
44

5+
export const DEFAULT_MAX_LENGTH = 10_000;
6+
57
/**
6-
* In-Memory implementation of a local store of messages.
8+
* In-Memory implementation of a local history of messages.
79
*
810
* Messages are store in SDS chronological order:
911
* - messages[0] is the oldest message
1012
* - messages[n] is the newest message
1113
*
1214
* Only stores content message: `message.lamportTimestamp` and `message.content` are present.
15+
*
16+
* Oldest messages are dropped when `maxLength` is reached.
17+
* If an array of items longer than `maxLength` is pushed, dropping will happen
18+
* at next push.
1319
*/
1420
export class MemLocalHistory {
1521
private items: ContentMessage[] = [];
1622

23+
/**
24+
* Construct a new in-memory local history
25+
*
26+
* @param maxLength The maximum number of message to store.
27+
*/
28+
public constructor(private maxLength: number = DEFAULT_MAX_LENGTH) {}
29+
1730
public get length(): number {
1831
return this.items.length;
1932
}
@@ -33,6 +46,12 @@ export class MemLocalHistory {
3346
// Remove duplicates by messageId while maintaining order
3447
this.items = _.uniqBy(combinedItems, "messageId");
3548

49+
// Let's drop older messages if max length is reached
50+
if (this.length > this.maxLength) {
51+
const numItemsToRemove = this.length - this.maxLength;
52+
this.items.splice(0, numItemsToRemove);
53+
}
54+
3655
return this.items.length;
3756
}
3857

0 commit comments

Comments
 (0)