refactor(agent): create dedicated jobs for recurring tasks

This commit is contained in:
Nicolas Meienberger 2026-05-09 11:00:27 +02:00
parent 90914cf08b
commit e2498ff638
No known key found for this signature in database
5 changed files with 60 additions and 17 deletions

View file

@ -23,7 +23,6 @@ import {
getStatFs, getStatFs,
getVolumePath, getVolumePath,
type AgentVolume, type AgentVolume,
type BackendConfig as HostBackendConfig,
} from "../../../../apps/agent/src/volume-host"; } from "../../../../apps/agent/src/volume-host";
import { import {
browseFilesystem as browseHostFilesystem, browseFilesystem as browseHostFilesystem,
@ -72,7 +71,7 @@ const volumeForAgent = async (volume: Volume): Promise<Volume> => ({
const volumeForHost = async (volume: Volume): Promise<AgentVolume> => ({ const volumeForHost = async (volume: Volume): Promise<AgentVolume> => ({
...volume, ...volume,
shortId: volume.shortId, shortId: volume.shortId,
config: (await decryptVolumeConfig(volume.config)) as HostBackendConfig, config: await decryptVolumeConfig(volume.config),
provisioningId: volume.provisioningId ?? null, provisioningId: volume.provisioningId ?? null,
}); });
@ -290,7 +289,7 @@ const updateVolume = async (shortId: ShortId, volumeData: UpdateVolumeBody) => {
const testConnection = async (backendConfig: BackendConfig) => { const testConnection = async (backendConfig: BackendConfig) => {
if (!config.flags.enableLocalAgent) { if (!config.flags.enableLocalAgent) {
return Effect.runPromise(testVolumeConnection(backendConfig as HostBackendConfig)); return Effect.runPromise(testVolumeConnection(backendConfig));
} }
const command = await runVolumeCommand(LOCAL_AGENT_ID, { name: "volume.testConnection", backendConfig }); const command = await runVolumeCommand(LOCAL_AGENT_ID, { name: "volume.testConnection", backendConfig });

View file

@ -1,31 +1,24 @@
import { logger } from "@zerobyte/core/node"; import { logger } from "@zerobyte/core/node";
import { Fiber } from "effect";
import { createControllerSession, type ControllerSession } from "./controller-session"; import { createControllerSession, type ControllerSession } from "./controller-session";
import { cleanupDanglingVolumeMountDirectories } from "./volume-host/cleanup"; import { startAgentJobs } from "./jobs";
const controllerUrl = process.env.ZEROBYTE_CONTROLLER_URL; const controllerUrl = process.env.ZEROBYTE_CONTROLLER_URL;
const agentToken = process.env.ZEROBYTE_AGENT_TOKEN; const agentToken = process.env.ZEROBYTE_AGENT_TOKEN;
const RECONNECT_DELAY_MS = 1000; const RECONNECT_DELAY_MS = 1000;
const VOLUME_CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
export class Agent { export class Agent {
private ws: WebSocket | null = null; private ws: WebSocket | null = null;
private controllerSession: ControllerSession | null = null; private controllerSession: ControllerSession | null = null;
private reconnectTimeout: ReturnType<typeof setTimeout> | null = null; private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
private volumeCleanupInterval: ReturnType<typeof setInterval> | null = null; private jobFibers: Fiber.RuntimeFiber<never, never>[] | null = null;
private runVolumeCleanup() { private startJobs() {
void cleanupDanglingVolumeMountDirectories().catch((error) => { if (this.jobFibers) {
logger.warn(`Agent volume cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
});
}
private startVolumeCleanup() {
if (this.volumeCleanupInterval) {
return; return;
} }
this.runVolumeCleanup(); this.jobFibers = startAgentJobs();
this.volumeCleanupInterval = setInterval(() => this.runVolumeCleanup(), VOLUME_CLEANUP_INTERVAL_MS);
} }
private scheduleReconnect() { private scheduleReconnect() {
@ -40,7 +33,7 @@ export class Agent {
} }
connect() { connect() {
this.startVolumeCleanup(); this.startJobs();
if (this.reconnectTimeout) { if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout); clearTimeout(this.reconnectTimeout);

View file

@ -0,0 +1,21 @@
import { logger } from "@zerobyte/core/node";
import { toMessage } from "@zerobyte/core/utils";
import { Effect } from "effect";
import { cleanupDanglingVolumeMountDirectories } from "../volume-host/cleanup";
import type { AgentJob } from "./types";
const VOLUME_CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
export const CleanupDanglingVolumeMountsJob: AgentJob = {
name: "cleanup-dangling-volume-mounts",
intervalMs: VOLUME_CLEANUP_INTERVAL_MS,
run: () =>
Effect.tryPromise({
try: cleanupDanglingVolumeMountDirectories,
catch: (error) => error,
}).pipe(
Effect.catchAll((error) =>
Effect.sync(() => logger.warn(`Agent volume cleanup failed: ${toMessage(error)}`)),
),
),
};

View file

@ -0,0 +1,23 @@
import { logger } from "@zerobyte/core/node";
import { Effect, Fiber } from "effect";
import { CleanupDanglingVolumeMountsJob } from "./cleanup-dangling";
import type { AgentJob } from "./types";
const agentJobs = [CleanupDanglingVolumeMountsJob];
export const startAgentJobs = (jobs: readonly AgentJob[] = agentJobs) => {
return jobs.map((job) =>
Effect.runFork(
Effect.forever(
Effect.gen(function* () {
yield* job.run();
yield* Effect.sleep(job.intervalMs);
}),
).pipe(Effect.ensuring(logger.effect.debug(`Agent job stopped: ${job.name}`))),
),
);
};
export const stopAgentJobs = (fibers: readonly Fiber.RuntimeFiber<never, never>[]) => {
return Effect.forEach(fibers, (fiber) => Fiber.interrupt(fiber), { discard: true });
};

View file

@ -0,0 +1,7 @@
import type { Effect } from "effect";
export type AgentJob = {
name: string;
intervalMs: number;
run: () => Effect.Effect<void, never, never>;
};