Skip to content

Commit cdc6276

Browse files
authored
feat(workflow): Reuse a single v8 context (#951)
* Reusable v8 context * Skip test-isolation in non reusable context mode * Fix tests * One less serialization * Fix SDK version for reuseV8Context * Run in worker thread * No serialization of activations and completions * Self review * Fix unhandled rejection handling * Address review comments
1 parent 576a12d commit cdc6276

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1031
-476
lines changed

.github/workflows/ci.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@ jobs:
1515
strategy:
1616
fail-fast: true
1717
matrix:
18+
reuse-v8-context: [false]
1819
node: [14, 16]
1920
os: [ubuntu-latest, macos-latest, windows-latest]
21+
include:
22+
- os: ubuntu-latest
23+
node: 16
24+
reuse-v8-context: true
25+
2026
runs-on: ${{ matrix.os }}
2127
steps:
2228
- name: Print build information
@@ -66,6 +72,7 @@ jobs:
6672
env:
6773
# TODO: Run integration tests on MacOS / Windows probably using temporalite
6874
RUN_INTEGRATION_TESTS: ${{ startsWith(matrix.os, 'ubuntu') }}
75+
REUSE_V8_CONTEXT: ${{ matrix.reuse-v8-context }}
6976

7077
- uses: actions/upload-artifact@v3
7178
if: ${{ failure() }}
@@ -303,3 +310,11 @@ jobs:
303310
with:
304311
test-type: ci-stress
305312
test-timeout-minutes: 20
313+
reuse-v8-context: false
314+
315+
stress-tests-reuse-context:
316+
uses: ./.github/workflows/stress.yml
317+
with:
318+
test-type: ci-stress
319+
test-timeout-minutes: 20
320+
reuse-v8-context: true

.github/workflows/stress.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,22 @@ on:
99
test-timeout-minutes:
1010
required: true
1111
type: number
12+
reuse-v8-context:
13+
required: true
14+
type: boolean
1215

1316
env:
1417
TEMPORAL_TESTING_LOG_DIR: /tmp/worker-logs
1518
TEMPORAL_TESTING_MEM_LOG_DIR: /tmp/worker-mem-logs
1619
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20+
REUSE_V8_CONTEXT: ${{ inputs.reuse-v8-context }}
1721

1822
jobs:
1923
stress-test:
20-
runs-on: ubuntu-latest-4-cores
24+
runs-on: buildjet-4vcpu-ubuntu-2204
2125
steps:
2226
- name: Print build info
23-
run: 'echo test-type: ${{ inputs.test-type }}, test-timeout-minutes: ${{ inputs.test-timeout-minutes }}'
27+
run: 'echo test-type: ${{ inputs.test-type }}, test-timeout-minutes: ${{ inputs.test-timeout-minutes }}, reuse-v8-context: $REUSE_V8_CONTEXT'
2428
- uses: actions/checkout@v2
2529
with:
2630
submodules: recursive

docs/activation-in-debug-mode.mermaid

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
%% Activation diagram for Worker with `debugMode` option on
2+
sequenceDiagram
3+
participant Server
4+
participant Core as Rust Core
5+
participant MT as Node Main Thread
6+
participant VM as Workflow VM Sandbox
7+
8+
MT->>+Core: Poll Workflow Activation
9+
opt No pending work
10+
Core->>+Server: Poll Workflow Task
11+
Server-->>-Core: Respond with Workflow Task
12+
end
13+
Core->>-MT: Respond with Activation
14+
MT->>MT: Decode Payloads
15+
loop patches, signals, completions, queries as jobs
16+
MT->>VM: Activate(jobs)
17+
VM->>VM: Run Microtasks
18+
MT->>VM: Try Unblock Conditions
19+
end
20+
MT->>VM: Collect Commands
21+
MT->>MT: Encode Payloads
22+
MT->>+VM: Collect Sink Calls
23+
VM-->>-MT: Respond with Sink Calls
24+
MT->>MT: Run Sink Functions
25+
MT->>Core: Complete Activation
26+
opt Completed Workflow Task
27+
Core->>Server: Complete Workflow Task
28+
end
29+

docs/activation.mermaid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
%% Activation diagram for Worker with `reuseV8Context` option off
12
sequenceDiagram
23
participant Server
34
participant Core as Rust Core

docs/arch-debug-mode.svg

Lines changed: 4 additions & 0 deletions
Loading

packages/test/src/helpers.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import path from 'path';
22
import StackUtils from 'stack-utils';
3-
import ava from 'ava';
3+
import ava, { TestFn } from 'ava';
44
import { inWorkflowContext } from '@temporalio/workflow';
55
import { Payload, PayloadCodec } from '@temporalio/common';
6+
import { Worker as RealWorker, WorkerOptions } from '@temporalio/worker';
7+
import * as worker from '@temporalio/worker';
68

79
export function u8(s: string): Uint8Array {
810
// TextEncoder requires lib "dom"
@@ -18,6 +20,7 @@ export function isSet(env: string | undefined): boolean {
1820
}
1921

2022
export const RUN_INTEGRATION_TESTS = inWorkflowContext() || isSet(process.env.RUN_INTEGRATION_TESTS);
23+
export const REUSE_V8_CONTEXT = inWorkflowContext() || isSet(process.env.REUSE_V8_CONTEXT);
2124

2225
export async function sleep(ms: number): Promise<void> {
2326
return new Promise((resolve) => setTimeout(resolve, ms));
@@ -48,10 +51,22 @@ export function cleanStackTrace(ostack: string): string {
4851
return normalizedStack ? `${firstLine}\n${normalizedStack}` : firstLine;
4952
}
5053

54+
function noopTest() {
55+
// eslint: this function body is empty and it's okay.
56+
}
57+
58+
noopTest.serial = () => undefined;
59+
noopTest.macro = () => undefined;
60+
noopTest.before = () => undefined;
61+
noopTest.after = () => undefined;
62+
(noopTest.after as any).always = () => undefined;
63+
noopTest.beforeEach = () => undefined;
64+
noopTest.afterEach = () => undefined;
65+
5166
/**
52-
* (Incomplete) helper to allow mixing workflow and non-workflow code in the same test file.
67+
* (Mostly complete) helper to allow mixing workflow and non-workflow code in the same test file.
5368
*/
54-
export const test = inWorkflowContext() ? () => undefined : ava;
69+
export const test: TestFn<unknown> = inWorkflowContext() ? (noopTest as any) : ava;
5570

5671
export const bundlerOptions = {
5772
// This is a bit ugly but it does the trick, when a test that includes workflow code tries to import a forbidden
@@ -65,6 +80,7 @@ export const bundlerOptions = {
6580
'crypto',
6681
'module',
6782
'path',
83+
'stack-utils',
6884
],
6985
};
7086

@@ -86,3 +102,16 @@ export class ByteSkewerPayloadCodec implements PayloadCodec {
86102
}));
87103
}
88104
}
105+
106+
// Hack around Worker not being available in workflow context
107+
if (inWorkflowContext()) {
108+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
109+
// @ts-ignore
110+
worker.Worker = class {}; // eslint-disable-line import/namespace
111+
}
112+
113+
export class Worker extends worker.Worker {
114+
static async create(options: WorkerOptions): Promise<worker.Worker> {
115+
return RealWorker.create({ ...options, reuseV8Context: REUSE_V8_CONTEXT });
116+
}
117+
}

0 commit comments

Comments
 (0)