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.
// This uses execa like the current 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";
// 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';
class DefaultProcess extends EventEmitter implements IProcess {
private process;
stdin: Writable | null = null;
stdout: Readable | null = null;
stderr: Readable | null = null;
private process;
stdin: Writable | null = null;
stdout: Readable | null = null;
stderr: Readable | null = null;
constructor(command: string, opts?: ProcessLaunchOptions) {
super();
constructor(command: string, opts?: ProcessLaunchOptions) {
super();
this.process = execaCommand(command, opts);
this.process = execaCommand(command, opts);
this.stdin = this.process.stdin;
this.stdout = this.process.stdout;
this.stderr = this.process.stderr;
this.stdin = this.process.stdin;
this.stdout = this.process.stdout;
this.stderr = this.process.stderr;
let self = this;
this.process.on('spawn', () => {
self.emit('spawn');
});
let self = this;
this.process.on('spawn', () => {
self.emit('spawn');
});
this.process.on('exit', (code) => {
self.emit('exit', code);
});
}
this.process.on('exit', (code) => {
self.emit('exit', code);
});
}
kill(signal?: number | NodeJS.Signals): boolean {
return this.process.kill(signal);
}
kill(signal?: number | NodeJS.Signals): boolean {
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 {
launch(command: string, opts?: ProcessLaunchOptions | undefined): IProcess {
return new DefaultProcess(command, opts);
}
launch(command: string, opts?: ProcessLaunchOptions | undefined): IProcess {
return new DefaultProcess(command, opts);
}
}

View file

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

View file

@ -5,6 +5,8 @@ import { unlink } from 'node:fs/promises';
import pino from 'pino';
import { Readable, Writable } from 'stream';
import { IProcess, IProcessLauncher } from './ProcessInterface.js';
import { DefaultProcessLauncher } from './DefaultProcess.js';
export enum VMState {
Stopped,
@ -68,7 +70,8 @@ export class QemuVM extends EventEmitter {
// QMP stuff.
private qmpInstance: QmpClient = new QmpClient();
private qemuProcess: ExecaChildProcess | null = null;
private qemuProcess: IProcess | null = null;
private qemuLauncher: IProcessLauncher;
private displayInfo: QemuVMDisplayInfo | null = null;
private definition: QemuVmDefinition;
@ -76,13 +79,23 @@ export class QemuVM extends EventEmitter {
private logger: pino.Logger;
constructor(def: QemuVmDefinition) {
constructor(def: QemuVmDefinition, processLauncher?: IProcessLauncher) {
super();
this.definition = def;
this.logger = pino({
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;
// 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}\"`);
// Start QEMU
this.qemuProcess = execaCommand(split, {
this.qemuProcess = this.qemuLauncher.launch(split, {
stdin: 'pipe',
stdout: 'pipe',
stderr: 'pipe'
@ -251,6 +264,10 @@ export class QemuVM extends EventEmitter {
this.qemuProcess.on('exit', async (code) => {
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.setWriter(null);