* Unified handle to a background process. * Uses runtime.exec for all operations, working identically for local and SSH. * * Output files (output.log, exit_code) are on the runtime's filesystem. * This handle provides lifecycle management via execBuffered commands.
| 214 | * This handle provides lifecycle management via execBuffered commands. |
| 215 | */ |
| 216 | class RuntimeBackgroundHandle implements BackgroundHandle { |
| 217 | private terminated = false; |
| 218 | |
| 219 | constructor( |
| 220 | private readonly runtime: Runtime, |
| 221 | private readonly pid: number, |
| 222 | public readonly outputDir: string, |
| 223 | private readonly quotePath: (p: string) => string |
| 224 | ) {} |
| 225 | |
| 226 | /** |
| 227 | * Get the exit code from the exit_code file. |
| 228 | * Returns null if process is still running (file doesn't exist yet). |
| 229 | */ |
| 230 | async getExitCode(): Promise<number | null> { |
| 231 | try { |
| 232 | const exitCodePath = this.quotePath(`${this.outputDir}/${EXIT_CODE_FILENAME}`); |
| 233 | const result = await execBuffered( |
| 234 | this.runtime, |
| 235 | `cat ${exitCodePath} 2>/dev/null || echo ""`, |
| 236 | { cwd: FALLBACK_CWD, timeout: 10 } |
| 237 | ); |
| 238 | return parseExitCode(result.stdout); |
| 239 | } catch (error) { |
| 240 | log.debug(`RuntimeBackgroundHandle.getExitCode: Error: ${errorMsg(error)}`); |
| 241 | return null; |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | /** |
| 246 | * Terminate the process group. |
| 247 | * Sends SIGTERM to process group, waits briefly, then SIGKILL if still running. |
| 248 | */ |
| 249 | async terminate(): Promise<void> { |
| 250 | if (this.terminated) return; |
| 251 | |
| 252 | try { |
| 253 | const exitCodePath = `${this.outputDir}/${EXIT_CODE_FILENAME}`; |
| 254 | const terminateCmd = buildTerminateCommand(this.pid, exitCodePath, this.quotePath); |
| 255 | await execBuffered(this.runtime, terminateCmd, { |
| 256 | cwd: FALLBACK_CWD, |
| 257 | timeout: 15, |
| 258 | }); |
| 259 | log.debug(`RuntimeBackgroundHandle: Terminated process group ${this.pid}`); |
| 260 | } catch (error) { |
| 261 | // Process may already be dead - that's fine |
| 262 | log.debug(`RuntimeBackgroundHandle.terminate: Error: ${errorMsg(error)}`); |
| 263 | } |
| 264 | |
| 265 | this.terminated = true; |
| 266 | } |
| 267 | |
| 268 | /** |
| 269 | * Clean up resources. |
| 270 | * No resources to clean - process runs independently via nohup. |
| 271 | */ |
| 272 | async dispose(): Promise<void> { |
| 273 | // No resources to clean up |
nothing calls this directly
no outgoing calls
no test coverage detected