From 0dfef732bfa31b1e3bfe75be4e8fe9b349542134 Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Sun, 29 Dec 2024 20:30:10 +0100 Subject: [PATCH] refactor: prevent race conditions on init --- apps/server/src/app.ts | 7 +++++-- .../message-service/MessageService.ts | 15 ++++++++++--- .../__tests__/MessageService.test.ts | 21 ++++++++++--------- apps/server/src/stores/EventStore.ts | 1 + 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 92f53f3c4..233e2f547 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -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 express from 'express'; @@ -200,7 +200,7 @@ export const startServer = async ( clock: state.clock, timer: state.timer, onAir: state.timer.playback !== Playback.Stop, - message: messageService.getState(), + message: { ...runtimeStorePlaceholder.message }, runtime: state.runtime, eventNow: state.eventNow, currentBlock: { @@ -224,6 +224,9 @@ export const startServer = async ( const persistedCustomFields = getDataProvider().getCustomFields(); initRundown(persistedRundown, persistedCustomFields); + // initialise message service + messageService.init(eventStore.set, eventStore.get); + // load restore point if it exists const maybeRestorePoint = await restoreService.load(); diff --git a/apps/server/src/services/message-service/MessageService.ts b/apps/server/src/services/message-service/MessageService.ts index 42e214d35..eec0c3266 100644 --- a/apps/server/src/services/message-service/MessageService.ts +++ b/apps/server/src/services/message-service/MessageService.ts @@ -2,12 +2,21 @@ import { MessageState, runtimeStorePlaceholder } from 'ontime-types'; import { DeepPartial } from 'ts-essentials'; 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 */ -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 @@ -22,7 +31,7 @@ export function clear() { * Exposes the internal state of the message service */ export function getState(): MessageState { - return eventStore.get('message'); + return storeGet('message'); } /** diff --git a/apps/server/src/services/message-service/__tests__/MessageService.test.ts b/apps/server/src/services/message-service/__tests__/MessageService.test.ts index d6c30916d..0ef7ef153 100644 --- a/apps/server/src/services/message-service/__tests__/MessageService.test.ts +++ b/apps/server/src/services/message-service/__tests__/MessageService.test.ts @@ -2,31 +2,32 @@ import * as messageService from '../MessageService.js'; describe('MessageService', () => { 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(); }); it('should patch the message state', () => { - const message = { + const newState = messageService.patch({ timer: { text: 'new text', visible: true }, external: 'external', - }; + }); - const newState = messageService.patch(message); - - expect(newState).toEqual({ + expect(newState).toMatchObject({ timer: { text: 'new text', visible: true, blackout: false, blink: false, secondarySource: null }, external: 'external', }); }); it('should not affect other properties when patching', () => { - const initialMessage = { + const newState = messageService.patch({ timer: { text: 'initial text', visible: true }, - }; + }); - const newState = messageService.patch(initialMessage); - - expect(newState).toEqual({ + expect(newState).toMatchObject({ timer: { text: 'initial text', visible: true, blackout: false, blink: false, secondarySource: null }, external: '', }); diff --git a/apps/server/src/stores/EventStore.ts b/apps/server/src/stores/EventStore.ts index f60d62854..1405a23ba 100644 --- a/apps/server/src/stores/EventStore.ts +++ b/apps/server/src/stores/EventStore.ts @@ -3,6 +3,7 @@ import { RuntimeStore } from 'ontime-types'; import { socket } from '../adapters/WebsocketAdapter.js'; export type PublishFn = (key: T, value: RuntimeStore[T]) => void; +export type StoreGetter = (key: T) => Partial[T]; let store: Partial = {};