Skip to content

Commit 7da857d

Browse files
committed
Migrated store data to SharedArrayBuffer for utilizing WebWorkers
1 parent 50c991b commit 7da857d

18 files changed

Lines changed: 1015 additions & 101 deletions

web/src/app/extensions/public/module.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,12 @@
1616

1717
import { NgModule } from '@angular/core';
1818
import { KHIExtensionBundle } from '../extension-common/extension';
19-
import { NodeNameBindingWithPod } from './timeline-navigator-extensions';
2019
import { Reporter, ConsoleReporter } from 'src/app/common/reporter/reporter';
2120

2221
@NgModule({
2322
providers: [
24-
KHIExtensionBundle.forExtension(initExtension),
23+
KHIExtensionBundle.forExtension(() => {}),
2524
{ provide: Reporter, useClass: ConsoleReporter },
2625
],
2726
})
2827
export class PublicKHIExtension {}
29-
30-
function initExtension(extension: KHIExtensionBundle) {
31-
extension.addTimelineNavigatorExtension(new NodeNameBindingWithPod());
32-
}

web/src/app/parser/core/builder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ export class InspectionDataBuilder {
6363
private iconAtlasPromise?: Promise<void>;
6464

6565
constructor() {
66-
this.logStore = new LogStore(this.internPool, this.styleStore);
67-
this.timelineStore = new TimelineStore(
66+
this.logStore = LogStore.create(this.internPool, this.styleStore);
67+
this.timelineStore = TimelineStore.create(
6868
this.internPool,
6969
this.styleStore,
7070
this.logStore,

web/src/app/services/selection-manager-v2.service.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ describe('SelectionManagerV2', () => {
3939

4040
const internPool = InternPoolStore.create();
4141
const styleStore = new StyleStore();
42-
logStore = new LogStore(internPool, styleStore);
43-
timelineStore = new TimelineStore(internPool, styleStore, logStore);
42+
logStore = LogStore.create(internPool, styleStore);
43+
timelineStore = TimelineStore.create(internPool, styleStore, logStore);
4444

4545
styleStore.addSeverities([
4646
{

web/src/app/store/domain/filter/cel-env.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ describe('CELTimelineFilterEnvironment', () => {
3737
env = new CELTimelineFilterEnvironment();
3838
internPool = InternPoolStore.create();
3939
styleStore = new StyleStore();
40-
logStore = new LogStore(internPool, styleStore);
41-
timelineStore = new TimelineStore(internPool, styleStore, logStore);
40+
logStore = LogStore.create(internPool, styleStore);
41+
timelineStore = TimelineStore.create(internPool, styleStore, logStore);
4242
});
4343

4444
it('should successfully compile a valid CEL expression', () => {

web/src/app/store/domain/intern-pool-store.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,41 @@ describe('InternPoolStore', () => {
209209
sharedStore.addFieldPathSets([{ id: 200, fieldPathStringIds: [10] }]);
210210
}).toThrowError('Cannot write to a shared read-only InternPoolStore');
211211
});
212+
213+
describe('ArrayBuffer fallback when SharedArrayBuffer is unsupported', () => {
214+
let originalSharedArrayBuffer: typeof SharedArrayBuffer | undefined;
215+
216+
beforeEach(() => {
217+
originalSharedArrayBuffer = (
218+
globalThis as Record<string, typeof SharedArrayBuffer | undefined>
219+
)['SharedArrayBuffer'];
220+
(globalThis as Record<string, typeof SharedArrayBuffer | undefined>)[
221+
'SharedArrayBuffer'
222+
] = undefined;
223+
});
224+
225+
afterEach(() => {
226+
(globalThis as Record<string, typeof SharedArrayBuffer | undefined>)[
227+
'SharedArrayBuffer'
228+
] = originalSharedArrayBuffer;
229+
});
230+
231+
it('should allocate ArrayBuffer instead of SharedArrayBuffer and perform operations successfully', () => {
232+
const fallbackStore = InternPoolStore.create();
233+
fallbackStore.addStrings([
234+
{ id: 10, value: 'fallback-string-1' },
235+
{ id: 20, value: 'fallback-string-2' },
236+
]);
237+
238+
expect(fallbackStore.getString(10)).toBe('fallback-string-1');
239+
expect(fallbackStore.getString(20)).toBe('fallback-string-2');
240+
241+
const sharedData = fallbackStore.getSharedData();
242+
expect(sharedData.metadataSab instanceof ArrayBuffer).toBeTrue();
243+
expect(sharedData.bufferSabs[0] instanceof ArrayBuffer).toBeTrue();
244+
245+
const reconstructedStore = InternPoolStore.fromSharedData(sharedData);
246+
expect(reconstructedStore.getString(10)).toBe('fallback-string-1');
247+
});
248+
});
212249
});

web/src/app/store/domain/intern-pool-store.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export interface StringEntryDTO {
2828
readonly value: string;
2929
}
3030

31+
import { allocateBuffer, isSharedBuffer } from 'src/app/store/domain/types';
32+
3133
/**
3234
* Represents an entry defining a set of field path names.
3335
*/
@@ -47,8 +49,8 @@ export interface FieldPathSetEntryDTO {
4749
* This can be transferred to a WebWorker via postMessage.
4850
*/
4951
export interface InternPoolSharedData {
50-
readonly bufferSabs: readonly SharedArrayBuffer[];
51-
readonly metadataSab: SharedArrayBuffer;
52+
readonly bufferSabs: readonly (SharedArrayBuffer | ArrayBuffer)[];
53+
readonly metadataSab: SharedArrayBuffer | ArrayBuffer;
5254
readonly capacity: number;
5355
readonly fieldPathSets: readonly (readonly number[])[];
5456
}
@@ -65,7 +67,7 @@ export class InternPoolStore {
6567
/**
6668
* The SharedArrayBuffers backing the encoded string buffers.
6769
*/
68-
private readonly bufferSabs: SharedArrayBuffer[] = [];
70+
private readonly bufferSabs: (SharedArrayBuffer | ArrayBuffer)[] = [];
6971

7072
/**
7173
* Tracks the buffer index for each string ID (1-based index, 0 represents uninitialized).
@@ -85,7 +87,7 @@ export class InternPoolStore {
8587
/**
8688
* The single SharedArrayBuffer holding all metadata arrays.
8789
*/
88-
private metadataSab: SharedArrayBuffer;
90+
private metadataSab: SharedArrayBuffer | ArrayBuffer;
8991

9092
/**
9193
* Whether this store is read-only (for worker-side decoding).
@@ -121,7 +123,7 @@ export class InternPoolStore {
121123
this.readOnly = readOnly;
122124
if (typeof initialCapacityOrSharedData === 'number') {
123125
const initialCapacity = initialCapacityOrSharedData;
124-
this.metadataSab = new SharedArrayBuffer(initialCapacity * 10);
126+
this.metadataSab = allocateBuffer(initialCapacity * 10);
125127
this.bufferIndices = new Uint16Array(
126128
this.metadataSab,
127129
0,
@@ -208,7 +210,7 @@ export class InternPoolStore {
208210
this.maxBufferSize - this.currentOffset < encoded.length
209211
) {
210212
const newSize = Math.max(this.maxBufferSize, encoded.length);
211-
const sab = new SharedArrayBuffer(newSize);
213+
const sab = allocateBuffer(newSize);
212214
this.bufferSabs.push(sab);
213215
this.buffers.push(new Uint8Array(sab));
214216
this.currentBufferIndex = this.buffers.length - 1;
@@ -261,9 +263,12 @@ export class InternPoolStore {
261263

262264
const buffer = this.buffers[bufferIndex];
263265
const bytes = buffer.subarray(offset, offset + length);
264-
const nonSharedBytes = new Uint8Array(length);
265-
nonSharedBytes.set(bytes);
266-
return this.decoder.decode(nonSharedBytes);
266+
if (isSharedBuffer(bytes.buffer)) {
267+
const nonSharedBytes = new Uint8Array(length);
268+
nonSharedBytes.set(bytes);
269+
return this.decoder.decode(nonSharedBytes);
270+
}
271+
return this.decoder.decode(bytes);
267272
}
268273

269274
/**
@@ -310,7 +315,7 @@ export class InternPoolStore {
310315
newCapacity *= 2;
311316
}
312317

313-
const newMetadataSab = new SharedArrayBuffer(newCapacity * 10);
318+
const newMetadataSab = allocateBuffer(newCapacity * 10);
314319
const newBufferIndices = new Uint16Array(newMetadataSab, 0, newCapacity);
315320
const newOffsets = new Uint32Array(
316321
newMetadataSab,

web/src/app/store/domain/log-store.spec.ts

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe('LogStore', () => {
3333
beforeEach(() => {
3434
internPool = InternPoolStore.create();
3535
styleStore = new StyleStore();
36-
store = new LogStore(internPool, styleStore);
36+
store = LogStore.create(internPool, styleStore);
3737

3838
// Avoid errors of missing keys in basic tests
3939
styleStore.addSeverities([
@@ -316,4 +316,99 @@ describe('LogStore', () => {
316316
expect(iteratedLogs[0].id).toBe(1);
317317
expect(iteratedLogs[1].id).toBe(2);
318318
});
319+
320+
it('should restore from shared memory using fromSharedData and enforce readOnly guard', () => {
321+
internPool.addStrings([
322+
{ id: 20, value: 'user' },
323+
{ id: 21, value: 'alice' },
324+
]);
325+
internPool.addFieldPathSets([{ id: 2, fieldPathStringIds: [20] }]);
326+
const struct = create(InternedStructSchema, {
327+
fieldPathSetId: 2,
328+
values: [
329+
create(InternedValueSchema, {
330+
kind: { case: 'stringValue', value: 21 },
331+
}),
332+
],
333+
});
334+
const rawBody = toBinary(InternedStructSchema, struct);
335+
336+
const logs: LogDTO[] = [
337+
{
338+
id: 1,
339+
ts: 1000n,
340+
logTypeId: 1,
341+
severityTypeId: 1,
342+
summaryStringId: 1,
343+
body: rawBody,
344+
},
345+
];
346+
347+
store.initialize(logs, 1);
348+
349+
const sharedData = store.getSharedData();
350+
const restoredStore = LogStore.fromSharedData(
351+
internPool,
352+
styleStore,
353+
sharedData,
354+
);
355+
356+
expect(restoredStore.count).toBe(1);
357+
const restoredLog = restoredStore.getLog(1);
358+
expect(restoredLog.id).toBe(1);
359+
expect(restoredLog.timestamp).toBe(1000n);
360+
expect(restoredLog.body).toEqual({ user: 'alice' });
361+
362+
expect(() => {
363+
restoredStore.initialize(logs, 1);
364+
}).toThrowError('Cannot write to a shared read-only LogStore');
365+
});
366+
367+
describe('ArrayBuffer fallback when SharedArrayBuffer is unsupported', () => {
368+
let originalSharedArrayBuffer: typeof SharedArrayBuffer | undefined;
369+
370+
beforeEach(() => {
371+
originalSharedArrayBuffer = (
372+
globalThis as Record<string, typeof SharedArrayBuffer | undefined>
373+
)['SharedArrayBuffer'];
374+
(globalThis as Record<string, typeof SharedArrayBuffer | undefined>)[
375+
'SharedArrayBuffer'
376+
] = undefined;
377+
});
378+
379+
afterEach(() => {
380+
(globalThis as Record<string, typeof SharedArrayBuffer | undefined>)[
381+
'SharedArrayBuffer'
382+
] = originalSharedArrayBuffer;
383+
});
384+
385+
it('should allocate ArrayBuffer instead of SharedArrayBuffer and perform operations successfully', () => {
386+
const fallbackStore = LogStore.create(internPool, styleStore);
387+
const logs: LogDTO[] = [
388+
{
389+
id: 1,
390+
ts: 1000n,
391+
logTypeId: 1,
392+
severityTypeId: 1,
393+
summaryStringId: 1,
394+
},
395+
];
396+
fallbackStore.initialize(logs, 1);
397+
398+
expect(fallbackStore.count).toBe(1);
399+
const log = fallbackStore.getLog(1);
400+
expect(log.id).toBe(1);
401+
402+
const sharedData = fallbackStore.getSharedData();
403+
expect(sharedData.metadataSab instanceof ArrayBuffer).toBeTrue();
404+
405+
const restoredStore = LogStore.fromSharedData(
406+
internPool,
407+
styleStore,
408+
sharedData,
409+
);
410+
expect(restoredStore.count).toBe(1);
411+
expect(restoredStore.getLog(1).id).toBe(1);
412+
});
413+
});
319414
});

0 commit comments

Comments
 (0)