-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathcreateDeploymentWithNextVersion.server.ts
More file actions
99 lines (86 loc) · 2.99 KB
/
Copy pathcreateDeploymentWithNextVersion.server.ts
File metadata and controls
99 lines (86 loc) · 2.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import {
isUniqueConstraintError,
type Prisma,
type PrismaClientOrTransaction,
type WorkerDeployment,
} from "@trigger.dev/database";
import { setTimeout as sleep } from "node:timers/promises";
import { logger } from "~/services/logger.server";
import { calculateNextBuildVersion } from "../../utils/calculateNextBuildVersion";
export type CreateDeploymentData = Omit<
Prisma.WorkerDeploymentUncheckedCreateInput,
"version" | "environmentId"
>;
export type CreateDeploymentWithNextVersionOptions = {
maxRetries?: number;
jitterMs?: { min: number; max: number };
};
const DEFAULT_MAX_RETRIES = 5;
const DEFAULT_JITTER_MS = { min: 5, max: 50 };
export class DeploymentVersionCollisionError extends Error {
readonly name = "DeploymentVersionCollisionError";
readonly environmentId: string;
readonly attempts: number;
readonly lastAttemptedVersion: string;
constructor(args: {
environmentId: string;
attempts: number;
lastAttemptedVersion: string;
cause: unknown;
}) {
super(
`Failed to allocate a unique worker deployment version for environment ${args.environmentId} after ${args.attempts} attempt(s); last tried "${args.lastAttemptedVersion}"`,
{ cause: args.cause }
);
this.environmentId = args.environmentId;
this.attempts = args.attempts;
this.lastAttemptedVersion = args.lastAttemptedVersion;
}
}
export async function createDeploymentWithNextVersion(
prisma: PrismaClientOrTransaction,
environmentId: string,
buildData: (nextVersion: string) => CreateDeploymentData | Promise<CreateDeploymentData>,
options: CreateDeploymentWithNextVersionOptions = {}
): Promise<WorkerDeployment> {
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
const jitterMs = options.jitterMs ?? DEFAULT_JITTER_MS;
let lastError: unknown;
let lastVersion = "";
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const latest = await prisma.workerDeployment.findFirst({
where: { environmentId },
orderBy: { createdAt: "desc" },
take: 1,
});
const version = calculateNextBuildVersion(latest?.version);
lastVersion = version;
const data = await buildData(version);
try {
return await prisma.workerDeployment.create({
data: { ...data, environmentId, version },
});
} catch (error) {
if (!isUniqueConstraintError(error, ["environmentId", "version"])) {
throw error;
}
lastError = error;
logger.warn("Worker deployment version collided, retrying", {
environmentId,
attempt: attempt + 1,
maxRetries,
attemptedVersion: version,
});
// Randomised backoff so N concurrent racers don't loop in lockstep into the
// same collision again.
const delay = jitterMs.min + Math.random() * (jitterMs.max - jitterMs.min);
await sleep(delay);
}
}
throw new DeploymentVersionCollisionError({
environmentId,
attempts: maxRetries + 1,
lastAttemptedVersion: lastVersion,
cause: lastError,
});
}