Skip to content

Commit

Permalink
Merge pull request #167 from nrkno/fix/infinite-number
Browse files Browse the repository at this point in the history
fix: don't use infinite numbers, because they are not json-serializeable (SOFIE-3081)
  • Loading branch information
nytamin authored May 8, 2024
2 parents 9908dbf + cdc72c6 commit 22bea72
Show file tree
Hide file tree
Showing 11 changed files with 62 additions and 35 deletions.
7 changes: 4 additions & 3 deletions apps/appcontainer-node/packages/generic/src/appContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
LockId,
protectString,
getLogLevel,
Cost,
} from '@sofie-package-manager/api'

import { WorkforceAPI } from './workforceApi'
Expand Down Expand Up @@ -373,7 +374,7 @@ export class AppContainer {

async requestAppTypeForExpectation(
exp: Expectation.Any
): Promise<{ success: true; appType: AppType; cost: number } | { success: false; reason: Reason }> {
): Promise<{ success: true; appType: AppType; cost: Cost } | { success: false; reason: Reason }> {
this.logger.debug(`Got request for resources, for exp "${exp.id}"`)
if (this.apps.size >= this.config.appContainer.maxRunningApps) {
this.logger.debug(`Is already at our limit, no more resources available`)
Expand Down Expand Up @@ -420,7 +421,7 @@ export class AppContainer {

async requestAppTypeForPackageContainer(
packageContainer: PackageContainerExpectation
): Promise<{ success: true; appType: AppType; cost: number } | { success: false; reason: Reason }> {
): Promise<{ success: true; appType: AppType; cost: Cost } | { success: false; reason: Reason }> {
this.logger.debug(`Got request for resources, for packageContainer "${packageContainer.id}"`)
if (this.apps.size >= this.config.appContainer.maxRunningApps) {
this.logger.debug(`Is already at our limit, no more resources available`)
Expand Down Expand Up @@ -807,7 +808,7 @@ interface AvailableAppInfo {
/** Whether the application can be spun up as a critical worker */
canRunInCriticalExpectationsOnlyMode: boolean
/** Some kind of value, how much it costs to run it, per minute */
cost: number
cost: Cost
}

interface RunningAppInfo {
Expand Down
11 changes: 6 additions & 5 deletions shared/packages/api/src/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ExpectedPackageStatusAPI } from '@sofie-automation/shared-lib/dist/pack
import { Expectation } from './expectationApi'
import { PackageContainerExpectation } from './packageContainerApi'
import {
Cost,
ReturnTypeDisposePackageContainerMonitors,
ReturnTypeDoYouSupportExpectation,
ReturnTypeDoYouSupportPackageContainer,
Expand Down Expand Up @@ -160,10 +161,10 @@ export namespace ExpectationManagerWorkerAgent {
}

export interface ExpectationCost {
/** Cost for working on the Expectation */
cost: number
/** Cost for working on the Expectation (null means "infinite cost") */
cost: Cost
/** Cost "in queue" until working on the Expectation can start */
startCost: number
startCost: Cost
reason: Reason
}
export type MessageFromWorker = (
Expand Down Expand Up @@ -234,10 +235,10 @@ export namespace WorkForceAppContainer {

requestAppTypeForExpectation: (
exp: Expectation.Any
) => Promise<{ success: true; appType: AppType; cost: number } | { success: false; reason: Reason }>
) => Promise<{ success: true; appType: AppType; cost: Cost } | { success: false; reason: Reason }>
requestAppTypeForPackageContainer: (
packageContainer: PackageContainerExpectation
) => Promise<{ success: true; appType: AppType; cost: number } | { success: false; reason: Reason }>
) => Promise<{ success: true; appType: AppType; cost: Cost } | { success: false; reason: Reason }>

spinUp: (appType: AppType) => Promise<AppId>
spinDown: (appId: AppId, reason: string) => Promise<void>
Expand Down
7 changes: 4 additions & 3 deletions shared/packages/api/src/statusReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
WorkerAgentId,
WorkInProgressLocalId,
} from './ids'
import { Cost } from './worker'

export interface WorkforceStatusReport {
workerAgents: WorkerStatusReport[]
Expand Down Expand Up @@ -50,7 +51,7 @@ export interface ExpectationManagerStatusReport {
id: WorkInProgressId
lastUpdated: number
workerId: WorkerAgentId
cost: number
cost: Cost
label: string
progress: number
expectationId: ExpectationId
Expand All @@ -65,8 +66,8 @@ export interface WorkerStatusReport {
}[]

currentJobs: {
cost: number
startCost: number
cost: Cost
startCost: Cost
cancelled: boolean
wipId: WorkInProgressLocalId
progress: number
Expand Down
10 changes: 9 additions & 1 deletion shared/packages/api/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export type ReturnTypeDoYouSupportExpectation =
reason: Reason
}
export type ReturnTypeGetCostFortExpectation = {
cost: number
/** (null means "infinite cost") */
cost: Cost
reason: Reason
}
export type ReturnTypeIsExpectationReadyToStartWorkingOn =
Expand Down Expand Up @@ -103,3 +104,10 @@ export type ReturnTypeSetupPackageContainerMonitors =
export interface MonitorProperties {
label: string
}

/** A numeric value representing the effort needed to work on something (null means "infinitely high cost"). */
export type Cost = number | null // Note: we're using null to represent infinity because Number.Infinity is not JSON-serializable
/** Converts Cost into a numeric value, that can be used to compare different costs to each other */
export function valueOfCost(cost: Cost): number {
return cost === null ? Number.POSITIVE_INFINITY : cost
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// eslint-disable-next-line node/no-extraneous-import
import { ExpectedPackageStatusAPI } from '@sofie-automation/shared-lib/dist/package-manager/package'
import { stringifyError } from '@sofie-package-manager/api'
import { valueOfCost, stringifyError } from '@sofie-package-manager/api'
import { expLabel } from '../../lib/trackedExpectation'
import { assertState, EvaluateContext } from '../lib'

Expand All @@ -23,7 +23,7 @@ export async function evaluateExpectationStateReady({
if (
trackedExp.session.assignedWorker &&
// Only allow starting if the job can start in a short while:
trackedExp.session.assignedWorker.cost.startCost > 0 // 2022-03-25: We're setting this to 0 to only allow one job per worker
valueOfCost(trackedExp.session.assignedWorker.cost.startCost) > 0 // 2022-03-25: We're setting this to 0 to only allow one job per worker
) {
trackedExp.session.noAssignedWorkerReason = {
user: `Workers are busy`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// eslint-disable-next-line node/no-extraneous-import
import { ExpectedPackageStatusAPI } from '@sofie-automation/shared-lib/dist/package-manager/package'
import {
Cost,
ExpectationManagerWorkerAgent,
LoggerInstance,
Reason,
Expand Down Expand Up @@ -161,7 +162,7 @@ export interface WorkInProgress {
trackedExp: TrackedExpectation
workerId: WorkerAgentId
worker: WorkerAgentAPI
cost: number
startCost: number
cost: Cost
startCost: Cost
lastUpdated: number
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PromisePool } from '@supercharge/promise-pool'
import { LoggerInstance, Reason, WorkerAgentId, stringifyError } from '@sofie-package-manager/api'
import { LoggerInstance, Reason, WorkerAgentId, valueOfCost, stringifyError } from '@sofie-package-manager/api'
import { ExpectationStateHandlerSession, WorkerAgentAssignment } from '../../lib/types'
import { WorkerAgentAPI } from '../../workerAgentApi'
import { ExpectationTracker } from '../../expectationTracker/expectationTracker'
Expand Down Expand Up @@ -123,7 +123,8 @@ export class TrackedWorkerAgents {
countQueried++
const cost = await workerAgent.api.getCostForExpectation(trackedExp.exp)

if (cost.cost < Number.POSITIVE_INFINITY) {
if (cost.cost !== null) {
// null means that the cost is "infinite"
workerCosts.push({
worker: workerAgent.api,
id: workerId,
Expand All @@ -147,8 +148,8 @@ export class TrackedWorkerAgents {

workerCosts.sort((a, b) => {
// Lowest cost first:
const aCost: number = a.cost.startCost + a.cost.cost
const bCost: number = b.cost.startCost + b.cost.cost
const aCost: number = valueOfCost(a.cost.startCost) + valueOfCost(a.cost.cost)
const bCost: number = valueOfCost(b.cost.startCost) + valueOfCost(b.cost.cost)
if (aCost > bCost) return 1
if (aCost < bCost) return -1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ReturnTypeGetCostFortExpectation,
PackageContainerId,
AccessorId,
Cost,
} from '@sofie-package-manager/api'
import { prioritizeAccessors } from '../../../lib/lib'
import { AccessorHandlerResultGeneric } from '../../../accessorHandlers/genericHandle'
Expand Down Expand Up @@ -161,20 +162,23 @@ export function findBestAccessorOnPackageContainer(
}
/** Return a standard cost for the various accessorHandler types */
export function getStandardCost(exp: Expectation.Any, worker: BaseWorker): ReturnTypeGetCostFortExpectation {
let sourceCost: number = Number.POSITIVE_INFINITY
// null means that the cost is "infinite"
let sourceCost: Cost
if (exp.startRequirement.sources.length > 0) {
const source = findBestPackageContainerWithAccessToPackage(worker, exp.startRequirement.sources)
sourceCost = source ? getAccessorCost(source.accessor.type) : Number.POSITIVE_INFINITY
sourceCost = source ? getAccessorCost(source.accessor.type) : null
} else {
// If there are no sources defined, there is no cost for the source
sourceCost = 0
}

const target = findBestPackageContainerWithAccessToPackage(worker, exp.endRequirement.targets)
const targetCost = target ? getAccessorCost(target.accessor.type) : Number.POSITIVE_INFINITY
const targetCost: Cost = target ? getAccessorCost(target.accessor.type) : null

const resultingCost: Cost = sourceCost !== null && targetCost !== null ? 30 * (sourceCost + targetCost) : null

return {
cost: 30 * (sourceCost + targetCost),
cost: resultingCost,
reason: {
user: `Source cost: ${sourceCost}, Target cost: ${targetCost}`,
tech: `Source cost: ${sourceCost}, Target cost: ${targetCost}`,
Expand Down
17 changes: 12 additions & 5 deletions shared/packages/worker/src/workerAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
isProtectedString,
WorkInProgressLocalId,
objectEntries,
Cost,
} from '@sofie-package-manager/api'

import { AppContainerAPI } from './appContainerApi'
Expand Down Expand Up @@ -379,7 +380,7 @@ export class WorkerAgent {
this.spinDownTime = spinDownTime
this.setupIntervalCheck()
}
private getStartCost(exp: Expectation.Any): { cost: number; jobCount: number } {
private getStartCost(exp: Expectation.Any): { cost: Cost; jobCount: number } {
const workerMultiplier: number = this.config.worker.costMultiplier || 1

let systemStartCost = 0
Expand All @@ -391,11 +392,17 @@ export class WorkerAgent {
}
}
}
let resultingCost: Cost = systemStartCost
for (const job of this.currentJobs) {
// null means that the cost is "infinite"
if (resultingCost === null) break

if (job.cost.cost === null) resultingCost = null
else resultingCost += job.cost.cost * (1 - job.progress) * workerMultiplier
}

return {
cost:
(this.currentJobs.reduce((sum, job) => sum + job.cost.cost * (1 - job.progress), 0) + systemStartCost) *
workerMultiplier,
cost: resultingCost,
jobCount: this.currentJobs.length,
}
}
Expand Down Expand Up @@ -627,7 +634,7 @@ export class WorkerAgent {
const startCost = this.getStartCost(exp)

return {
cost: costForExpectation.cost * workerMultiplier,
cost: costForExpectation.cost !== null ? costForExpectation.cost * workerMultiplier : null,
reason: {
user: costForExpectation.reason.user,
tech: `Cost: ${costForExpectation.reason.tech}, multiplier: ${workerMultiplier}, jobCount: ${startCost.jobCount}`,
Expand Down
5 changes: 3 additions & 2 deletions shared/packages/workforce/src/appContainerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
WorkforceId,
AppType,
AppId,
Cost,
} from '@sofie-package-manager/api'

/**
Expand Down Expand Up @@ -40,12 +41,12 @@ export class AppContainerAPI

async requestAppTypeForExpectation(
exp: Expectation.Any
): Promise<{ success: true; appType: AppType; cost: number } | { success: false; reason: Reason }> {
): Promise<{ success: true; appType: AppType; cost: Cost } | { success: false; reason: Reason }> {
return this._sendMessage('requestAppTypeForExpectation', exp)
}
async requestAppTypeForPackageContainer(
packageContainer: PackageContainerExpectation
): Promise<{ success: true; appType: AppType; cost: number } | { success: false; reason: Reason }> {
): Promise<{ success: true; appType: AppType; cost: Cost } | { success: false; reason: Reason }> {
return this._sendMessage('requestAppTypeForPackageContainer', packageContainer)
}
async spinUp(appType: AppType): Promise<AppId> {
Expand Down
10 changes: 6 additions & 4 deletions shared/packages/workforce/src/workerHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
PackageContainerExpectation,
AppContainerId,
AppType,
Cost,
valueOfCost,
} from '@sofie-package-manager/api'
import { Workforce } from './workforce'

Expand All @@ -21,12 +23,12 @@ export class WorkerHandler {
this.logger.debug(`Workforce: Got request for resources for exp "${exp.id}"`)

let errorReason = `No AppContainers registered`
let best: { appContainerId: AppContainerId; appType: AppType; cost: number } | null = null
let best: { appContainerId: AppContainerId; appType: AppType; cost: Cost } | null = null
for (const [appContainerId, appContainer] of this.workForce.appContainers.entries()) {
this.logger.debug(`Workforce: Asking appContainer "${appContainerId}"`)
const proposal = await appContainer.api.requestAppTypeForExpectation(exp)
if (proposal.success) {
if (!best || proposal.cost < best.cost) {
if (!best || valueOfCost(proposal.cost) < valueOfCost(best.cost)) {
best = {
appContainerId: appContainerId,
appType: proposal.appType,
Expand Down Expand Up @@ -57,12 +59,12 @@ export class WorkerHandler {
this.logger.debug(`Workforce: Got request for resources for packageContainer "${packageContainer.id}"`)

let errorReason = `No AppContainers registered`
let best: { appContainerId: AppContainerId; appType: AppType; cost: number } | null = null
let best: { appContainerId: AppContainerId; appType: AppType; cost: Cost } | null = null
for (const [appContainerId, appContainer] of this.workForce.appContainers.entries()) {
this.logger.debug(`Workforce: Asking appContainer "${appContainerId}"`)
const proposal = await appContainer.api.requestAppTypeForPackageContainer(packageContainer)
if (proposal.success) {
if (!best || proposal.cost < best.cost) {
if (!best || valueOfCost(proposal.cost) < valueOfCost(best.cost)) {
best = {
appContainerId: appContainerId,
appType: proposal.appType,
Expand Down

0 comments on commit 22bea72

Please sign in to comment.