refactor: prevent race conditions on init

This commit is contained in:
Carlos Valente
2024-12-29 20:30:10 +01:00
committed by Carlos Valente
parent f65ec0da3c
commit 0dfef732bf
4 changed files with 29 additions and 15 deletions
+5 -2
View File
@@ -1,4 +1,4 @@
import { LogOrigin, Playback, SimpleDirection, SimplePlayback } from 'ontime-types'; import { LogOrigin, Playback, runtimeStorePlaceholder, SimpleDirection, SimplePlayback } from 'ontime-types';
import 'dotenv/config'; import 'dotenv/config';
import express from 'express'; import express from 'express';
@@ -200,7 +200,7 @@ export const startServer = async (
clock: state.clock, clock: state.clock,
timer: state.timer, timer: state.timer,
onAir: state.timer.playback !== Playback.Stop, onAir: state.timer.playback !== Playback.Stop,
message: messageService.getState(), message: { ...runtimeStorePlaceholder.message },
runtime: state.runtime, runtime: state.runtime,
eventNow: state.eventNow, eventNow: state.eventNow,
currentBlock: { currentBlock: {
@@ -224,6 +224,9 @@ export const startServer = async (
const persistedCustomFields = getDataProvider().getCustomFields(); const persistedCustomFields = getDataProvider().getCustomFields();
initRundown(persistedRundown, persistedCustomFields); initRundown(persistedRundown, persistedCustomFields);
// initialise message service
messageService.init(eventStore.set, eventStore.get);
// load restore point if it exists // load restore point if it exists
const maybeRestorePoint = await restoreService.load(); const maybeRestorePoint = await restoreService.load();
@@ -2,12 +2,21 @@ import { MessageState, runtimeStorePlaceholder } from 'ontime-types';
import { DeepPartial } from 'ts-essentials'; import { DeepPartial } from 'ts-essentials';
import { throttle } from '../../utils/throttle.js'; import { throttle } from '../../utils/throttle.js';
import { eventStore, type PublishFn } from '../../stores/EventStore.js'; import type { StoreGetter, PublishFn } from '../../stores/EventStore.js';
/** /**
* Create a throttled version of the set function * Create a throttled version of the set function
*/ */
const throttledSet: PublishFn = throttle(eventStore.set, 100); let throttledSet: PublishFn = () => undefined;
let storeGet: StoreGetter = (_key: string) => undefined;
/**
* Allows providing store interfaces
*/
export function init(storeSetter: PublishFn, storeGetter: StoreGetter) {
throttledSet = throttle(storeSetter, 100);
storeGet = storeGetter;
}
/** /**
* Exposes function to reset the internal state * Exposes function to reset the internal state
@@ -22,7 +31,7 @@ export function clear() {
* Exposes the internal state of the message service * Exposes the internal state of the message service
*/ */
export function getState(): MessageState { export function getState(): MessageState {
return eventStore.get('message'); return storeGet('message');
} }
/** /**
@@ -2,31 +2,32 @@ import * as messageService from '../MessageService.js';
describe('MessageService', () => { describe('MessageService', () => {
beforeEach(() => { beforeEach(() => {
// at runtime, the store is instantiated before the message service
const store = {};
const storeSetter = (key, value) => (store[key] = value);
const storeGetter = (key) => store[key];
messageService.init(storeSetter, storeGetter);
messageService.clear(); messageService.clear();
}); });
it('should patch the message state', () => { it('should patch the message state', () => {
const message = { const newState = messageService.patch({
timer: { text: 'new text', visible: true }, timer: { text: 'new text', visible: true },
external: 'external', external: 'external',
}; });
const newState = messageService.patch(message); expect(newState).toMatchObject({
expect(newState).toEqual({
timer: { text: 'new text', visible: true, blackout: false, blink: false, secondarySource: null }, timer: { text: 'new text', visible: true, blackout: false, blink: false, secondarySource: null },
external: 'external', external: 'external',
}); });
}); });
it('should not affect other properties when patching', () => { it('should not affect other properties when patching', () => {
const initialMessage = { const newState = messageService.patch({
timer: { text: 'initial text', visible: true }, timer: { text: 'initial text', visible: true },
}; });
const newState = messageService.patch(initialMessage); expect(newState).toMatchObject({
expect(newState).toEqual({
timer: { text: 'initial text', visible: true, blackout: false, blink: false, secondarySource: null }, timer: { text: 'initial text', visible: true, blackout: false, blink: false, secondarySource: null },
external: '', external: '',
}); });
+1
View File
@@ -3,6 +3,7 @@ import { RuntimeStore } from 'ontime-types';
import { socket } from '../adapters/WebsocketAdapter.js'; import { socket } from '../adapters/WebsocketAdapter.js';
export type PublishFn = <T extends keyof RuntimeStore>(key: T, value: RuntimeStore[T]) => void; export type PublishFn = <T extends keyof RuntimeStore>(key: T, value: RuntimeStore[T]) => void;
export type StoreGetter = <T extends keyof RuntimeStore>(key: T) => Partial<RuntimeStore>[T];
let store: Partial<RuntimeStore> = {}; let store: Partial<RuntimeStore> = {};