Skip to content

Commit 91a66df

Browse files
authored
Merge pull request #6612 from remotion-dev/fix/serverless-progress-upload
`@remotion/serverless`: More resilient and efficient progress uploading
2 parents 6d6a733 + 7eacbb4 commit 91a66df

File tree

3 files changed

+75
-60
lines changed

3 files changed

+75
-60
lines changed

packages/serverless/src/handlers/launch.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ const innerLaunchHandler = async <Provider extends CloudProvider>({
479479
willRetry: false,
480480
totalAttempts: 1,
481481
});
482-
overallProgress.upload();
482+
overallProgress.upload('artifactWriteError');
483483
RenderInternals.Log.error(
484484
{indent: false, logLevel: params.logLevel},
485485
'Failed to write artifact to S3',
@@ -684,7 +684,7 @@ export const launchHandler = async <Provider extends CloudProvider>({
684684
willRetry: false,
685685
totalAttempts: 1,
686686
});
687-
overallProgress.upload();
687+
overallProgress.upload('timeoutWebhookError');
688688
}
689689
};
690690

@@ -777,7 +777,7 @@ export const launchHandler = async <Provider extends CloudProvider>({
777777
willRetry: false,
778778
totalAttempts: 1,
779779
});
780-
overallProgress.upload();
780+
overallProgress.upload('successWebhookError');
781781

782782
RenderInternals.Log.error(
783783
{indent: false, logLevel: params.logLevel},
@@ -816,7 +816,7 @@ export const launchHandler = async <Provider extends CloudProvider>({
816816
willRetry: false,
817817
message: (err as Error).message,
818818
});
819-
await overallProgress.upload();
819+
await overallProgress.upload('fatalError');
820820

821821
runCleanupTasks();
822822

@@ -866,7 +866,7 @@ export const launchHandler = async <Provider extends CloudProvider>({
866866
willRetry: false,
867867
totalAttempts: 1,
868868
});
869-
overallProgress.upload();
869+
overallProgress.upload('errorWebhookError');
870870

871871
RenderInternals.Log.error(
872872
{indent: false, logLevel: params.logLevel},

packages/serverless/src/merge-chunks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export const mergeChunksAndFinishRender = async <
156156
providerSpecifics: options.providerSpecifics,
157157
});
158158

159-
options.overallProgress.setPostRenderData(postRenderData);
159+
await options.overallProgress.setPostRenderData(postRenderData);
160160

161161
fs.unlinkSync(outfile);
162162
await cleanupChunksProm;

packages/serverless/src/overall-render-progress.ts

