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:
parent
d19f649a55
commit
adcefd76ef
3 changed files with 60 additions and 34 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue