Skip to content

Commit df7ba42

Browse files
authored
Merge pull request Sofie-Automation#1422 from bbc/upstream/fix-publications
fix: some snapshots not restoring correctly due to id generation order & publications lost errors
2 parents 6efa214 + 04b3998 commit df7ba42

File tree

4 files changed

+71
-39
lines changed

4 files changed

+71
-39
lines changed

meteor/server/api/deviceTriggers/RundownsObserver.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { literal } from '@sofie-automation/corelib/dist/lib'
55
import { MongoFieldSpecifierOnesStrict } from '@sofie-automation/corelib/dist/mongo'
66
import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
77
import { PromiseDebounce } from '../../publications/lib/PromiseDebounce'
8+
import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError'
9+
import { logger } from '../../logging'
810

911
const REACTIVITY_DEBOUNCE = 20
1012

@@ -24,15 +26,19 @@ export class RundownsObserver {
2426
#disposed = false
2527

2628
readonly #triggerUpdateRundownContent = new PromiseDebounce(async () => {
27-
if (this.#disposed) return
29+
try {
30+
if (this.#disposed) return
2831

29-
if (!this.#changed) return
30-
this.#cleanup?.()
32+
if (!this.#changed) return
33+
this.#cleanup?.()
3134

32-
const changed = this.#changed
33-
this.#cleanup = await changed(this.rundownIds)
35+
const changed = this.#changed
36+
this.#cleanup = await changed(this.rundownIds)
3437

35-
if (this.#disposed) this.#cleanup?.()
38+
if (this.#disposed) this.#cleanup?.()
39+
} catch (e) {
40+
logger.error(`Error in RundownsObserver triggerUpdateRundownContent: ${stringifyError(e)}`)
41+
}
3642
}, REACTIVITY_DEBOUNCE)
3743

3844
private constructor(onChanged: ChangedHandler) {

meteor/server/publications/lib/PromiseDebounce.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export class PromiseDebounce<TResult = void, TArgs extends unknown[] = []> {
3838

3939
/**
4040
* Trigger an execution, but don't report the result.
41+
* Warning: If the function throws an error, that will not be logged or reported to the caller
4142
*/
4243
trigger = (...args: TArgs): void => {
4344
// If an execution is 'imminent', don't do anything

meteor/server/publications/lib/rundownsObserver.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Meteor } from 'meteor/meteor'
22
import { RundownId, RundownPlaylistId, StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids'
33
import { Rundowns } from '../../collections'
44
import { PromiseDebounce } from './PromiseDebounce'
5+
import { logger } from '../../logging'
6+
import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError'
57

68
const REACTIVITY_DEBOUNCE = 20
79

@@ -20,14 +22,22 @@ export class RundownsObserver implements Meteor.LiveQueryHandle {
2022
#disposed = false
2123

2224
readonly #triggerUpdateRundownContent = new PromiseDebounce(async () => {
23-
if (this.#disposed) return
24-
if (!this.#changed) return
25-
this.#cleanup?.()
26-
27-
const changed = this.#changed
28-
this.#cleanup = await changed(this.rundownIds)
29-
30-
if (this.#disposed) this.#cleanup?.()
25+
try {
26+
if (this.#disposed) return
27+
if (!this.#changed) return
28+
this.#cleanup?.()
29+
this.#cleanup = undefined
30+
31+
const changed = this.#changed
32+
this.#cleanup = await changed(this.rundownIds)
33+
34+
if (this.#disposed) {
35+
this.#cleanup?.()
36+
this.#cleanup = undefined
37+
}
38+
} catch (e) {
39+
logger.error(`Error in RundownsObserver triggerUpdateRundownContent: ${stringifyError(e)}`)
40+
}
3141
}, REACTIVITY_DEBOUNCE)
3242

3343
private constructor(onChanged: ChangedHandler) {

packages/job-worker/src/playout/snapshot.ts

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@ import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/Rund
3232
import { RundownOrphanedReason } from '@sofie-automation/corelib/dist/dataModel/Rundown'
3333
import { SofieIngestDataCacheObj } from '@sofie-automation/corelib/dist/dataModel/SofieIngestDataCache'
3434

35+
class IdMapWithGenerator<V extends ProtectedString<any>> extends Map<V, V> {
36+
getOrGenerate(key: V): V {
37+
return this.getOrGenerateAndWarn(key, '')
38+
}
39+
40+
getOrGenerateAndWarn(key: V, name: string): V {
41+
const existing = this.get(key)
42+
if (existing) return existing
43+
44+
const newValue = getRandomId<V>()
45+
this.set(key, newValue)
46+
if (name) logger.warn(`Couldn't find "${name}" when restoring snapshot`)
47+
return newValue
48+
}
49+
}
50+
3551
/**
3652
* Generate the Playlist owned portions of a Playlist snapshot
3753
*/
@@ -244,7 +260,7 @@ export async function handleRestorePlaylistSnapshot(
244260
rd._id = getRandomId()
245261
rundownIdMap.set(oldId, rd._id)
246262
}
247-
const partIdMap = new Map<PartId, PartId>()
263+
const partIdMap = new IdMapWithGenerator<PartId>()
248264
for (const part of snapshot.parts) {
249265
const oldId = part._id
250266
part._id = part.externalId ? getPartId(getNewRundownId(part.rundownId), part.externalId) : getRandomId()
@@ -253,32 +269,34 @@ export async function handleRestorePlaylistSnapshot(
253269
}
254270

255271
const partInstanceOldRundownIdMap = new Map<PartInstanceId, RundownId>()
256-
const partInstanceIdMap = new Map<PartInstanceId, PartInstanceId>()
272+
const partInstanceIdMap = new IdMapWithGenerator<PartInstanceId>()
257273
for (const partInstance of snapshot.partInstances) {
258274
const oldId = partInstance._id
259275
partInstance._id = getRandomId()
260276
partInstanceIdMap.set(oldId, partInstance._id)
261277

262-
partInstance.part._id = partIdMap.get(partInstance.part._id) || getRandomId()
278+
partInstance.part._id = partIdMap.getOrGenerate(partInstance.part._id)
263279
partInstanceOldRundownIdMap.set(oldId, partInstance.rundownId)
264280
}
265-
const segmentIdMap = new Map<SegmentId, SegmentId>()
281+
const segmentIdMap = new IdMapWithGenerator<SegmentId>()
266282
for (const segment of snapshot.segments) {
267283
const oldId = segment._id
268284
segment._id = getSegmentId(getNewRundownId(segment.rundownId), segment.externalId)
269285
segmentIdMap.set(oldId, segment._id)
270286
}
271287
type AnyPieceId = PieceId | AdLibActionId | RundownBaselineAdLibActionId
272-
const pieceIdMap = new Map<AnyPieceId, AnyPieceId>()
288+
const pieceIdMap = new IdMapWithGenerator<AnyPieceId>()
273289
for (const piece of snapshot.pieces) {
274290
const oldId = piece._id
275291
piece.startRundownId = getNewRundownId(piece.startRundownId)
276-
piece.startPartId =
277-
partIdMap.get(piece.startPartId) ||
278-
getRandomIdAndWarn(`piece.startPartId=${piece.startPartId} of piece=${piece._id}`)
279-
piece.startSegmentId =
280-
segmentIdMap.get(piece.startSegmentId) ||
281-
getRandomIdAndWarn(`piece.startSegmentId=${piece.startSegmentId} of piece=${piece._id}`)
292+
piece.startPartId = partIdMap.getOrGenerateAndWarn(
293+
piece.startPartId,
294+
`piece.startPartId=${piece.startPartId} of piece=${piece._id}`
295+
)
296+
piece.startSegmentId = segmentIdMap.getOrGenerateAndWarn(
297+
piece.startSegmentId,
298+
`piece.startSegmentId=${piece.startSegmentId} of piece=${piece._id}`
299+
)
282300
piece._id = getRandomId()
283301
pieceIdMap.set(oldId, piece._id)
284302
}
@@ -297,10 +315,11 @@ export async function handleRestorePlaylistSnapshot(
297315
for (const pieceInstance of snapshot.pieceInstances) {
298316
pieceInstance._id = getRandomId()
299317

300-
pieceInstance.piece._id = (pieceIdMap.get(pieceInstance.piece._id) || getRandomId()) as PieceId // Note: don't warn if not found, as the piece may have been deleted
318+
pieceInstance.piece._id = pieceIdMap.getOrGenerate(pieceInstance.piece._id) as PieceId // Note: don't warn if not found, as the piece may have been deleted
301319
if (pieceInstance.infinite) {
302-
pieceInstance.infinite.infinitePieceId =
303-
(pieceIdMap.get(pieceInstance.infinite.infinitePieceId) as PieceId) || getRandomId() // Note: don't warn if not found, as the piece may have been deleted
320+
pieceInstance.infinite.infinitePieceId = pieceIdMap.getOrGenerate(
321+
pieceInstance.infinite.infinitePieceId
322+
) as PieceId // Note: don't warn if not found, as the piece may have been deleted
304323
}
305324
}
306325

@@ -330,9 +349,10 @@ export async function handleRestorePlaylistSnapshot(
330349
case ExpectedPackageDBType.ADLIB_ACTION:
331350
case ExpectedPackageDBType.BASELINE_ADLIB_PIECE:
332351
case ExpectedPackageDBType.BASELINE_ADLIB_ACTION: {
333-
expectedPackage.pieceId =
334-
pieceIdMap.get(expectedPackage.pieceId) ||
335-
getRandomIdAndWarn(`expectedPackage.pieceId=${expectedPackage.pieceId}`)
352+
expectedPackage.pieceId = pieceIdMap.getOrGenerateAndWarn(
353+
expectedPackage.pieceId,
354+
`expectedPackage.pieceId=${expectedPackage.pieceId}`
355+
)
336356

337357
expectedPackage._id = getExpectedPackageId(expectedPackage.pieceId, expectedPackage.blueprintPackageId)
338358

@@ -383,13 +403,13 @@ export async function handleRestorePlaylistSnapshot(
383403
}
384404

385405
if (obj.partId) {
386-
obj.partId = partIdMap.get(obj.partId) || getRandomId()
406+
obj.partId = partIdMap.getOrGenerate(obj.partId)
387407
}
388408
if (obj.segmentId) {
389-
obj.segmentId = segmentIdMap.get(obj.segmentId) || getRandomId()
409+
obj.segmentId = segmentIdMap.getOrGenerate(obj.segmentId)
390410
}
391411
if (obj.partInstanceId) {
392-
obj.partInstanceId = partInstanceIdMap.get(obj.partInstanceId) || getRandomId()
412+
obj.partInstanceId = partInstanceIdMap.getOrGenerate(obj.partInstanceId)
393413
}
394414

395415
if (updateOwnId) {
@@ -516,11 +536,6 @@ export async function handleRestorePlaylistSnapshot(
516536
}
517537
}
518538

519-
function getRandomIdAndWarn<T extends ProtectedString<any>>(name: string): T {
520-
logger.warn(`Couldn't find "${name}" when restoring snapshot`)
521-
return getRandomId<T>()
522-
}
523-
524539
function fixupImportedSelectedPartInstanceIds(
525540
snapshot: CoreRundownPlaylistSnapshot,
526541
rundownIdMap: Map<RundownId, RundownId>,

0 commit comments

Comments
 (0)