diff --git a/apps/orchestrator/src/spawner/docker-sandbox.service.spec.ts b/apps/orchestrator/src/spawner/docker-sandbox.service.spec.ts index bd68184..8e1593e 100644 --- a/apps/orchestrator/src/spawner/docker-sandbox.service.spec.ts +++ b/apps/orchestrator/src/spawner/docker-sandbox.service.spec.ts @@ -162,6 +162,42 @@ describe("DockerSandboxService", () => { ); }); + it("should include a random suffix in container name for uniqueness", async () => { + const agentId = "agent-123"; + const taskId = "task-456"; + const workspacePath = "/workspace/agent-123"; + + await service.createContainer(agentId, taskId, workspacePath); + + const callArgs = (mockDocker.createContainer as ReturnType).mock + .calls[0][0] as Docker.ContainerCreateOptions; + const containerName = callArgs.name as string; + + // Name format: mosaic-agent-{agentId}-{timestamp}-{8 hex chars} + expect(containerName).toMatch(/^mosaic-agent-agent-123-\d+-[0-9a-f]{8}$/); + }); + + it("should generate unique container names across rapid successive calls", async () => { + const agentId = "agent-123"; + const taskId = "task-456"; + const workspacePath = "/workspace/agent-123"; + const containerNames = new Set(); + + // Spawn multiple containers rapidly to test for collisions + for (let i = 0; i < 20; i++) { + await service.createContainer(agentId, taskId, workspacePath); + } + + const calls = (mockDocker.createContainer as ReturnType).mock.calls; + for (const call of calls) { + const args = call[0] as Docker.ContainerCreateOptions; + containerNames.add(args.name as string); + } + + // All 20 names must be unique (no collisions) + expect(containerNames.size).toBe(20); + }); + it("should throw error if container creation fails", async () => { const agentId = "agent-123"; const taskId = "task-456"; diff --git a/apps/orchestrator/src/spawner/docker-sandbox.service.ts b/apps/orchestrator/src/spawner/docker-sandbox.service.ts index d90cbde..37c1922 100644 --- a/apps/orchestrator/src/spawner/docker-sandbox.service.ts +++ b/apps/orchestrator/src/spawner/docker-sandbox.service.ts @@ -1,5 +1,6 @@ import { Injectable, Logger } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; +import { randomBytes } from "crypto"; import Docker from "dockerode"; import { DockerSandboxOptions, @@ -248,8 +249,10 @@ export class DockerSandboxService { } } - // Container name with timestamp to ensure uniqueness - const containerName = `mosaic-agent-${agentId}-${Date.now().toString()}`; + // Container name with timestamp and random suffix to guarantee uniqueness + // even when multiple agents are spawned simultaneously within the same millisecond + const uniqueSuffix = randomBytes(4).toString("hex"); + const containerName = `mosaic-agent-${agentId}-${Date.now().toString()}-${uniqueSuffix}`; this.logger.log( `Creating container for agent ${agentId} (image: ${image}, memory: ${memoryMB.toString()}MB, cpu: ${cpuLimit.toString()})`