Skip to content

Commit

Permalink
fix: adapt to changed runner update messages
Browse files Browse the repository at this point in the history
  • Loading branch information
bompo committed Dec 5, 2024
1 parent 5315a8e commit ae8403c
Showing 1 changed file with 56 additions and 32 deletions.
88 changes: 56 additions & 32 deletions src/lib/ProgramStateConnection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AxiosError } from "axios"
import { makeAutoObservable, runInAction } from "mobx"
import type { NovaClient } from "../NovaClient"
import { AutoReconnectingWebsocket } from "./AutoReconnectingWebsocket"
import { tryParseJson } from "./converters"
Expand All @@ -25,10 +26,13 @@ export type CurrentProgram = {
}

type ProgramStateMessage = {
id: string
state: ProgramState
start_time?: number | null
execution_time?: number | null
type: string
runner: {
id: string
state: ProgramState
start_time?: number | null
execution_time?: number | null
}
}

/**
Expand All @@ -45,29 +49,36 @@ export class ProgramStateConnection {
programStateSocket: AutoReconnectingWebsocket

constructor(readonly nova: NovaClient) {
makeAutoObservable(this, {}, { autoBind: true })

this.programStateSocket = nova.openReconnectingWebsocket(`/programs/state`)

this.log("Program state connection established")
this.programStateSocket.addEventListener("message", (ev) => {
this.log(ev.data)
const msg = tryParseJson(ev.data)

if (!msg) {
console.error("Failed to parse program state message", ev.data)
return
}

this.handleProgramStateMessage(msg)
})
}

/** Handle a program state update from the backend */
async handleProgramStateMessage(msg: ProgramStateMessage) {
const { runner } = msg
this.log(runner.id)
// Ignoring other programs for now
// TODO - show if execution state is busy from another source
if (msg.id !== this.currentlyExecutingProgramRunnerId) return
if (runner.id !== this.currentlyExecutingProgramRunnerId) return

if (msg.state === ProgramState.Failed) {
if (runner.state === ProgramState.Failed) {
try {
const runnerState = await this.nova.api.program.getProgramRunner(msg.id)
const runnerState = await this.nova.api.program.getProgramRunner(
runner.id,
)

// TODO - wandelengine should send print statements in real time over
// websocket as well, rather than at the end
Expand All @@ -76,62 +87,66 @@ export class ProgramStateConnection {
this.log(stdout)
}
this.logError(
`Program runner ${msg.id} failed with error: ${runnerState.error}\n${runnerState.traceback}`,
`Program runner ${runner.id} failed with error: ${runnerState.error}\n${runnerState.traceback}`,
)
} catch (err) {
this.logError(
`Failed to retrieve results for program ${msg.id}: ${err}`,
`Failed to retrieve results for program ${runner.id}: ${err}`,
)
}

this.currentProgram.state = ProgramState.Failed

this.gotoIdleState()
} else if (msg.state === ProgramState.Stopped) {
} else if (runner.state === ProgramState.Stopped) {
try {
const runnerState = await this.nova.api.program.getProgramRunner(msg.id)
const runnerState = await this.nova.api.program.getProgramRunner(
runner.id,
)

const stdout = (runnerState as any).stdout
if (stdout) {
this.log(stdout)
}

this.currentProgram.state = ProgramState.Stopped
this.log(`Program runner ${msg.id} stopped`)
this.log(`Program runner ${runner.id} stopped`)
} catch (err) {
this.logError(
`Failed to retrieve results for program ${msg.id}: ${err}`,
`Failed to retrieve results for program ${runner.id}: ${err}`,
)
}

this.gotoIdleState()
} else if (msg.state === ProgramState.Completed) {
} else if (runner.state === ProgramState.Completed) {
try {
const runnerState = await this.nova.api.program.getProgramRunner(msg.id)
const runnerState = await this.nova.api.program.getProgramRunner(
runner.id,
)

const stdout = (runnerState as any).stdout
if (stdout) {
this.log(stdout)
}
this.log(
`Program runner ${msg.id} finished successfully in ${msg.execution_time?.toFixed(2)} seconds`,
`Program runner ${runner.id} finished successfully in ${runner.execution_time?.toFixed(2)} seconds`,
)

this.currentProgram.state = ProgramState.Completed
} catch (err) {
this.logError(
`Failed to retrieve results for program ${msg.id}: ${err}`,
`Failed to retrieve results for program ${runner.id}: ${err}`,
)
}

this.gotoIdleState()
} else if (msg.state === ProgramState.Running) {
} else if (runner.state === ProgramState.Running) {
this.currentProgram.state = ProgramState.Running
this.log(`Program runner ${msg.id} now running`)
} else if (msg.state !== ProgramState.NotStarted) {
console.error(msg)
this.log(`Program runner ${runner.id} now running`)
} else if (runner.state !== ProgramState.NotStarted) {
console.error(runner)
this.logError(
`Program runner ${msg.id} entered unexpected state: ${msg.state}`,
`Program runner ${runner.id} entered unexpected state: ${runner.state}`,
)
this.currentProgram.state = ProgramState.NotStarted
this.gotoIdleState()
Expand All @@ -140,7 +155,9 @@ export class ProgramStateConnection {

/** Call when a program is no longer executing */
gotoIdleState() {
this.executionState = "idle"
runInAction(() => {
this.executionState = "idle"
})
this.currentlyExecutingProgramRunnerId = null
}

Expand All @@ -156,8 +173,9 @@ export class ProgramStateConnection {

const { currentProgram: openProgram } = this
if (!openProgram) return

this.executionState = "starting"
runInAction(() => {
this.executionState = "starting"
})

// Jogging can cause program execution to fail for some time after
// So we need to explicitly stop jogging before running a program
Expand Down Expand Up @@ -189,8 +207,9 @@ export class ProgramStateConnection {
)

this.log(`Created program runner ${programRunnerRef.id}"`)

this.executionState = "executing"
runInAction(() => {
this.executionState = "executing"
})
this.currentlyExecutingProgramRunnerId = programRunnerRef.id
} catch (error) {
if (error instanceof AxiosError && error.response && error.request) {
Expand All @@ -200,22 +219,27 @@ export class ProgramStateConnection {
} else {
this.logError(JSON.stringify(error))
}
this.executionState = "idle"
runInAction(() => {
this.executionState = "idle"
})
}
}

async stopProgram() {
if (!this.currentlyExecutingProgramRunnerId) return

this.executionState = "stopping"
runInAction(() => {
this.executionState = "stopping"
})

try {
await this.nova.api.program.stopProgramRunner(
this.currentlyExecutingProgramRunnerId,
)
} catch (err) {
// Reactivate the stop button so user can try again
this.executionState = "executing"
runInAction(() => {
this.executionState = "executing"
})
throw err
}
}
Expand Down

0 comments on commit ae8403c

Please sign in to comment.