Skip to content

Commit de04500

Browse files
authored
Merge pull request #207 from nrkno/fix/pm-stuck-on-error
fix: state machine improvements and error handling (SOFIE-3751)
2 parents d93e29a + 7a61f5f commit de04500

File tree

7 files changed

+89
-59
lines changed

7 files changed

+89
-59
lines changed

apps/appcontainer-node/packages/generic/src/appContainer.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040

4141
import { WorkforceAPI } from './workforceApi'
4242
import { WorkerAgentAPI } from './workerAgentApi'
43+
import { ExpectedPackageStatusAPI } from '@sofie-automation/shared-lib/dist/package-manager/package'
4344

4445
/** Mimimum time between app restarts */
4546
const RESTART_COOLDOWN = 60 * 1000 // ms
@@ -413,6 +414,10 @@ export class AppContainer {
413414
this.logger.debug(`Available apps: ${Array.from(this.availableApps.keys()).join(', ')}`)
414415
}
415416

417+
let lastNotSupportReason: ExpectedPackageStatusAPI.Reason = {
418+
user: 'No apps available',
419+
tech: 'No apps available',
420+
}
416421
for (const [appType, availableApp] of this.availableApps.entries()) {
417422
const runningApp = await this.getRunningOrSpawnScalingApp(appType)
418423

@@ -424,6 +429,11 @@ export class AppContainer {
424429
appType: appType,
425430
cost: availableApp.cost,
426431
}
432+
} else {
433+
lastNotSupportReason = result.reason
434+
this.logger.silly(
435+
`App "${appType}": Does not support the expectation, reason: "${result.reason.tech}", cost: "${availableApp.cost}"`
436+
)
427437
}
428438
} else {
429439
this.logger.warn(`appType "${appType}" not available`)
@@ -432,8 +442,8 @@ export class AppContainer {
432442
return {
433443
success: false,
434444
reason: {
435-
user: `No worker supports this expectation`,
436-
tech: `No worker supports this expectation`,
445+
user: `No worker supports this expectation (reason: ${lastNotSupportReason?.user})`,
446+
tech: `No worker supports this expectation (one reason: ${lastNotSupportReason?.tech})`,
437447
},
438448
}
439449
}

scripts/prepare-for-tests.mjs

+10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import path from 'path'
1313
import cp from 'child_process'
1414
import fetch from 'node-fetch'
1515

16+
console.log(`Preparing for tests...`)
17+
1618
const targetVersions = JSON.parse(await fs.readFile('./tests/ffmpegReleases.json'))
1719

1820
const toPosix = (str) => str.split(path.sep).join(path.posix.sep)
@@ -33,12 +35,14 @@ async function pathExists(path) {
3335

3436
const platformInfo = `${process.platform}-${process.arch}`
3537
const platformVersions = targetVersions[platformInfo]
38+
let didDoAnything = false
3639

3740
if (platformVersions) {
3841
for (const version of platformVersions) {
3942
const versionPath = path.join(ffmpegRootDir, version.id)
4043
const dirStat = await pathExists(versionPath)
4144
if (!dirStat) {
45+
didDoAnything = true
4246
console.log(`Fetching ${version.url}`)
4347
// Download it
4448

@@ -81,3 +85,9 @@ if (platformVersions) {
8185
} else {
8286
throw new Error(`No FFMpeg binaries have been defined for "${platformInfo}" yet`)
8387
}
88+
89+
if (!didDoAnything) {
90+
console.log(`Nothing to prepare!`)
91+
} else {
92+
console.log(`Preparations done!`)
93+
}

shared/packages/api/src/worker.ts

-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export type ReturnTypeIsExpectationReadyToStartWorkingOn =
3131
*/
3232
sourceExists?: boolean
3333
isPlaceholder?: boolean
34-
isWaitingForAnother?: boolean
3534
reason: Reason
3635
}
3736
export type ReturnTypeIsExpectationFulfilled =

shared/packages/expectationManager/src/evaluationRunner/evaluateExpectationStates/fulfilled.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export async function evaluateExpectationStateFulfilled({
1919
assertState(trackedExp, ExpectedPackageStatusAPI.WorkStatusState.FULFILLED)
2020
// TODO: Some monitor that is able to invalidate if it isn't fulfilled anymore?
2121

22+
// We don't want to check too often if it's still fulfilled:
2223
if (timeSinceLastEvaluation > tracker.getFulfilledWaitTime()) {
2324
await manager.workerAgents.assignWorkerToSession(trackedExp)
2425
if (trackedExp.session.assignedWorker) {

shared/packages/expectationManager/src/evaluationRunner/evaluateExpectationStates/waiting.ts

+48-41
Original file line numberDiff line numberDiff line change
@@ -23,63 +23,70 @@ export async function evaluateExpectationStateWaiting({
2323

2424
if (trackedExp.session.assignedWorker) {
2525
try {
26+
// First check if the Expectation depends on the fulfilled-status of another Expectation:
27+
const isWaitingForOther = tracker.trackedExpectationAPI.isExpectationWaitingForOther(trackedExp)
28+
29+
if (isWaitingForOther) {
30+
// Not ready to start because it's waiting for another expectation to be fulfilled first
31+
// Stay here in WAITING state:
32+
tracker.trackedExpectationAPI.updateTrackedExpectationStatus(trackedExp, {
33+
reason: {
34+
user: `Waiting for "${isWaitingForOther.exp.statusReport.label}"`,
35+
tech: `Waiting for "${isWaitingForOther.exp.statusReport.label}"`,
36+
},
37+
})
38+
39+
return
40+
}
41+
2642
// First, check if it is already fulfilled:
2743
const fulfilled = await trackedExp.session.assignedWorker.worker.isExpectationFulfilled(
2844
trackedExp.exp,
2945
false
3046
)
3147
if (fulfilled.fulfilled) {
3248
// The expectation is already fulfilled:
49+
3350
tracker.trackedExpectationAPI.updateTrackedExpectationStatus(trackedExp, {
3451
state: ExpectedPackageStatusAPI.WorkStatusState.FULFILLED,
3552
})
3653
if (tracker.trackedExpectationAPI.onExpectationFulfilled(trackedExp)) {
3754
// Something was triggered, run again ASAP:
3855
trackedExp.session.triggerOtherExpectationsAgain = true
3956
}
40-
} else {
41-
const readyToStart = await tracker.trackedExpectationAPI.isExpectationReadyToStartWorkingOn(
42-
trackedExp.session.assignedWorker.worker,
43-
trackedExp
44-
)
57+
return
58+
}
59+
const readyToStart = await tracker.trackedExpectationAPI.isExpectationReadyToStartWorkingOn(
60+
trackedExp.session.assignedWorker.worker,
61+
trackedExp
62+
)
4563

46-
const newStatus: Partial<TrackedExpectation['status']> = {}
47-
{
48-
const sourceExists = readyToStart.ready || readyToStart.sourceExists
49-
if (sourceExists !== undefined) newStatus.sourceExists = sourceExists
50-
}
51-
{
52-
const isPlaceholder = readyToStart.ready ? false : readyToStart.isPlaceholder
53-
if (isPlaceholder !== undefined) newStatus.sourceIsPlaceholder = isPlaceholder
54-
}
64+
const newStatus: Partial<TrackedExpectation['status']> = {}
65+
{
66+
const sourceExists = readyToStart.ready || readyToStart.sourceExists
67+
if (sourceExists !== undefined) newStatus.sourceExists = sourceExists
68+
}
69+
{
70+
const isPlaceholder = readyToStart.ready ? false : readyToStart.isPlaceholder
71+
if (isPlaceholder !== undefined) newStatus.sourceIsPlaceholder = isPlaceholder
72+
}
5573

56-
if (readyToStart.ready) {
57-
tracker.trackedExpectationAPI.updateTrackedExpectationStatus(trackedExp, {
58-
state: ExpectedPackageStatusAPI.WorkStatusState.READY,
59-
reason: {
60-
user: 'About to start working..',
61-
tech: `About to start, was not fulfilled: ${fulfilled.reason.tech}`,
62-
},
63-
status: newStatus,
64-
})
65-
} else {
66-
// Not ready to start
67-
if (readyToStart.isWaitingForAnother) {
68-
// Not ready to start because it's waiting for another expectation to be fulfilled first
69-
// Stay here in WAITING state:
70-
tracker.trackedExpectationAPI.updateTrackedExpectationStatus(trackedExp, {
71-
reason: readyToStart.reason,
72-
status: newStatus,
73-
})
74-
} else {
75-
// Not ready to start because of some other reason (e.g. the source doesn't exist)
76-
// Stay here in WAITING state:
77-
tracker.trackedExpectationAPI.updateTrackedExpectationStatus(trackedExp, {
78-
reason: readyToStart.reason,
79-
status: newStatus,
80-
})
81-
}
82-
}
74+
if (readyToStart.ready) {
75+
tracker.trackedExpectationAPI.updateTrackedExpectationStatus(trackedExp, {
76+
state: ExpectedPackageStatusAPI.WorkStatusState.READY,
77+
reason: {
78+
user: 'About to start working..',
79+
tech: `About to start, was not fulfilled: ${fulfilled.reason.tech}`,
80+
},
81+
status: newStatus,
82+
})
83+
} else {
84+
// Not ready to start because of some reason (e.g. the source doesn't exist)
85+
// Stay here in WAITING state:
86+
tracker.trackedExpectationAPI.updateTrackedExpectationStatus(trackedExp, {
87+
reason: readyToStart.reason,
88+
status: newStatus,
89+
})
8390
}
8491
} catch (error) {
8592
// There was an error, clearly it's not ready to start

shared/packages/expectationManager/src/expectationTracker/lib/trackedExpectationAPI.ts

-14
Original file line numberDiff line numberDiff line change
@@ -110,20 +110,6 @@ export class TrackedExpectationAPI {
110110
workerAgent: WorkerAgentAPI,
111111
trackedExp: TrackedExpectation
112112
): Promise<ReturnTypeIsExpectationReadyToStartWorkingOn> {
113-
// First check if the Expectation depends on the fulfilled-status of another Expectation:
114-
const waitingFor = this.isExpectationWaitingForOther(trackedExp)
115-
116-
if (waitingFor) {
117-
return {
118-
ready: false,
119-
reason: {
120-
user: `Waiting for "${waitingFor.exp.statusReport.label}"`,
121-
tech: `Waiting for "${waitingFor.exp.statusReport.label}"`,
122-
},
123-
isWaitingForAnother: true,
124-
}
125-
}
126-
127113
return workerAgent.isExpectationReadyToStartWorkingOn(trackedExp.exp)
128114
}
129115
/** Checks if the expectation is waiting for another expectation, and returns the awaited Expectation, otherwise null */

shared/packages/worker/src/worker/accessorHandlers/quantel.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,31 @@ export class QuantelAccessorHandle<Metadata> extends GenericAccessorHandle<Metad
155155

156156
const clipSummary = await this.searchForLatestClip(quantel)
157157

158+
const content = this.getContent()
159+
158160
if (!clipSummary) {
159-
const content = this.getContent()
160161
return {
161162
success: false,
162163
packageExists: false,
163164
reason: { user: `Clip not found`, tech: `Clip "${content.guid || content.title}" not found` },
164165
}
165166
}
166167

168+
// Verify that the clip does exist:
169+
const clipData = await quantel.getClip(clipSummary.ClipID)
170+
if (!clipData) {
171+
return {
172+
success: false,
173+
packageExists: false,
174+
reason: {
175+
user: `Clip not found`,
176+
tech: `Clip id ${clipSummary.ClipID} not found in this zone (although "${
177+
content.guid || content.title
178+
}" was found)`,
179+
},
180+
}
181+
}
182+
167183
if (!parseInt(clipSummary.Frames, 10)) {
168184
return {
169185
success: false,
@@ -279,6 +295,7 @@ export class QuantelAccessorHandle<Metadata> extends GenericAccessorHandle<Metad
279295
// Verify that the clip is of the right version:
280296
const clipData = await quantel.getClip(readInfo.clipId)
281297
if (!clipData) throw new Error(`Clip id ${readInfo.clipId} not found`)
298+
282299
if (clipData.Created !== readInfo.version.created)
283300
throw new Error(
284301
`Clip id ${readInfo.clipId} property "Created" doesn't match (${clipData.Created} vs ${readInfo.version.created})`

0 commit comments

Comments
 (0)