Skip to content

[Bug] Can't use structuredClone in workflow code #1663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
pn1k opened this issue Apr 1, 2025 · 5 comments
Open

[Bug] Can't use structuredClone in workflow code #1663

pn1k opened this issue Apr 1, 2025 · 5 comments
Labels
bug Something isn't working

Comments

@pn1k
Copy link

pn1k commented Apr 1, 2025

What are you really trying to do?

Make a copy of value in workflow code with structuredClone() function of nodejs.

Describe the bug

It seems that webpack'ed code lack of structuredClone function.

Minimal Reproduction

// in workflow code
const someData = { foo: 'bar' }
const cloneData = structuredClone(someData);

Environment/Versions

  • Node 22.14.0
  • OS and processor: Linux x86
  • Temporal SDK: 1.11.7
@pn1k pn1k added the bug Something isn't working label Apr 1, 2025
@lukeramsden
Copy link
Contributor

I've just found this too, trying to use structuredClone to copy some input arrays that are then modified inside a class. @mjameswh is there any reason not to expose this in the workflow sandbox same as we did for TextDecoder etc? I can open a PR to do that.

@lukeramsden
Copy link
Contributor

lukeramsden commented Apr 16, 2025

diff --git a/node_modules/@temporalio/worker/lib/workflow/reusable-vm.js b/node_modules/@temporalio/worker/lib/workflow/reusable-vm.js
index f3a06d6..8fb90fd 100644
--- a/node_modules/@temporalio/worker/lib/workflow/reusable-vm.js
+++ b/node_modules/@temporalio/worker/lib/workflow/reusable-vm.js
@@ -60,6 +60,7 @@ class ReusableVMWorkflowCreator {
             TextEncoder: node_util_1.TextEncoder,
             TextDecoder: node_util_1.TextDecoder,
             AbortController,
+            structuredClone,
         };
         this._context = node_vm_1.default.createContext(globals, { microtaskMode: 'afterEvaluate' });
         this.injectConsole();
diff --git a/node_modules/@temporalio/worker/lib/workflow/vm.js b/node_modules/@temporalio/worker/lib/workflow/vm.js
index 239aa90..21e2655 100644
--- a/node_modules/@temporalio/worker/lib/workflow/vm.js
+++ b/node_modules/@temporalio/worker/lib/workflow/vm.js
@@ -69,6 +69,7 @@ class VMWorkflowCreator {
             TextEncoder: node_util_1.TextEncoder,
             TextDecoder: node_util_1.TextDecoder,
             AbortController,
+            structuredClone,
         };
         const context = node_vm_1.default.createContext(globals, { microtaskMode: 'afterEvaluate' });
         this.script.runInContext(context);

If you use patch-package, this is a patch that will get this working. Testing myself in our system it all seems to work fine.

@mjameswh
Copy link
Contributor

This is a summary from this Slack conversation.

If the structuredClone function is injected this way, cloned objects prototypes will be associated with the wrong execution context, which means that instanceof will always return false, even for built-in types.

For example (this was executed ):

let x = { a: [1], b: new Error(), c: new Date() };
let y = structuredClone(x);

[x, y].map((z) => (z instanceof Object)
 => [true, false]

[x, y].map((z) => (z.a instanceof Array)
 => [true, false]

[x, y].map((z) => (z.b instanceof Error)
 => [true, false]

[x, y].map((z) => (z.c instanceof Date)
 => [true, false]

We have a utility function that we use internally to recursively fix some well known prototypes on objects being transferred from an outer execution context. However, using that over structuredClone would break the contract of structuredClone, i.e. a single object reachable through multiple paths inside a source object graph should be cloned to a single object in the resulting graph. The fixPrototypes function could also fall in a never ending loop if the source graph contains self reference loops.

For those reasons, the best approach at this time is to load a polyfill.

@lukeramsden
Copy link
Contributor

Fascinating - didn't know that. I would say that's worth documenting since I think this will come up again a lot. Going to replace my patch with a poly-fill.

@mjameswh
Copy link
Contributor

I would say that's worth documenting since I think this will come up again a lot.

Yeah, been thinking about this for some time. We definitely need to provide more clarity regarding what expectations hold inside the workflow sandbox and why some things are not provided. Our current claim about only exposing "APIs that are deterministic-safe" is too imprecise, and not sufficient since there are other constraints at play.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants