retool QemuVM to use the process launcher interface

Relatively easy since we basically comform to a subset of execa anyways.
This commit is contained in:
Lily Tsuru 2024-11-02 03:00:51 -04:00
parent d19f649a55
commit adcefd76ef
3 changed files with 60 additions and 34 deletions

View file

@ -1,44 +1,52 @@
// Default process implementation. // Default process implementation.
// This uses execa like the current code, but conforms to our abstration :) // This uses execa like the previous code, but conforms to our abstration :)
import EventEmitter from "events";
import { IProcess, IProcessLauncher, ProcessLaunchOptions } from "./ProcessInterface";
import { execaCommand } from "execa";
import { Readable, Writable } from "stream";
import EventEmitter from 'events';
import { IProcess, IProcessLauncher, ProcessLaunchOptions } from './ProcessInterface';
import { execaCommand } from 'execa';
import { Readable, Writable } from 'stream';
class DefaultProcess extends EventEmitter implements IProcess { class DefaultProcess extends EventEmitter implements IProcess {
private process; private process;
stdin: Writable | null = null; stdin: Writable | null = null;
stdout: Readable | null = null; stdout: Readable | null = null;
stderr: Readable | null = null; stderr: Readable | null = null;
constructor(command: string, opts?: ProcessLaunchOptions) { constructor(command: string, opts?: ProcessLaunchOptions) {
super(); super();
this.process = execaCommand(command, opts); this.process = execaCommand(command, opts);
this.stdin = this.process.stdin; this.stdin = this.process.stdin;
this.stdout = this.process.stdout; this.stdout = this.process.stdout;
this.stderr = this.process.stderr; this.stderr = this.process.stderr;
let self = this; let self = this;
this.process.on('spawn', () => { this.process.on('spawn', () => {
self.emit('spawn'); self.emit('spawn');
}); });
this.process.on('exit', (code) => { this.process.on('exit', (code) => {
self.emit('exit', code); self.emit('exit', code);
}); });
} }
kill(signal?: number | NodeJS.Signals): boolean { kill(signal?: number | NodeJS.Signals): boolean {
return this.process.kill(signal); return this.process.kill(signal);
} }
dispose(): void {
this.stdin = null;
this.stdout = null;
this.stderr = null;
this.process.removeAllListeners();
this.removeAllListeners();
}
} }
export class DefaultProcessLauncher implements IProcessLauncher { export class DefaultProcessLauncher implements IProcessLauncher {
launch(command: string, opts?: ProcessLaunchOptions | undefined): IProcess { launch(command: string, opts?: ProcessLaunchOptions | undefined): IProcess {
return new DefaultProcess(command, opts); return new DefaultProcess(command, opts);
} }
} }

View file

@ -28,6 +28,7 @@ export interface IProcess extends EventEmitter {
kill(signal?: number | NodeJS.Signals): boolean; kill(signal?: number | NodeJS.Signals): boolean;
dispose(): void;
} }
// Launches a processs. // Launches a processs.

View file

@ -5,6 +5,8 @@ import { unlink } from 'node:fs/promises';
import pino from 'pino'; import pino from 'pino';
import { Readable, Writable } from 'stream'; import { Readable, Writable } from 'stream';
import { IProcess, IProcessLauncher } from './ProcessInterface.js';
import { DefaultProcessLauncher } from './DefaultProcess.js';
export enum VMState { export enum VMState {
Stopped, Stopped,
@ -68,7 +70,8 @@ export class QemuVM extends EventEmitter {
// QMP stuff. // QMP stuff.
private qmpInstance: QmpClient = new QmpClient(); private qmpInstance: QmpClient = new QmpClient();
private qemuProcess: ExecaChildProcess | null = null; private qemuProcess: IProcess | null = null;
private qemuLauncher: IProcessLauncher;
private displayInfo: QemuVMDisplayInfo | null = null; private displayInfo: QemuVMDisplayInfo | null = null;
private definition: QemuVmDefinition; private definition: QemuVmDefinition;
@ -76,13 +79,23 @@ export class QemuVM extends EventEmitter {
private logger: pino.Logger; private logger: pino.Logger;
constructor(def: QemuVmDefinition) { constructor(def: QemuVmDefinition, processLauncher?: IProcessLauncher) {
super(); super();
this.definition = def; this.definition = def;
this.logger = pino({ this.logger = pino({
name: `SuperQEMU.QemuVM/${this.definition.id}` name: `SuperQEMU.QemuVM/${this.definition.id}`
}); });
// Fall back to the default process launcher. This is
// done so we can have our cake (compatibility) and eat it too
// (do this fun process abstraction stuff for whatever really)
if(!processLauncher) {
this.logger.warn('Using default process launcher. If this is not desired review your code to make sure the launcher is passed properly!');
this.qemuLauncher = new DefaultProcessLauncher();
} else {
this.qemuLauncher = processLauncher;
}
let self = this; let self = this;
// Handle the STOP event sent when using -no-shutdown // Handle the STOP event sent when using -no-shutdown
@ -233,7 +246,7 @@ export class QemuVM extends EventEmitter {
this.logger.info(`Starting QEMU with command \"${split}\"`); this.logger.info(`Starting QEMU with command \"${split}\"`);
// Start QEMU // Start QEMU
this.qemuProcess = execaCommand(split, { this.qemuProcess = this.qemuLauncher.launch(split, {
stdin: 'pipe', stdin: 'pipe',
stdout: 'pipe', stdout: 'pipe',
stderr: 'pipe' stderr: 'pipe'
@ -251,6 +264,10 @@ export class QemuVM extends EventEmitter {
this.qemuProcess.on('exit', async (code) => { this.qemuProcess.on('exit', async (code) => {
self.logger.info('QEMU process exited'); self.logger.info('QEMU process exited');
// Dispose events. StartQemu() will assign them again to the new process.
// A bit mucky but /shrug.
self.qemuProcess?.dispose();
self.qmpInstance.reset(); self.qmpInstance.reset();
self.qmpInstance.setWriter(null); self.qmpInstance.setWriter(null);