Lines changed: 69 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type {
1212
import {overallProgressKey} from '@remotion/serverless-client';
1313

1414
export type OverallProgressHelper<Provider extends CloudProvider> = {
15-
upload: () => Promise<void>;
15+
upload: (reason: string) => Promise<void>;
1616
setFrames: ({
1717
encoded,
1818
rendered,
@@ -31,7 +31,9 @@ export type OverallProgressHelper<Provider extends CloudProvider> = {
3131
setCombinedFrames: (framesEncoded: number) => void;
3232
setTimeToCombine: (timeToCombine: number) => void;
3333
addRetry: (retry: ChunkRetry) => void;
34-
setPostRenderData: (postRenderData: PostRenderData<Provider>) => void;
34+
setPostRenderData: (
35+
postRenderData: PostRenderData<Provider>,
36+
) => Promise<void>;
3537
setRenderMetadata: (renderMetadata: RenderMetadata<Provider>) => void;
3638
addErrorWithoutUpload: (errorInfo: FunctionErrorInfo) => void;
3739
setExpectedChunks: (expectedChunks: number) => void;
@@ -95,62 +97,73 @@ export const makeOverallRenderProgress = <Provider extends CloudProvider>({
9597
const renderProgress: OverallRenderProgress<Provider> =
9698
makeInitialOverallRenderProgress(timeoutTimestamp);
9799

98-
let currentUploadPromise: Promise<void> | null = null;
99-
100-
const getCurrentProgress = () => renderProgress;
101-
102-
let latestUploadRequest = 0;
103-
const getLatestRequestId = () => latestUploadRequest;
100+
let dirty = false;
101+
let dirtyReasons: string[] = [];
102+
let uploadLoopPromise: Promise<void> | null = null;
104103

105104
let encodeStartTime: number | null = null;
106105
let renderFramesStartTime: number | null = null;
107106

108-
const upload = async () => {
109-
const uploadRequestId = ++latestUploadRequest;
110-
if (currentUploadPromise) {
111-
await currentUploadPromise;
112-
}
113-
114-
// If request has been replaced by a new one
115-
if (getLatestRequestId() !== uploadRequestId) {
116-
return;
117-
}
107+
const markDirty = (reason: string) => {
108+
dirty = true;
109+
dirtyReasons.push(reason);
110+
};
118111

119-
const toWrite = JSON.stringify(getCurrentProgress());
112+
const runUploadLoop = async () => {
113+
while (dirty) {
114+
dirty = false;
115+
const reasons = dirtyReasons.join(', ');
116+
dirtyReasons = [];
117+
const toWrite = JSON.stringify(renderProgress);
120118

121-
const start = Date.now();
122-
currentUploadPromise = providerSpecifics
123-
.writeFile({
124-
body: toWrite,
125-
bucketName,
126-
customCredentials: null,
127-
downloadBehavior: null,
128-
expectedBucketOwner,
129-
key: overallProgressKey(renderId),
130-
privacy: 'private',
131-
region,
132-
forcePathStyle,
133-
storageClass: null,
134-
requestHandler: null,
135-
})
136-
.then(() => {
137-
// By default, upload is way too fast (~20 requests per second)
119+
RenderInternals.Log.verbose(
120+
{indent: false, logLevel},
121+
`Uploading progress - ${reasons} (${toWrite.length} bytes)`,
122+
);
123+
const start = Date.now();
124+
try {
125+
await providerSpecifics.writeFile({
126+
body: toWrite,
127+
bucketName,
128+
customCredentials: null,
129+
downloadBehavior: null,
130+
expectedBucketOwner,
131+
key: overallProgressKey(renderId),
132+
privacy: 'private',
133+
region,
134+
forcePathStyle,
135+
storageClass: null,
136+
requestHandler: null,
137+
});
138+
RenderInternals.Log.verbose(
139+
{indent: false, logLevel},
140+
`Uploaded progress in ${Date.now() - start}ms`,
141+
);
138142
// Space out the requests a bit
139-
return new Promise<void>((resolve) => {
143+
await new Promise<void>((resolve) => {
140144
setTimeout(resolve, Math.max(0, 250 - (Date.now() - start)));
141145
});
142-
})
143-
.catch((err) => {
146+
} catch (err) {
144147
// If an error occurs in uploading the state that contains the errors,
145148
// that is unfortunate. We just log it.
146149
RenderInternals.Log.error(
147150
{indent: false, logLevel},
148151
'Error uploading progress',
149152
err,
150153
);
151-
});
152-
await currentUploadPromise;
153-
currentUploadPromise = null;
154+
}
155+
}
156+
157+
uploadLoopPromise = null;
158+
};
159+
160+
const upload = async (reason: string) => {
161+
markDirty(reason);
162+
if (!uploadLoopPromise) {
163+
uploadLoopPromise = runUploadLoop();
164+
}
165+
166+
await uploadLoopPromise;
154167
};
155168

156169
return {
@@ -203,7 +216,9 @@ export const makeOverallRenderProgress = <Provider extends CloudProvider>({
203216

204217
renderProgress.framesRendered = totalFramesRendered;
205218
renderProgress.framesEncoded = totalFramesEncoded;
206-
upload();
219+
upload(
220+
`setFrames(rendered=${totalFramesRendered}, encoded=${totalFramesEncoded})`,
221+
);
207222
},
208223
addChunkCompleted: (chunkIndex, start, rendered) => {
209224
renderProgress.chunks.push(chunkIndex);
@@ -220,15 +235,15 @@ export const makeOverallRenderProgress = <Provider extends CloudProvider>({
220235
start,
221236
rendered,
222237
});
223-
upload();
238+
upload(`addChunkCompleted(chunk=${chunkIndex})`);
224239
},
225240
setCombinedFrames: (frames: number) => {
226241
renderProgress.combinedFrames = frames;
227-
upload();
242+
upload(`setCombinedFrames(${frames})`);
228243
},
229244
setTimeToCombine: (timeToCombine: number) => {
230245
renderProgress.timeToCombine = timeToCombine;
231-
upload();
246+
upload(`setTimeToCombine(${timeToCombine})`);
232247
},
233248
setLambdaInvoked(chunk) {
234249
if (lambdasInvoked.length === 0) {
@@ -240,15 +255,15 @@ export const makeOverallRenderProgress = <Provider extends CloudProvider>({
240255
(a, b) => a + Number(b),
241256
0,
242257
);
243-
upload();
258+
upload(`setLambdaInvoked(chunk=${chunk})`);
244259
},
245-
setPostRenderData(postRenderData) {
260+
async setPostRenderData(postRenderData) {
246261
renderProgress.postRenderData = postRenderData;
247-
upload();
262+
await upload('setPostRenderData');
248263
},
249264
setRenderMetadata: (renderMetadata) => {
250265
renderProgress.renderMetadata = renderMetadata;
251-
upload();
266+
upload('setRenderMetadata');
252267
},
253268
addErrorWithoutUpload: (errorInfo) => {
254269
renderProgress.errors.push(errorInfo);
@@ -260,19 +275,19 @@ export const makeOverallRenderProgress = <Provider extends CloudProvider>({
260275
},
261276
setCompositionValidated(timestamp) {
262277
renderProgress.compositionValidated = timestamp;
263-
upload();
278+
upload('setCompositionValidated');
264279
},
265280
setServeUrlOpened(timestamp) {
266281
renderProgress.serveUrlOpened = timestamp;
267-
upload();
282+
upload('setServeUrlOpened');
268283
},
269284
addRetry(retry) {
270285
renderProgress.retries.push(retry);
271-
upload();
286+
upload('addRetry');
272287
},
273288
addReceivedArtifact(asset) {
274289
renderProgress.receivedArtifact.push(asset);
275-
upload();
290+
upload('addReceivedArtifact');
276291
},
277292
getReceivedArtifacts() {
278293
return renderProgress.receivedArtifact;

0 commit comments

Comments
 (0)