mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-04 14:51:37 -05:00
Large nests tie up the workstation that runs them. In Database mode, Auto Nest can now queue the job on the nest server, where a worker container solves one job at a time under its own CPU and memory limits. The job has no time limit, so a throttled worker finds the same nest a fast one does, only later. - Server: /api/nest-jobs queue (schema v15). The worker holds a job on a heartbeat lease. When the lease lapses, the job goes back to the queue, or fails after three attempts. Only one job runs at a time. Packages and results are opaque to the HTTP server, which stays off Engine and IO. - Worker: OpenNest.Server.Worker in the same image (OPENNEST_ROLE=worker), reaching the server over HTTP only. It records each engine answer with RecordingNestingEngine and gives its job back on shutdown. - Desktop: "Nest on the server" in the Auto Nest dialog, for built-in engines only. The client replays the recorded answers with RemoteNestSubmission, so validation, the review and Keep run locally against the client's own drawings, exactly as after a local run. A drawing edited after sending refuses the result. If the server cannot take the job, the user may nest locally instead. - compose.server.yaml adds the throttled worker service. The Dockerfile, .dockerignore, CI paths and the deploy script now include the worker's projects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
45 lines
1.9 KiB
C#
45 lines
1.9 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
|
|
namespace OpenNest.Engine.Jobs.Remote;
|
|
|
|
/// <summary>
|
|
/// Answers each solve with the next recorded result of a <see cref="NestSolveTranscript"/>,
|
|
/// after checking it is asked the same job the recording engine was. A different question
|
|
/// (the drawings no longer bundle or recover the same way) or one too many fails the replay
|
|
/// with <see cref="NestReplayMismatchException"/>; nothing is ever solved here.
|
|
/// </summary>
|
|
internal sealed class ReplayNestingEngine : INestingEngine
|
|
{
|
|
private readonly NestSolveTranscript transcript;
|
|
private int next;
|
|
|
|
public ReplayNestingEngine(NestSolveTranscript transcript)
|
|
{
|
|
this.transcript = transcript ?? throw new ArgumentNullException(nameof(transcript));
|
|
}
|
|
|
|
/// <summary>True once every recorded solve has been replayed.</summary>
|
|
public bool Finished => next == transcript.Solves.Count;
|
|
|
|
public NestJobResult Solve(NestJob job, IProgress<NestJobProgress> progress = null, CancellationToken token = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(job);
|
|
token.ThrowIfCancellationRequested();
|
|
if (next >= transcript.Solves.Count)
|
|
throw new NestReplayMismatchException("The nest needed more engine runs here than it did on the server.");
|
|
var solve = transcript.Solves[next++];
|
|
if (solve.Request == null
|
|
|| !solve.Request.SequenceEqual(NestSolveTranscript.Fingerprint(job), StringComparer.Ordinal))
|
|
throw new NestReplayMismatchException("The nest asked the engine a different question here than it did on the server.");
|
|
return solve.Result.ToResult(job);
|
|
}
|
|
}
|
|
|
|
/// <summary>The server's result cannot be replayed against the drawings and stock here.</summary>
|
|
public sealed class NestReplayMismatchException : InvalidOperationException
|
|
{
|
|
public NestReplayMismatchException(string message) : base(message) { }
|
|
}
|