Refactor cancelSubJob method in PrinterClient for improved error handling and state management
All checks were successful
farmcontrol/farmcontrol-server/pipeline/head This commit looks good

- Updated the cancelSubJob method to accept a subJob object instead of an ID, enhancing clarity.
- Added comprehensive error handling for various failure scenarios, including connection status and missing job IDs.
- Implemented state checks to ensure only queued sub jobs can be canceled, improving robustness.
- Enhanced logging for better visibility into the cancellation process and outcomes.
- Updated PrinterManager to handle the new cancelSubJob action, integrating it into the existing command structure.
This commit is contained in:
Tom Butcher 2026-11-08 23:59:53 +00:00
parent 11ebc0d66c
commit 78a246726f
2 changed files with 72 additions and 17 deletions

View File

@ -1363,25 +1363,76 @@ export class PrinterClient {
logger.info("Sub job deployed to printer:", this.id);
}
async cancelSubJob(subJobId) {
async cancelSubJob(subJob) {
const subJobId = refId(subJob);
logger.info(`Cancelling sub job ${subJobId} for ${this.printer.name}`);
if (!this.isOnline) {
logger.error(
`Cannot cancel sub job: Not connected to Moonraker (${this.printer.name})`,
);
return false;
}
if (!subJobId) {
logger.error("Cannot cancel sub job: missing sub job id");
return false;
}
return this._runQueueMutation(async () => {
try {
this.subJobCancelId = subJobId;
const record = await this.socketClient.getObject({
objectType: "subJob",
_id: subJobId,
});
const moonrakerJobId = record?.moonrakerJobId;
if (!moonrakerJobId) {
logger.error(
`Cannot cancel sub job ${subJobId}: missing moonraker job id`,
);
return false;
}
if (record?.state?.type !== "queued") {
logger.error(
`Cannot cancel sub job ${subJobId}: state is ${record?.state?.type}`,
);
return false;
}
this.subJobCancelId = moonrakerJobId;
const result = await this.sendPrinterCommand({
method: "server.job_queue.delete_job",
params: {
job_ids: [subJobId],
job_ids: [moonrakerJobId],
},
});
if (!result) {
throw new Error("Failed to cancel sub job");
}
this.subJobCancelId = null;
logger.info("Sub job canceled:", subJobId);
await this.database.updateSubJobState(subJobId, { type: "cancelled" });
await this.database.setSubJobFinishedAt(subJobId, new Date());
const jobId = refId(record.job);
if (jobId) {
await this.database.updateJobState(jobId);
}
const printer = await this.database.getPrinter();
const queue = Array.isArray(printer?.queue) ? printer.queue : [];
const remaining = queue.filter((item) => refId(item) !== subJobId);
await this.database.setQueuedSubJobs(remaining);
this.queue = remaining;
logger.info(`Successfully cancelled sub job ${subJobId}`);
return true;
} catch (error) {
logger.error(`Error canceling sub job ${subJobId}:`, error);
return false;
} finally {
this.subJobCancelId = null;
}
});
}
async setTemperature(temperature) {

View File

@ -170,6 +170,10 @@ export class PrinterManager {
const cancelJobResult = await printer.cancelJob();
callback(cancelJobResult);
return;
case "cancelSubJob":
const cancelSubJobResult = await printer.cancelSubJob(action.data);
callback(cancelSubJobResult);
return;
case "unloadFilamentStock":
const unloadFilamentStockResult = await printer.unloadFilamentStock();
callback(unloadFilamentStockResult);