From 6c2df85f4bdba381235dcd79e162384282b403c1 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Fri, 11 Apr 2025 15:11:26 -0700 Subject: [PATCH 01/11] Update core --- packages/core-bridge/sdk-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-bridge/sdk-core b/packages/core-bridge/sdk-core index 93471ac6d..e18982ec7 160000 --- a/packages/core-bridge/sdk-core +++ b/packages/core-bridge/sdk-core @@ -1 +1 @@ -Subproject commit 93471ac6d8bbf62839148a1bf97ef6dfd49aead0 +Subproject commit e18982ec72be62e357a5ea418b1670c8b2fee55f From 7cfcfc45082e6b320412348886091a5bb14c1eb0 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Fri, 11 Apr 2025 16:17:28 -0700 Subject: [PATCH 02/11] Upgrade core & make compile --- packages/common/src/index.ts | 1 + packages/common/src/worker-deployments.ts | 11 + packages/core-bridge/Cargo.lock | 461 +++++++++++------- packages/core-bridge/src/conversions.rs | 60 ++- .../src/conversions/slot_supplier_bridge.rs | 28 +- packages/core-bridge/ts/index.ts | 13 +- packages/worker/src/utils.ts | 14 + packages/worker/src/worker-options.ts | 32 +- packages/worker/src/worker.ts | 5 +- packages/worker/src/workflow/vm-shared.ts | 4 +- packages/workflow/src/interfaces.ts | 12 + 11 files changed, 432 insertions(+), 209 deletions(-) create mode 100644 packages/common/src/worker-deployments.ts diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 1c45a5a90..04e963079 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -22,6 +22,7 @@ export * from './logger'; export * from './priority'; export * from './retry-policy'; export type { Timestamp, Duration, StringValue } from './time'; +export * from './worker-deployments'; export * from './workflow-handle'; export * from './workflow-options'; export * from './versioning-intent'; diff --git a/packages/common/src/worker-deployments.ts b/packages/common/src/worker-deployments.ts new file mode 100644 index 000000000..8023be01f --- /dev/null +++ b/packages/common/src/worker-deployments.ts @@ -0,0 +1,11 @@ +/** + * Represents the version of a specific worker deployment. + * + * @experimental Worker deployments are experimental + */ +export interface WorkerDeploymentVersion { + buildId: string; + deploymentName: string; +} + +export type VersioningBehavior = 'pinned' | 'auto-upgrade'; diff --git a/packages/core-bridge/Cargo.lock b/packages/core-bridge/Cargo.lock index 2a6782fba..c078c5e77 100644 --- a/packages/core-bridge/Cargo.lock +++ b/packages/core-bridge/Cargo.lock @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.87" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -179,7 +179,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -217,9 +217,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bzip2" @@ -242,9 +242,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "jobserver", "libc", @@ -340,9 +340,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -374,9 +374,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -384,9 +384,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", @@ -398,9 +398,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", @@ -429,9 +429,9 @@ checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] @@ -529,9 +529,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "either" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "enum-iterator" @@ -583,9 +583,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -617,9 +617,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", "miniz_oxide", @@ -633,9 +633,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" @@ -648,9 +648,9 @@ dependencies = [ [[package]] name = "fragile" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" [[package]] name = "futures" @@ -783,16 +783,16 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", - "windows-targets", ] [[package]] @@ -818,7 +818,7 @@ dependencies = [ "futures-sink", "futures-timer", "futures-util", - "getrandom 0.3.1", + "getrandom 0.3.2", "no-std-compat", "nonzero_ext", "parking_lot", @@ -842,7 +842,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.7.1", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -889,9 +889,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -910,12 +910,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -987,9 +987,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-channel", @@ -997,6 +997,7 @@ dependencies = [ "http", "http-body", "hyper", + "libc", "pin-project-lite", "socket2", "tokio", @@ -1045,9 +1046,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -1069,9 +1070,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -1090,9 +1091,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -1161,9 +1162,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -1219,10 +1220,11 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.2", "libc", ] @@ -1244,9 +1246,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.170" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libloading" @@ -1255,7 +1257,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1271,9 +1273,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.15" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" @@ -1299,9 +1301,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "lru" @@ -1322,6 +1324,17 @@ dependencies = [ "crc", ] +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1351,9 +1364,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -1484,9 +1497,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl-probe" @@ -1626,7 +1639,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1652,7 +1665,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.7.1", + "indexmap 2.9.0", ] [[package]] @@ -1708,6 +1721,15 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1716,11 +1738,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -1751,9 +1773,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.30" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", "syn", @@ -1904,11 +1926,12 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" dependencies = [ "bytes", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", @@ -1918,17 +1941,18 @@ dependencies = [ "thiserror 2.0.12", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" dependencies = [ "bytes", - "getrandom 0.2.15", - "rand 0.8.5", + "getrandom 0.3.2", + "rand 0.9.0", "ring", "rustc-hash", "rustls", @@ -1942,9 +1966,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" +checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" dependencies = [ "cfg_aliases", "libc", @@ -1956,13 +1980,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -1982,7 +2012,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.21", + "zerocopy", ] [[package]] @@ -2020,23 +2050,23 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", ] [[package]] name = "raw-cpuid" -version = "11.4.0" +version = "11.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529468c1335c1c03919960dfefdb1b3648858c20d7ec2d0663e728e4a717efbc" +checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" dependencies = [ "bitflags", ] [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ "bitflags", ] @@ -2087,9 +2117,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.12" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64", "bytes", @@ -2133,9 +2163,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.11" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", @@ -2147,12 +2177,13 @@ dependencies = [ [[package]] name = "ringbuf" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726bb493fe9cac765e8f96a144c3a8396bdf766dedad22e504b70b908dcbceb4" +checksum = "fe47b720588c8702e34b5979cb3271a8b1842c7cb6f57408efa70c779363488c" dependencies = [ "crossbeam-utils", "portable-atomic", + "portable-atomic-util", ] [[package]] @@ -2192,9 +2223,9 @@ version = "0.1.0" [[package]] name = "rustix" -version = "0.38.44" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ "bitflags", "errno", @@ -2205,9 +2236,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ "log", "once_cell", @@ -2250,9 +2281,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "ring", "rustls-pki-types", @@ -2323,18 +2354,18 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -2432,15 +2463,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2475,9 +2506,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.99" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -2541,13 +2572,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.17.1" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.2", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2757,9 +2787,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "num-conv", @@ -2770,9 +2800,9 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "tinystr" @@ -2801,9 +2831,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", @@ -2851,9 +2881,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", @@ -3090,11 +3120,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.15.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", ] [[package]] @@ -3126,9 +3156,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -3266,7 +3296,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ "windows-core", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3278,7 +3308,7 @@ dependencies = [ "windows-implement", "windows-interface", "windows-result 0.1.2", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3303,15 +3333,21 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + [[package]] name = "windows-registry" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ - "windows-result 0.2.0", + "windows-result 0.3.2", "windows-strings", - "windows-targets", + "windows-targets 0.53.0", ] [[package]] @@ -3320,26 +3356,25 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] name = "windows-result" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.1.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ - "windows-result 0.2.0", - "windows-targets", + "windows-link", ] [[package]] @@ -3348,7 +3383,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3357,7 +3392,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3366,14 +3401,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -3382,53 +3433,101 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] @@ -3447,15 +3546,23 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xattr" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" +checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" dependencies = [ "libc", - "linux-raw-sys", "rustix", ] +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "yoke" version = "0.7.5" @@ -3482,39 +3589,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478" -dependencies = [ - "zerocopy-derive 0.8.21", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.21" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", @@ -3586,9 +3672,9 @@ dependencies = [ [[package]] name = "zip" -version = "2.2.3" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b280484c454e74e5fff658bbf7df8fdbe7a07c6b2de4a53def232c15ef138f3a" +checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744" dependencies = [ "aes", "arbitrary", @@ -3597,17 +3683,16 @@ dependencies = [ "crc32fast", "crossbeam-utils", "deflate64", - "displaydoc", "flate2", + "getrandom 0.3.2", "hmac", - "indexmap 2.7.1", + "indexmap 2.9.0", "lzma-rs", "memchr", "pbkdf2", - "rand 0.8.5", "sha1", - "thiserror 2.0.12", "time", + "xz2", "zeroize", "zopfli", "zstd", @@ -3638,18 +3723,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.3" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.14+zstd.1.5.7" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", diff --git a/packages/core-bridge/src/conversions.rs b/packages/core-bridge/src/conversions.rs index 2c2da9d07..504807168 100644 --- a/packages/core-bridge/src/conversions.rs +++ b/packages/core-bridge/src/conversions.rs @@ -8,20 +8,17 @@ use neon::{ use slot_supplier_bridge::SlotSupplierBridge; use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration}; use temporal_client::HttpConnectProxyOptions; -use temporal_sdk_core::api::{ - telemetry::{HistogramBucketOverrides, OtlpProtocol}, - worker::{PollerBehavior, SlotKind}, -}; use temporal_sdk_core::{ ClientOptions, ClientOptionsBuilder, ClientTlsConfig, ResourceBasedSlotsOptions, ResourceBasedSlotsOptionsBuilder, ResourceSlotOptions, RetryConfig, SlotSupplierOptions, TlsConfig, TunerHolderOptionsBuilder, Url, - api::telemetry::{Logger, MetricTemporality, TelemetryOptions, TelemetryOptionsBuilder}, api::{ telemetry::{ - OtelCollectorOptionsBuilder, PrometheusExporterOptionsBuilder, metrics::CoreMeter, + Logger, MetricTemporality, OtelCollectorOptionsBuilder, + PrometheusExporterOptionsBuilder, TelemetryOptions, TelemetryOptionsBuilder, + metrics::CoreMeter, }, - worker::{WorkerConfig, WorkerConfigBuilder}, + worker::{WorkerConfig, WorkerConfigBuilder, WorkerDeploymentVersion}, }, ephemeral_server::{ TemporalDevServerConfig, TemporalDevServerConfigBuilder, TestServerConfig, @@ -29,6 +26,13 @@ use temporal_sdk_core::{ }, telemetry::{build_otlp_metric_exporter, start_prometheus_metric_exporter}, }; +use temporal_sdk_core::{ + api::{ + telemetry::{HistogramBucketOverrides, OtlpProtocol}, + worker::{PollerBehavior, SlotKind, WorkerDeploymentOptions, WorkerVersioningStrategy}, + }, + protos::temporal::api::enums::v1::VersioningBehavior, +}; mod slot_supplier_bridge; @@ -517,10 +521,48 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> { return cx.throw_error("Missing tuner"); }; + let build_id = js_value_getter!(cx, self, "buildId", JsString); + let use_worker_versioning = js_value_getter!(cx, self, "useVersioning", JsBoolean); + let deployment_options = js_optional_getter!(cx, self, "workerDeploymentOptions", JsObject); + + let versioning_strategy = if let Some(dopts) = deployment_options { + let use_worker_versioning = + js_value_getter!(cx, &dopts, "useWorkerVersioning", JsBoolean); + let default_versioning_behavior = + js_optional_value_getter!(cx, &dopts, "defaultVersioningBehavior", JsString); + let default_versioning_behavior = match default_versioning_behavior.as_deref() { + Some("pinned") => Some(VersioningBehavior::Pinned), + Some("auto-upgrade") => Some(VersioningBehavior::AutoUpgrade), + None => None, + _ => return cx.throw_error("Invalid default versioning behavior"), + }; + let version = { + let wdv = js_getter!(cx, &dopts, "version", JsObject); + let build_id = js_value_getter!(cx, &wdv, "buildId", JsString); + let deployment_name = js_value_getter!(cx, &wdv, "deploymentName", JsString); + WorkerDeploymentVersion { + build_id, + deployment_name, + } + }; + WorkerVersioningStrategy::WorkerDeploymentBased(WorkerDeploymentOptions { + version, + use_worker_versioning, + default_versioning_behavior, + }) + } else if use_worker_versioning && !build_id.is_empty() { + WorkerVersioningStrategy::LegacyBuildIdBased { build_id } + } else if !build_id.is_empty() { + WorkerVersioningStrategy::None { build_id } + } else { + WorkerVersioningStrategy::None { + build_id: "".to_string(), + } + }; + match WorkerConfigBuilder::default() - .worker_build_id(js_value_getter!(cx, self, "buildId", JsString)) + .versioning_strategy(versioning_strategy) .client_identity_override(Some(js_value_getter!(cx, self, "identity", JsString))) - .use_worker_versioning(js_value_getter!(cx, self, "useVersioning", JsBoolean)) .no_remote_activities(!enable_remote_activities) .tuner(tuner) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(max_concurrent_wft_polls)) diff --git a/packages/core-bridge/src/conversions/slot_supplier_bridge.rs b/packages/core-bridge/src/conversions/slot_supplier_bridge.rs index 6b418f8dd..f82f1e7a7 100644 --- a/packages/core-bridge/src/conversions/slot_supplier_bridge.rs +++ b/packages/core-bridge/src/conversions/slot_supplier_bridge.rs @@ -13,7 +13,7 @@ use prost::Message; use std::{cell::RefCell, marker::PhantomData, sync::Arc, time::Duration}; use temporal_sdk_core::api::worker::{ SlotKind, SlotKindType, SlotMarkUsedContext, SlotReleaseContext, SlotReservationContext, - SlotSupplier, SlotSupplierPermit, + SlotSupplier, SlotSupplierPermit, WorkerDeploymentVersion, }; use tokio::sync::oneshot; @@ -76,7 +76,7 @@ impl SlotSupplier for SlotSupplierBridge { let rcb = self.reserve_cb.clone(); let task_queue = ctx.task_queue().to_string(); let worker_identity = ctx.worker_identity().to_string(); - let worker_build_id = ctx.worker_build_id().to_string(); + let worker_deployment_version = ctx.worker_deployment_version().clone(); let is_sticky = ctx.is_sticky(); let (callback_fut, _abort_on_drop) = match self @@ -85,7 +85,7 @@ impl SlotSupplier for SlotSupplierBridge { let context = Self::mk_reserve_ctx( task_queue, worker_identity, - worker_build_id, + worker_deployment_version, is_sticky, &mut cx, )?; @@ -157,7 +157,7 @@ impl SlotSupplier for SlotSupplierBridge { let rcb = self.try_reserve_cb.clone(); let task_queue = ctx.task_queue().to_string(); let worker_identity = ctx.worker_identity().to_string(); - let worker_build_id = ctx.worker_build_id().to_string(); + let worker_deployment_version = ctx.worker_deployment_version().clone(); let is_sticky = ctx.is_sticky(); // This is... unfortunate but since this method is called from an async context way up @@ -170,7 +170,7 @@ impl SlotSupplier for SlotSupplierBridge { let context = Self::mk_reserve_ctx( task_queue, worker_identity, - worker_build_id, + worker_deployment_version, is_sticky, &mut cx, )?; @@ -262,7 +262,7 @@ impl SlotSupplierBridge { fn mk_reserve_ctx<'a, C: Context<'a>>( task_queue: String, worker_identity: String, - worker_build_id: String, + worker_deployment_version: Option, is_sticky: bool, cx: &mut C, ) -> NeonResult> { @@ -278,8 +278,22 @@ impl SlotSupplierBridge { context.set(cx, "taskQueue", tq)?; let wid = cx.string(worker_identity); context.set(cx, "workerIdentity", wid)?; - let bid = cx.string(worker_build_id); + let bid = cx.string( + worker_deployment_version + .as_ref() + .map(|v| v.build_id.clone()) + .unwrap_or_default(), + ); context.set(cx, "workerBuildId", bid)?; + let depver = cx.empty_object(); + depver.set(cx, "buildId", bid)?; + let depname_str = cx.string( + worker_deployment_version + .map(|v| v.deployment_name) + .unwrap_or_default(), + ); + depver.set(cx, "deploymentName", depname_str)?; + context.set(cx, "workerDeploymentVersion", depver)?; let is_sticky = cx.boolean(is_sticky); context.set(cx, "isSticky", is_sticky)?; let context = context.as_value(cx); diff --git a/packages/core-bridge/ts/index.ts b/packages/core-bridge/ts/index.ts index e4ab77eaf..dd6298076 100644 --- a/packages/core-bridge/ts/index.ts +++ b/packages/core-bridge/ts/index.ts @@ -1,4 +1,10 @@ -import { LogLevel, Duration, SearchAttributeType } from '@temporalio/common'; +import { + LogLevel, + Duration, + SearchAttributeType, + WorkerDeploymentVersion, + VersioningBehavior, +} from '@temporalio/common'; import type { TLSConfig, ProxyConfig, HttpConnectProxyConfig } from '@temporalio/common/lib/internal-non-workflow'; import { WorkerTuner } from './worker-tuner'; import { SearchAttributeKey } from '@temporalio/common/src/search-attributes'; @@ -390,6 +396,11 @@ export interface WorkerOptions { maxTaskQueueActivitiesPerSecond?: number; maxActivitiesPerSecond?: number; shutdownGraceTimeMs: number; + workerDeploymentOptions?: { + version: WorkerDeploymentVersion; + useWorkerVersioning: boolean; + defaultVersioningBehavior?: VersioningBehavior; + }; } export type LogEntryMetadata = { diff --git a/packages/worker/src/utils.ts b/packages/worker/src/utils.ts index 62b8198e0..bddd5fbc5 100644 --- a/packages/worker/src/utils.ts +++ b/packages/worker/src/utils.ts @@ -1,3 +1,4 @@ +import { WorkerDeploymentVersion } from '@temporalio/common'; import type { coresdk } from '@temporalio/proto'; import { IllegalStateError, ParentWorkflowInfo } from '@temporalio/workflow'; @@ -28,3 +29,16 @@ export function convertToParentWorkflowType( namespace: parent.namespace, }; } + +export function convertDeploymentVersion( + v: coresdk.common.IWorkerDeploymentVersion | null | undefined +): WorkerDeploymentVersion | undefined { + if (!v) { + return undefined; + } + + return { + buildId: v.buildId ?? '', + deploymentName: v.deploymentName ?? '', + }; +} diff --git a/packages/worker/src/worker-options.ts b/packages/worker/src/worker-options.ts index 993de4592..81e489400 100644 --- a/packages/worker/src/worker-options.ts +++ b/packages/worker/src/worker-options.ts @@ -1,7 +1,13 @@ import * as os from 'node:os'; import * as v8 from 'node:v8'; import type { Configuration as WebpackConfiguration } from 'webpack'; -import { ActivityFunction, DataConverter, LoadedDataConverter } from '@temporalio/common'; +import { + ActivityFunction, + DataConverter, + LoadedDataConverter, + VersioningBehavior, + WorkerDeploymentVersion, +} from '@temporalio/common'; import { Duration, msOptionalToNumber, msToNumber } from '@temporalio/common/lib/time'; import { loadDataConverter } from '@temporalio/common/lib/internal-non-workflow'; import { LoggerSinks } from '@temporalio/workflow'; @@ -99,6 +105,7 @@ export interface WorkerOptions { * @default `@temporalio/worker` package name and version + checksum of workflow bundle's code * * @experimental The Worker Versioning API is still being designed. Major changes are expected. + * @deprecated Use {@link deploymentVersion} instead. */ buildId?: string; @@ -110,9 +117,32 @@ export interface WorkerOptions { * For more information, see https://docs.temporal.io/workers#worker-versioning * * @experimental The Worker Versioning API is still being designed. Major changes are expected. + * @deprecated Use {@link deploymentVersion} instead. */ useVersioning?: boolean; + /** + * Deployment options for the worker. Exclusive with `build_id` and `use_worker_versioning`. + + * @experimental Deployment based versioning is still experimental. + */ + workerDeploymentOptions?: { + /** + * The deployment version of the worker. + */ + version: WorkerDeploymentVersion; + + /** + * Whether to use deployment-based worker versioning. + */ + useWorkerVersioning: boolean; + + /** + * If specified, the default versioning behavior to use for all workflows on this worker. + */ + defaultVersioningBehavior?: VersioningBehavior; + }; + /** * The namespace this worker will connect to * diff --git a/packages/worker/src/worker.ts b/packages/worker/src/worker.ts index a47a22a36..518c960ad 100644 --- a/packages/worker/src/worker.ts +++ b/packages/worker/src/worker.ts @@ -71,7 +71,7 @@ import { } from './replay'; import { History, Runtime } from './runtime'; import { CloseableGroupedObservable, closeableGroupBy, mapWithState, mergeMapWithState } from './rxutils'; -import { byteArrayToBuffer, convertToParentWorkflowType } from './utils'; +import { byteArrayToBuffer, convertDeploymentVersion, convertToParentWorkflowType } from './utils'; import { CompiledWorkerOptions, CompiledWorkerOptionsWithBuildId, @@ -1301,7 +1301,8 @@ export class Worker { // A zero value means that it was not set by the server historySize: activation.historySizeBytes.toNumber(), continueAsNewSuggested: activation.continueAsNewSuggested, - currentBuildId: activation.buildIdForCurrentTask, + currentBuildId: activation.deploymentVersionForCurrentTask?.buildId ?? '', + currentDeploymentVersion: convertDeploymentVersion(activation.deploymentVersionForCurrentTask), unsafe: { now: () => Date.now(), // re-set in initRuntime isReplaying: activation.isReplaying, diff --git a/packages/worker/src/workflow/vm-shared.ts b/packages/worker/src/workflow/vm-shared.ts index 7418b2655..165f3ce6d 100644 --- a/packages/worker/src/workflow/vm-shared.ts +++ b/packages/worker/src/workflow/vm-shared.ts @@ -16,6 +16,7 @@ import { SdkFlags } from '@temporalio/workflow/lib/flags'; import { UnhandledRejectionError } from '../errors'; import { Workflow } from './interface'; import { WorkflowBundleWithSourceMapAndFilename } from './workflow-worker-thread/input'; +import { convertDeploymentVersion } from '../utils'; // Best effort to catch unhandled rejections from workflow code. // We crash the thread if we cannot find the culprit. @@ -348,7 +349,8 @@ export abstract class BaseVMWorkflow implements Workflow { // historySize === 0 means WFT was generated by pre-1.20.0 server, and the history size is unknown historySize: activation.historySizeBytes?.toNumber() ?? 0, continueAsNewSuggested: activation.continueAsNewSuggested ?? false, - currentBuildId: activation.buildIdForCurrentTask ?? undefined, + currentBuildId: activation.deploymentVersionForCurrentTask?.buildId ?? '', + currentDeploymentVersion: convertDeploymentVersion(activation.deploymentVersionForCurrentTask), unsafe: { ...info.unsafe, isReplaying: activation.isReplaying ?? false, diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts index 25fb2218a..d36494a74 100644 --- a/packages/workflow/src/interfaces.ts +++ b/packages/workflow/src/interfaces.ts @@ -13,6 +13,7 @@ import { TypedSearchAttributes, SearchAttributePair, Priority, + WorkerDeploymentVersion, } from '@temporalio/common'; import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers'; import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow/enums-helpers'; @@ -180,9 +181,20 @@ export interface WorkflowInfo { * task was completed by a worker without a Build ID. If this worker is the one executing this * task for the first time and has a Build ID set, then its ID will be used. This value may change * over the lifetime of the workflow run, but is deterministic and safe to use for branching. + * + * @deprecated Use `currentDeploymentVersion` instead */ readonly currentBuildId?: string; + /** + * The Deployment Version of the worker which executed the current Workflow Task. May be undefined + * if the task was completed by a worker without a Deployment Version. If this worker is the one + * executing this task for the first time and has a Deployment Version set, then its ID will be + * used. This value may change over the lifetime of the workflow run, but is deterministic and + * safe to use for branching. + */ + readonly currentDeploymentVersion?: WorkerDeploymentVersion; + readonly unsafe: UnsafeWorkflowInfo; /** From 70df3db6caecfecfbb31c3efd2041f807947809a Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 14 Apr 2025 14:01:00 -0700 Subject: [PATCH 03/11] First test / options passthrough --- packages/test/src/helpers-integration.ts | 6 + .../src/test-worker-deployment-versioning.ts | 172 ++++++++++++++++++ .../src/workflows/deployment-versioning.ts | 16 ++ packages/worker/src/worker-options.ts | 2 + packages/workflow/src/interfaces.ts | 18 ++ packages/workflow/src/workflow.ts | 18 ++ 6 files changed, 232 insertions(+) create mode 100644 packages/test/src/test-worker-deployment-versioning.ts create mode 100644 packages/test/src/workflows/deployment-versioning.ts diff --git a/packages/test/src/helpers-integration.ts b/packages/test/src/helpers-integration.ts index e932a1f66..941211065 100644 --- a/packages/test/src/helpers-integration.ts +++ b/packages/test/src/helpers-integration.ts @@ -41,6 +41,7 @@ const defaultDynamicConfigOptions = [ 'frontend.workerVersioningDataAPIs=true', 'frontend.workerVersioningWorkflowAPIs=true', 'system.enableActivityEagerExecution=true', + 'system.enableDeploymentVersions=true', 'system.enableEagerWorkflowStart=true', 'system.forceSearchAttributesCacheRefreshOnRead=true', 'worker.buildIdScavengerEnabled=true', @@ -100,6 +101,11 @@ export async function createLocalTestEnvironment( ...(opts || {}), // Use provided options or default to an empty object server: { searchAttributes: Object.values(defaultSAKeys), + // TODO: Remove after next CLI release + executable: { + type: 'cached-download', + version: 'v1.3.1-priority.0', + }, ...(opts?.server || {}), // Use provided server options or default to an empty object extraArgs: [ ...defaultDynamicConfigOptions.flatMap((opt) => ['--dynamic-config-value', opt]), diff --git a/packages/test/src/test-worker-deployment-versioning.ts b/packages/test/src/test-worker-deployment-versioning.ts new file mode 100644 index 000000000..4584b8827 --- /dev/null +++ b/packages/test/src/test-worker-deployment-versioning.ts @@ -0,0 +1,172 @@ +/** + * Tests worker-deployment-based versioning + * + * @module + */ +import assert from 'assert'; +import { randomUUID } from 'crypto'; +import asyncRetry from 'async-retry'; +import { Client } from '@temporalio/client'; +import { Worker } from './helpers'; +import * as activities from './activities'; +import { WorkerDeploymentVersion } from '@temporalio/common'; +import { makeTestFunction } from './helpers-integration'; + +const test = makeTestFunction({ workflowsPath: __filename }); + +test('Worker deployment based versioning', async (t) => { + const taskQueue = 'worker-deployment-based-versioning-' + randomUUID(); + const deploymentName = 'deployment-' + randomUUID(); + const client = t.context.env.client; + + const w1DeploymentVersion = { + buildId: '1.0', + deploymentName: deploymentName, + }; + const w2DeploymentVersion = { + buildId: '2.0', + deploymentName: deploymentName, + }; + const w3DeploymentVersion = { + buildId: '3.0', + deploymentName: deploymentName, + }; + + const worker1 = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + activities, + taskQueue, + workerDeploymentOptions: { + useWorkerVersioning: true, + version: w1DeploymentVersion, + }, + }); + const worker1Promise = worker1.run(); + worker1Promise.catch((err) => { + t.fail('Worker 1.0 run error: ' + JSON.stringify(err)); + }); + + const worker2 = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + activities, + taskQueue, + workerDeploymentOptions: { + useWorkerVersioning: true, + version: w2DeploymentVersion, + }, + }); + const worker2Promise = worker2.run(); + worker2Promise.catch((err) => { + t.fail('Worker 2.0 run error: ' + JSON.stringify(err)); + }); + + const worker3 = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + activities, + taskQueue, + workerDeploymentOptions: { + useWorkerVersioning: true, + version: w3DeploymentVersion, + }, + }); + const worker3Promise = worker3.run(); + worker3Promise.catch((err) => { + t.fail('Worker 3.0 run error: ' + JSON.stringify(err)); + }); + + // Wait for worker 1 to be visible and set as current version + const describeResp1 = await waitUntilWorkerDeploymentVisible(client, w1DeploymentVersion); + await setCurrentDeploymentVersion(client, describeResp1.conflictToken, w1DeploymentVersion); + + // Start workflow 1 which will use the 1.0 worker on auto-upgrade + const wf1 = await client.workflow.start('autoUpgradeWorkflow', { + taskQueue, + workflowId: 'basic-versioning-v1-' + randomUUID(), + }); + const state1 = await wf1.query('state'); + assert.equal(state1, 'v1'); + + // Wait for worker 2 to be visible and set as current version + const describeResp2 = await waitUntilWorkerDeploymentVisible(client, w2DeploymentVersion); + await setCurrentDeploymentVersion(client, describeResp2.conflictToken, w2DeploymentVersion); + + // Start workflow 2 which will use the 2.0 worker pinned + const wf2 = await client.workflow.start('pinnedWorkflow', { + taskQueue, + workflowId: 'basic-versioning-v2-' + randomUUID(), + }); + const state2 = await wf2.query('state'); + assert.equal(state2, 'v2'); + + // Wait for worker 3 to be visible and set as current version + const describeResp3 = await waitUntilWorkerDeploymentVisible(client, w3DeploymentVersion); + await setCurrentDeploymentVersion(client, describeResp3.conflictToken, w3DeploymentVersion); + + // Start workflow 3 which will use the 3.0 worker on auto-upgrade + const wf3 = await client.workflow.start('autoUpgradeWorkflow', { + taskQueue, + workflowId: 'basic-versioning-v3-' + randomUUID(), + }); + const state3 = await wf3.query('state'); + assert.equal(state3, 'v3'); + + // Signal all workflows to finish + await wf1.signal('doFinish'); + await wf2.signal('doFinish'); + await wf3.signal('doFinish'); + + const res1 = await wf1.result(); + const res2 = await wf2.result(); + const res3 = await wf3.result(); + + assert.equal(res1, 'version-v3'); + assert.equal(res2, 'version-v2'); + assert.equal(res3, 'version-v3'); + + worker1.shutdown(); + worker2.shutdown(); + worker3.shutdown(); + await worker1Promise; + await worker2Promise; + await worker3Promise; + t.pass(); +}); + +async function waitUntilWorkerDeploymentVisible(client: Client, version: WorkerDeploymentVersion) { + return await asyncRetry( + async () => { + try { + const resp = await client.workflowService.describeWorkerDeployment({ + namespace: client.options.namespace, + deploymentName: version.deploymentName, + }); + + const isVersionVisible = resp.workerDeploymentInfo!.versionSummaries!.some( + (vs) => vs.version === version.buildId + ); + + if (!isVersionVisible) { + throw new Error('Version not visible yet'); + } + + return resp; + } catch (error) { + throw error; + } + }, + { maxTimeout: 1000, retries: 10 } + ); +} + +async function setCurrentDeploymentVersion( + client: Client, + conflictToken: Uint8Array, + version: WorkerDeploymentVersion +) { + return await client.workflowService.setWorkerDeploymentCurrentVersion({ + namespace: client.options.namespace, + deploymentName: version.deploymentName, + version: version.buildId, + conflictToken, + }); +} diff --git a/packages/test/src/workflows/deployment-versioning.ts b/packages/test/src/workflows/deployment-versioning.ts new file mode 100644 index 000000000..5c9312c66 --- /dev/null +++ b/packages/test/src/workflows/deployment-versioning.ts @@ -0,0 +1,16 @@ +import { CancelledFailure, defineQuery, setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; +import { unblockSignal } from './definitions'; + +export const versionQuery = defineQuery('version'); + +export const deploymentVersioningV1AutoUpgrade = defineWorkflowWithOptions( + { versioningBehavior: 'auto-upgrade' }, + _deploymentVersioningV1AutoUpgrade +); +async function _deploymentVersioningV1AutoUpgrade(): Promise { + let doFinish = false; + setHandler(unblockSignal, () => void (doFinish = true)); + setHandler(versionQuery, () => 'v1'); + await condition(() => doFinish); + return 'version-v1'; +} diff --git a/packages/worker/src/worker-options.ts b/packages/worker/src/worker-options.ts index 81e489400..ddeaf25ba 100644 --- a/packages/worker/src/worker-options.ts +++ b/packages/worker/src/worker-options.ts @@ -139,6 +139,8 @@ export interface WorkerOptions { /** * If specified, the default versioning behavior to use for all workflows on this worker. + * If not specified, and `useWorkerVersioning` is true, workflows that do not specify a + * versioning behavior via {@link TODO} will cause an error to be thrown on startup. */ defaultVersioningBehavior?: VersioningBehavior; }; diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts index d36494a74..94c010ce4 100644 --- a/packages/workflow/src/interfaces.ts +++ b/packages/workflow/src/interfaces.ts @@ -14,6 +14,7 @@ import { SearchAttributePair, Priority, WorkerDeploymentVersion, + VersioningBehavior, } from '@temporalio/common'; import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers'; import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow/enums-helpers'; @@ -611,3 +612,20 @@ export interface ActivationCompletion { commands: coresdk.workflow_commands.IWorkflowCommand[]; usedInternalFlags: number[]; } + +/** + * Options that can be used when defining a workflow via {@link defineWorkflowWithOptions}. + */ +export interface WorkflowDefinitionOptions { + versioningBehavior?: VersioningBehavior; +} + +type AsyncFunction = (...args: Args) => Promise; + +/** + * A workflow function that has been defined with options from {@link WorkflowDefinitionOptions}. + */ +export interface WorkflowFunctionWithOptions extends AsyncFunction { + __temporal_is_workflow_function_with_options: true; + options: WorkflowDefinitionOptions; +} diff --git a/packages/workflow/src/workflow.ts b/packages/workflow/src/workflow.ts index b5e6500d2..940b044c6 100644 --- a/packages/workflow/src/workflow.ts +++ b/packages/workflow/src/workflow.ts @@ -58,6 +58,8 @@ import { encodeChildWorkflowCancellationType, encodeParentClosePolicy, DefaultQueryHandler, + WorkflowDefinitionOptions, + WorkflowFunctionWithOptions, } from './interfaces'; import { LocalActivityDoBackoff } from './errors'; import { assertInWorkflowContext, getActivator, maybeGetActivator } from './global-attributes'; @@ -1590,6 +1592,22 @@ export function allHandlersFinished(): boolean { return activator.inProgressSignals.size === 0 && activator.inProgressUpdates.size === 0; } +/** +* Can be used to define workflow functions with certain options specified at definition time. + +* @param options Options for the workflow defintion. +* @param fn The workflow function. +* @returns The same passed in workflow function, with the specified options applied. You can export +* this function to make it available as a workflow function. +*/ +export function defineWorkflowWithOptions( + options: WorkflowDefinitionOptions, + fn: (...args: A) => Promise +): WorkflowFunctionWithOptions { + const wrappedFn = Object.assign(fn, { options, __temporal_is_workflow_function_with_options: true as const }); + return wrappedFn; +} + export const stackTraceQuery = defineQuery('__stack_trace'); export const enhancedStackTraceQuery = defineQuery('__enhanced_stack_trace'); export const workflowMetadataQuery = defineQuery('__temporal_workflow_metadata'); From 84d3ae468e0423355ed27462bc0a11cc6a3ed2f6 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 14 Apr 2025 14:43:39 -0700 Subject: [PATCH 04/11] Need a way to run against extant server --- packages/core-bridge/src/conversions.rs | 2 + packages/test/src/helpers-integration.ts | 9 +++- packages/test/src/helpers.ts | 7 +++ packages/testing/src/index.ts | 62 ++++++++++++++++++------ 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/packages/core-bridge/src/conversions.rs b/packages/core-bridge/src/conversions.rs index 504807168..29ea0aff0 100644 --- a/packages/core-bridge/src/conversions.rs +++ b/packages/core-bridge/src/conversions.rs @@ -560,6 +560,8 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> { } }; + dbg!("!!!! versioning strategy is: {:?}", &versioning_strategy); + match WorkerConfigBuilder::default() .versioning_strategy(versioning_strategy) .client_identity_override(Some(js_value_getter!(cx, self, "identity", JsString))) diff --git a/packages/test/src/helpers-integration.ts b/packages/test/src/helpers-integration.ts index 941211065..f46c88cb0 100644 --- a/packages/test/src/helpers-integration.ts +++ b/packages/test/src/helpers-integration.ts @@ -140,7 +140,14 @@ export function makeTestFunction(opts: { return makeConfigurableEnvironmentTestFn({ recordedLogs: opts.recordedLogs, createTestContext: async (_t: ExecutionContext): Promise => { - const env = await createLocalTestEnvironment(opts.workflowEnvironmentOpts); + let env: TestWorkflowEnvironment; + if (process.env.TEMPORAL_SERVICE_ADDRESS) { + env = await TestWorkflowEnvironment.createExistingServer({ + address: process.env.TEMPORAL_SERVICE_ADDRESS, + }); + } else { + env = await createLocalTestEnvironment(opts.workflowEnvironmentOpts); + } return { workflowBundle: await createTestWorkflowBundle({ workflowsPath: opts.workflowsPath, diff --git a/packages/test/src/helpers.ts b/packages/test/src/helpers.ts index 0190ecd54..d15bbea91 100644 --- a/packages/test/src/helpers.ts +++ b/packages/test/src/helpers.ts @@ -11,6 +11,7 @@ import { Payload, PayloadCodec } from '@temporalio/common'; import { historyToJSON } from '@temporalio/common/lib/proto-utils'; import * as iface from '@temporalio/proto'; import { + ExistingServerTestWorkflowEnvironmentOptions, LocalTestWorkflowEnvironmentOptions, TestWorkflowEnvironment as RealTestWorkflowEnvironment, TimeSkippingTestWorkflowEnvironmentOptions, @@ -213,6 +214,12 @@ export class TestWorkflowEnvironment extends RealTestWorkflowEnvironment { : undefined), }); } + + static async createExistingServer( + opts?: ExistingServerTestWorkflowEnvironmentOptions + ): Promise { + return RealTestWorkflowEnvironment.createExistingServer(opts); + } } // Some of our tests expect "default custom search attributes" to exists, which used to be the case diff --git a/packages/testing/src/index.ts b/packages/testing/src/index.ts index ff56e6165..c366d32d2 100644 --- a/packages/testing/src/index.ts +++ b/packages/testing/src/index.ts @@ -130,7 +130,7 @@ export type ClientOptionsForTestEnv = Omit; +/** + * Options for {@link TestWorkflowEnvironment.createExistingServer} + */ +export type ExistingServerTestWorkflowEnvironmentOptions = { + /** If not set, defaults to localhost:7233 */ + address?: string; + client?: ClientOptions; +}; + +export type TestWorkflowEnvironmentOptionsWithDefaults = Required> & + TestWorkflowEnvironmentOptions; function addDefaults(opts: TestWorkflowEnvironmentOptions): TestWorkflowEnvironmentOptionsWithDefaults { return { @@ -204,7 +214,7 @@ export class TestWorkflowEnvironment { protected constructor( public readonly options: TestWorkflowEnvironmentOptionsWithDefaults, public readonly supportsTimeSkipping: boolean, - protected readonly server: EphemeralServer, + protected readonly server: EphemeralServer | undefined, connection: Connection, nativeConnection: NativeConnection, namespace: string | undefined @@ -241,7 +251,7 @@ export class TestWorkflowEnvironment { * environment, not to the workflow under test. We highly recommend running tests serially when using a single * environment or creating a separate environment per test. * - * By default, the latest release of the Test Serveer will be downloaded and cached to a temporary directory + * By default, the latest release of the Test Server will be downloaded and cached to a temporary directory * (e.g. `$TMPDIR/temporal-test-server-sdk-typescript-*` or `%TEMP%/temporal-test-server-sdk-typescript-*.exe`). Note * that existing cached binairies will be reused without validation that they are still up-to-date, until the SDK * itself is updated. Alternatively, a specific version number of the Test Server may be provided, or the path to an @@ -292,6 +302,17 @@ export class TestWorkflowEnvironment { }); } + static async createExistingServer( + opts?: ExistingServerTestWorkflowEnvironmentOptions + ): Promise { + return await this.create({ + server: undefined, + client: opts?.client, + namespace: opts?.client?.namespace ?? 'default', + supportsTimeSkipping: false, + }); + } + /** * Create a new test environment */ @@ -299,24 +320,31 @@ export class TestWorkflowEnvironment { opts: TestWorkflowEnvironmentOptions & { supportsTimeSkipping: boolean; namespace?: string; + address?: string; } ): Promise { const { supportsTimeSkipping, namespace, ...rest } = opts; const optsWithDefaults = addDefaults(filterNullAndUndefined(rest)); - // Add search attributes to CLI server arguments - if ('searchAttributes' in optsWithDefaults.server && optsWithDefaults.server.searchAttributes) { - let newArgs: string[] = []; - for (const { name, type } of optsWithDefaults.server.searchAttributes) { - newArgs.push('--search-attribute'); - newArgs.push(`${name}=${TypedSearchAttributes.toMetadataType(type)}`); + let address: string; + let server: EphemeralServer | undefined; + if (optsWithDefaults.server !== undefined) { + // Add search attributes to CLI server arguments + if ('searchAttributes' in optsWithDefaults.server && optsWithDefaults.server.searchAttributes) { + let newArgs: string[] = []; + for (const { name, type } of optsWithDefaults.server.searchAttributes) { + newArgs.push('--search-attribute'); + newArgs.push(`${name}=${TypedSearchAttributes.toMetadataType(type)}`); + } + newArgs = newArgs.concat(optsWithDefaults.server.extraArgs ?? []); + optsWithDefaults.server.extraArgs = newArgs; } - newArgs = newArgs.concat(optsWithDefaults.server.extraArgs ?? []); - optsWithDefaults.server.extraArgs = newArgs; - } - const server = await Runtime.instance().createEphemeralServer(optsWithDefaults.server); - const address = getEphemeralServerTarget(server); + server = await Runtime.instance().createEphemeralServer(optsWithDefaults.server); + address = getEphemeralServerTarget(server); + } else { + address = opts.address ?? 'localhost:7233'; + } const nativeConnection = await NativeConnection.connect({ address }); const connection = await Connection.connect({ address }); @@ -330,7 +358,9 @@ export class TestWorkflowEnvironment { async teardown(): Promise { await this.connection.close(); await this.nativeConnection.close(); - await Runtime.instance().shutdownEphemeralServer(this.server); + if (this.server !== undefined) { + await Runtime.instance().shutdownEphemeralServer(this.server); + } } /** From f4cddf1a963ad721b793c6cec6aad4a9284fcc97 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 14 Apr 2025 16:30:21 -0700 Subject: [PATCH 05/11] Versioning behavior passed through properly / definition changes --- packages/common/src/index.ts | 1 + packages/common/src/interfaces.ts | 1 + packages/common/src/worker-deployments.ts | 8 +- .../common/src/workflow-definition-options.ts | 18 ++ packages/common/src/workflow-options.ts | 9 +- packages/core-bridge/src/conversions.rs | 2 - .../src/deployment-versioning-v1/index.ts | 11 ++ .../src/deployment-versioning-v2/index.ts | 11 ++ .../src/deployment-versioning-v3/index.ts | 11 ++ .../src/test-worker-deployment-versioning.ts | 181 ++++++++++++++---- packages/test/src/workflows/definitions.ts | 3 +- .../src/workflows/deployment-versioning.ts | 16 -- packages/worker/src/worker.ts | 2 +- packages/workflow/src/interfaces.ts | 20 +- packages/workflow/src/internals.ts | 15 +- packages/workflow/src/worker-interface.ts | 7 +- packages/workflow/src/workflow.ts | 8 +- 17 files changed, 242 insertions(+), 82 deletions(-) create mode 100644 packages/common/src/workflow-definition-options.ts create mode 100644 packages/test/src/deployment-versioning-v1/index.ts create mode 100644 packages/test/src/deployment-versioning-v2/index.ts create mode 100644 packages/test/src/deployment-versioning-v3/index.ts delete mode 100644 packages/test/src/workflows/deployment-versioning.ts diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 04e963079..7aecd6dc7 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -23,6 +23,7 @@ export * from './priority'; export * from './retry-policy'; export type { Timestamp, Duration, StringValue } from './time'; export * from './worker-deployments'; +export * from './workflow-definition-options'; export * from './workflow-handle'; export * from './workflow-options'; export * from './versioning-intent'; diff --git a/packages/common/src/interfaces.ts b/packages/common/src/interfaces.ts index 34becc49b..f66a7ed82 100644 --- a/packages/common/src/interfaces.ts +++ b/packages/common/src/interfaces.ts @@ -1,4 +1,5 @@ import type { temporal } from '@temporalio/proto'; +import { WorkflowFunctionWithOptions } from './workflow-definition-options'; export type Payload = temporal.api.common.v1.IPayload; diff --git a/packages/common/src/worker-deployments.ts b/packages/common/src/worker-deployments.ts index 8023be01f..4722dd9ea 100644 --- a/packages/common/src/worker-deployments.ts +++ b/packages/common/src/worker-deployments.ts @@ -4,8 +4,12 @@ * @experimental Worker deployments are experimental */ export interface WorkerDeploymentVersion { - buildId: string; - deploymentName: string; + readonly buildId: string; + readonly deploymentName: string; +} + +export function toCanonicalString(version: WorkerDeploymentVersion): string { + return `${version.deploymentName}.${version.buildId}`; } export type VersioningBehavior = 'pinned' | 'auto-upgrade'; diff --git a/packages/common/src/workflow-definition-options.ts b/packages/common/src/workflow-definition-options.ts new file mode 100644 index 000000000..979d2b20a --- /dev/null +++ b/packages/common/src/workflow-definition-options.ts @@ -0,0 +1,18 @@ +import { VersioningBehavior } from './worker-deployments'; + +/** + * Options that can be used when defining a workflow via {@link defineWorkflowWithOptions}. + */ +export interface WorkflowDefinitionOptions { + versioningBehavior?: VersioningBehavior; +} + +type AsyncFunction = (...args: Args) => Promise; + +/** + * A workflow function that has been defined with options from {@link WorkflowDefinitionOptions}. + */ +export interface WorkflowFunctionWithOptions extends AsyncFunction { + __temporal_is_workflow_function_with_options: true; + options: WorkflowDefinitionOptions; +} diff --git a/packages/common/src/workflow-options.ts b/packages/common/src/workflow-options.ts index aeb3aa349..52d06bc3d 100644 --- a/packages/common/src/workflow-options.ts +++ b/packages/common/src/workflow-options.ts @@ -5,6 +5,7 @@ import { Duration } from './time'; import { makeProtoEnumConverters } from './internal-workflow'; import { SearchAttributePair, SearchAttributes, TypedSearchAttributes } from './search-attributes'; import { Priority } from './priority'; +import { WorkflowFunctionWithOptions } from './workflow-definition-options'; /** * Defines what happens when trying to start a Workflow with the same ID as a *Closed* Workflow. @@ -243,7 +244,9 @@ export interface WorkflowDurationOptions { export type CommonWorkflowOptions = BaseWorkflowOptions & WorkflowDurationOptions; -export function extractWorkflowType(workflowTypeOrFunc: string | T): string { +export function extractWorkflowType( + workflowTypeOrFunc: string | T | WorkflowFunctionWithOptions +): string { if (typeof workflowTypeOrFunc === 'string') return workflowTypeOrFunc as string; if (typeof workflowTypeOrFunc === 'function') { if (workflowTypeOrFunc?.name) return workflowTypeOrFunc.name; @@ -253,3 +256,7 @@ export function extractWorkflowType(workflowTypeOrFunc: stri `Invalid workflow type: expected either a string or a function, got '${typeof workflowTypeOrFunc}'` ); } + +export function isWorkflowFunctionWithOptions(obj: any): obj is WorkflowFunctionWithOptions { + return obj.__temporal_is_workflow_function_with_options === true; +} diff --git a/packages/core-bridge/src/conversions.rs b/packages/core-bridge/src/conversions.rs index 29ea0aff0..504807168 100644 --- a/packages/core-bridge/src/conversions.rs +++ b/packages/core-bridge/src/conversions.rs @@ -560,8 +560,6 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> { } }; - dbg!("!!!! versioning strategy is: {:?}", &versioning_strategy); - match WorkerConfigBuilder::default() .versioning_strategy(versioning_strategy) .client_identity_override(Some(js_value_getter!(cx, self, "identity", JsString))) diff --git a/packages/test/src/deployment-versioning-v1/index.ts b/packages/test/src/deployment-versioning-v1/index.ts new file mode 100644 index 000000000..955bf5710 --- /dev/null +++ b/packages/test/src/deployment-versioning-v1/index.ts @@ -0,0 +1,11 @@ +import { CancelledFailure, setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; +import { unblockSignal, versionQuery } from '../workflows'; + +defineWorkflowWithOptions({ versioningBehavior: 'auto-upgrade' }, deploymentVersioning); +export async function deploymentVersioning(): Promise { + let doFinish = false; + setHandler(unblockSignal, () => void (doFinish = true)); + setHandler(versionQuery, () => 'v1'); + await condition(() => doFinish); + return 'version-v1'; +} diff --git a/packages/test/src/deployment-versioning-v2/index.ts b/packages/test/src/deployment-versioning-v2/index.ts new file mode 100644 index 000000000..d10ca5d24 --- /dev/null +++ b/packages/test/src/deployment-versioning-v2/index.ts @@ -0,0 +1,11 @@ +import { CancelledFailure, setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; +import { unblockSignal, versionQuery } from '../workflows'; + +defineWorkflowWithOptions({ versioningBehavior: 'pinned' }, deploymentVersioning); +export async function deploymentVersioning(): Promise { + let doFinish = false; + setHandler(unblockSignal, () => void (doFinish = true)); + setHandler(versionQuery, () => 'v2'); + await condition(() => doFinish); + return 'version-v2'; +} diff --git a/packages/test/src/deployment-versioning-v3/index.ts b/packages/test/src/deployment-versioning-v3/index.ts new file mode 100644 index 000000000..eab1060fb --- /dev/null +++ b/packages/test/src/deployment-versioning-v3/index.ts @@ -0,0 +1,11 @@ +import { CancelledFailure, setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; +import { unblockSignal, versionQuery } from '../workflows'; + +defineWorkflowWithOptions({ versioningBehavior: 'auto-upgrade' }, deploymentVersioning); +export async function deploymentVersioning(): Promise { + let doFinish = false; + setHandler(unblockSignal, () => void (doFinish = true)); + setHandler(versionQuery, () => 'v3'); + await condition(() => doFinish); + return 'version-v3'; +} diff --git a/packages/test/src/test-worker-deployment-versioning.ts b/packages/test/src/test-worker-deployment-versioning.ts index 4584b8827..5431a70bc 100644 --- a/packages/test/src/test-worker-deployment-versioning.ts +++ b/packages/test/src/test-worker-deployment-versioning.ts @@ -9,8 +9,9 @@ import asyncRetry from 'async-retry'; import { Client } from '@temporalio/client'; import { Worker } from './helpers'; import * as activities from './activities'; -import { WorkerDeploymentVersion } from '@temporalio/common'; +import { toCanonicalString, WorkerDeploymentVersion } from '@temporalio/common'; import { makeTestFunction } from './helpers-integration'; +import { unblockSignal, versionQuery } from './workflows/'; const test = makeTestFunction({ workflowsPath: __filename }); @@ -33,7 +34,7 @@ test('Worker deployment based versioning', async (t) => { }; const worker1 = await Worker.create({ - workflowsPath: require.resolve('./workflows'), + workflowsPath: require.resolve('./deployment-versioning-v1'), activities, taskQueue, workerDeploymentOptions: { @@ -47,7 +48,7 @@ test('Worker deployment based versioning', async (t) => { }); const worker2 = await Worker.create({ - workflowsPath: require.resolve('./workflows'), + workflowsPath: require.resolve('./deployment-versioning-v2'), activities, taskQueue, workerDeploymentOptions: { @@ -61,7 +62,7 @@ test('Worker deployment based versioning', async (t) => { }); const worker3 = await Worker.create({ - workflowsPath: require.resolve('./workflows'), + workflowsPath: require.resolve('./deployment-versioning-v3'), activities, taskQueue, workerDeploymentOptions: { @@ -79,11 +80,11 @@ test('Worker deployment based versioning', async (t) => { await setCurrentDeploymentVersion(client, describeResp1.conflictToken, w1DeploymentVersion); // Start workflow 1 which will use the 1.0 worker on auto-upgrade - const wf1 = await client.workflow.start('autoUpgradeWorkflow', { + const wf1 = await client.workflow.start('deploymentVersioning', { taskQueue, - workflowId: 'basic-versioning-v1-' + randomUUID(), + workflowId: 'deployment-versioning-v1-' + randomUUID(), }); - const state1 = await wf1.query('state'); + const state1 = await wf1.query(versionQuery); assert.equal(state1, 'v1'); // Wait for worker 2 to be visible and set as current version @@ -91,11 +92,11 @@ test('Worker deployment based versioning', async (t) => { await setCurrentDeploymentVersion(client, describeResp2.conflictToken, w2DeploymentVersion); // Start workflow 2 which will use the 2.0 worker pinned - const wf2 = await client.workflow.start('pinnedWorkflow', { + const wf2 = await client.workflow.start('deploymentVersioning', { taskQueue, - workflowId: 'basic-versioning-v2-' + randomUUID(), + workflowId: 'deployment-versioning-v2-' + randomUUID(), }); - const state2 = await wf2.query('state'); + const state2 = await wf2.query(versionQuery); assert.equal(state2, 'v2'); // Wait for worker 3 to be visible and set as current version @@ -103,17 +104,17 @@ test('Worker deployment based versioning', async (t) => { await setCurrentDeploymentVersion(client, describeResp3.conflictToken, w3DeploymentVersion); // Start workflow 3 which will use the 3.0 worker on auto-upgrade - const wf3 = await client.workflow.start('autoUpgradeWorkflow', { + const wf3 = await client.workflow.start('deploymentVersioning', { taskQueue, - workflowId: 'basic-versioning-v3-' + randomUUID(), + workflowId: 'deployment-versioning-v3-' + randomUUID(), }); - const state3 = await wf3.query('state'); + const state3 = await wf3.query(versionQuery); assert.equal(state3, 'v3'); // Signal all workflows to finish - await wf1.signal('doFinish'); - await wf2.signal('doFinish'); - await wf3.signal('doFinish'); + await wf1.signal(unblockSignal); + await wf2.signal(unblockSignal); + await wf3.signal(unblockSignal); const res1 = await wf1.result(); const res2 = await wf2.result(); @@ -132,27 +133,139 @@ test('Worker deployment based versioning', async (t) => { t.pass(); }); +test('Worker deployment based versioning with ramping', async (t) => { + const taskQueue = 'worker-deployment-based-ramping-' + randomUUID(); + const deploymentName = 'deployment-ramping-' + randomUUID(); + const client = t.context.env.client; + + const v1 = { + buildId: '1.0', + deploymentName: deploymentName, + }; + const v2 = { + buildId: '2.0', + deploymentName: deploymentName, + }; + + const worker1 = await Worker.create({ + workflowsPath: require.resolve('./deployment-versioning-v1'), + activities, + taskQueue, + workerDeploymentOptions: { + useWorkerVersioning: true, + version: v1, + }, + }); + const worker1Promise = worker1.run(); + worker1Promise.catch((err) => { + t.fail('Worker 1.0 run error: ' + JSON.stringify(err)); + }); + + const worker2 = await Worker.create({ + workflowsPath: require.resolve('./deployment-versioning-v2'), + activities, + taskQueue, + workerDeploymentOptions: { + useWorkerVersioning: true, + version: v2, + }, + }); + const worker2Promise = worker2.run(); + worker2Promise.catch((err) => { + t.fail('Worker 2.0 run error: ' + JSON.stringify(err)); + }); + + // Wait for worker deployments to be visible + await waitUntilWorkerDeploymentVisible(client, v1); + const describeResp = await waitUntilWorkerDeploymentVisible(client, v2); + + // Set current version to v1 and ramp v2 to 100% + let conflictToken = (await setCurrentDeploymentVersion(client, describeResp.conflictToken, v1)).conflictToken; + conflictToken = (await setRampingVersion(client, conflictToken, v2, 100)).conflictToken; + + // Run workflows and verify they run on v2 + for (let i = 0; i < 3; i++) { + const wf = await client.workflow.start('deploymentVersioning', { + taskQueue, + workflowId: `versioning-ramp-100-${i}-${randomUUID()}`, + }); + await wf.signal(unblockSignal); + const res = await wf.result(); + assert.equal(res, 'version-v2'); + } + + // Set ramp to 0, expecting workflows to run on v1 + conflictToken = (await setRampingVersion(client, conflictToken, v2, 0)).conflictToken; + for (let i = 0; i < 3; i++) { + const wf = await client.workflow.start('deploymentVersioning', { + taskQueue, + workflowId: `versioning-ramp-0-${i}-${randomUUID()}`, + }); + await wf.signal(unblockSignal); + const res = await wf.result(); + assert.equal(res, 'version-v1'); + } + + // Set ramp to 50 and eventually verify workflows run on both versions + await setRampingVersion(client, conflictToken, v2, 50); + const seenResults = new Set(); + + const runAndRecord = async () => { + const wf = await client.workflow.start('deploymentVersioning', { + taskQueue, + workflowId: `versioning-ramp-50-${randomUUID()}`, + }); + await wf.signal(unblockSignal); + return await wf.result(); + }; + + await asyncRetry( + async () => { + const res = await runAndRecord(); + seenResults.add(res); + if (!seenResults.has('version-v1') || !seenResults.has('version-v2')) { + throw new Error('Not all versions seen yet'); + } + }, + { maxTimeout: 1000, retries: 20 } + ); + + worker1.shutdown(); + worker2.shutdown(); + await worker1Promise; + await worker2Promise; + t.pass(); +}); + +async function setRampingVersion( + client: Client, + conflictToken: Uint8Array, + version: WorkerDeploymentVersion, + percentage: number +) { + return await client.workflowService.setWorkerDeploymentRampingVersion({ + namespace: client.options.namespace, + deploymentName: version.deploymentName, + version: toCanonicalString(version), + conflictToken, + percentage, + }); +} + async function waitUntilWorkerDeploymentVisible(client: Client, version: WorkerDeploymentVersion) { return await asyncRetry( async () => { - try { - const resp = await client.workflowService.describeWorkerDeployment({ - namespace: client.options.namespace, - deploymentName: version.deploymentName, - }); - - const isVersionVisible = resp.workerDeploymentInfo!.versionSummaries!.some( - (vs) => vs.version === version.buildId - ); - - if (!isVersionVisible) { - throw new Error('Version not visible yet'); - } - - return resp; - } catch (error) { - throw error; + const resp = await client.workflowService.describeWorkerDeployment({ + namespace: client.options.namespace, + deploymentName: version.deploymentName, + }); + const isVersionVisible = resp.workerDeploymentInfo!.versionSummaries!.some( + (vs) => vs.version === toCanonicalString(version) + ); + if (!isVersionVisible) { + throw new Error('Version not visible yet'); } + return resp; }, { maxTimeout: 1000, retries: 10 } ); @@ -166,7 +279,7 @@ async function setCurrentDeploymentVersion( return await client.workflowService.setWorkerDeploymentCurrentVersion({ namespace: client.options.namespace, deploymentName: version.deploymentName, - version: version.buildId, + version: toCanonicalString(version), conflictToken, }); } diff --git a/packages/test/src/workflows/definitions.ts b/packages/test/src/workflows/definitions.ts index e5aa77e01..b1056fda1 100644 --- a/packages/test/src/workflows/definitions.ts +++ b/packages/test/src/workflows/definitions.ts @@ -1,8 +1,9 @@ /* eslint-disable no-duplicate-imports */ -import { defineSignal } from '@temporalio/workflow'; +import { defineQuery, defineSignal } from '@temporalio/workflow'; export const activityStartedSignal = defineSignal('activityStarted'); export const failSignal = defineSignal('fail'); export const failWithMessageSignal = defineSignal<[string]>('fail'); export const argsTestSignal = defineSignal<[number, string]>('argsTest'); export const unblockSignal = defineSignal('unblock'); +export const versionQuery = defineQuery('version'); diff --git a/packages/test/src/workflows/deployment-versioning.ts b/packages/test/src/workflows/deployment-versioning.ts deleted file mode 100644 index 5c9312c66..000000000 --- a/packages/test/src/workflows/deployment-versioning.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { CancelledFailure, defineQuery, setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; -import { unblockSignal } from './definitions'; - -export const versionQuery = defineQuery('version'); - -export const deploymentVersioningV1AutoUpgrade = defineWorkflowWithOptions( - { versioningBehavior: 'auto-upgrade' }, - _deploymentVersioningV1AutoUpgrade -); -async function _deploymentVersioningV1AutoUpgrade(): Promise { - let doFinish = false; - setHandler(unblockSignal, () => void (doFinish = true)); - setHandler(versionQuery, () => 'v1'); - await condition(() => doFinish); - return 'version-v1'; -} diff --git a/packages/worker/src/worker.ts b/packages/worker/src/worker.ts index 518c960ad..bbff78a2a 100644 --- a/packages/worker/src/worker.ts +++ b/packages/worker/src/worker.ts @@ -1272,7 +1272,7 @@ export class Worker { priority, } = initWorkflowJob; - // Note that we can't do payload convertion here, as there's no guarantee that converted payloads would be safe to + // Note that we can't do payload conversion here, as there's no guarantee that converted payloads would be safe to // transfer through the V8 message port. Those will therefore be set in the Activator's initializeWorkflow job handler. const workflowInfo: WorkflowInfo = { workflowId, diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts index 94c010ce4..706e90d39 100644 --- a/packages/workflow/src/interfaces.ts +++ b/packages/workflow/src/interfaces.ts @@ -18,7 +18,7 @@ import { } from '@temporalio/common'; import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers'; import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow/enums-helpers'; -import type { coresdk } from '@temporalio/proto'; +import type { coresdk, temporal } from '@temporalio/proto'; /** * Workflow Execution information @@ -611,21 +611,5 @@ export type UpdateHandlerOptions = { export interface ActivationCompletion { commands: coresdk.workflow_commands.IWorkflowCommand[]; usedInternalFlags: number[]; -} - -/** - * Options that can be used when defining a workflow via {@link defineWorkflowWithOptions}. - */ -export interface WorkflowDefinitionOptions { - versioningBehavior?: VersioningBehavior; -} - -type AsyncFunction = (...args: Args) => Promise; - -/** - * A workflow function that has been defined with options from {@link WorkflowDefinitionOptions}. - */ -export interface WorkflowFunctionWithOptions extends AsyncFunction { - __temporal_is_workflow_function_with_options: true; - options: WorkflowDefinitionOptions; + versioningBehavior?: temporal.api.enums.v1.VersioningBehavior; } diff --git a/packages/workflow/src/internals.ts b/packages/workflow/src/internals.ts index acc49985b..5b75a849b 100644 --- a/packages/workflow/src/internals.ts +++ b/packages/workflow/src/internals.ts @@ -20,6 +20,8 @@ import { WorkflowUpdateValidatorType, mapFromPayloads, fromPayloadsAtIndex, + WorkflowFunctionWithOptions, + VersioningBehavior, } from '@temporalio/common'; import { decodeSearchAttributes, @@ -27,7 +29,7 @@ import { } from '@temporalio/common/lib/converter/payload-search-attributes'; import { composeInterceptors } from '@temporalio/common/lib/interceptors'; import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow'; -import type { coresdk, temporal } from '@temporalio/proto'; +import { coresdk, temporal } from '@temporalio/proto'; import { alea, RNG } from './alea'; import { RootCancellationScope } from './cancellation-scope'; import { UpdateScope } from './update-scope'; @@ -376,7 +378,7 @@ export class Activator implements ActivationHandler { /** * Reference to the current Workflow, initialized when a Workflow is started */ - public workflow?: Workflow; + public workflow?: Workflow | WorkflowFunctionWithOptions; /** * Information about the current Workflow @@ -416,6 +418,8 @@ export class Activator implements ActivationHandler { public readonly registeredActivityNames: Set; + public versioningBehavior?: VersioningBehavior; + constructor({ info, now, @@ -485,9 +489,16 @@ export class Activator implements ActivationHandler { } concludeActivation(): ActivationCompletion { + let versioningBehavior; + if (this.versioningBehavior === 'auto-upgrade') { + versioningBehavior = temporal.api.enums.v1.VersioningBehavior.VERSIONING_BEHAVIOR_AUTO_UPGRADE; + } else if (this.versioningBehavior === 'pinned') { + versioningBehavior = temporal.api.enums.v1.VersioningBehavior.VERSIONING_BEHAVIOR_PINNED; + } return { commands: this.commands.splice(0), usedInternalFlags: [...this.knownFlags], + versioningBehavior: versioningBehavior, }; } diff --git a/packages/workflow/src/worker-interface.ts b/packages/workflow/src/worker-interface.ts index 0d902c565..80bb0df8b 100644 --- a/packages/workflow/src/worker-interface.ts +++ b/packages/workflow/src/worker-interface.ts @@ -3,7 +3,7 @@ * * @module */ -import { IllegalStateError } from '@temporalio/common'; +import { IllegalStateError, isWorkflowFunctionWithOptions } from '@temporalio/common'; import { composeInterceptors } from '@temporalio/common/lib/interceptors'; import { coresdk } from '@temporalio/proto'; import { disableStorage } from './cancellation-scope'; @@ -79,7 +79,10 @@ export function initRuntime(options: WorkflowCreateOptionsInternal): void { const workflowFn = mod[activator.info.workflowType]; const defaultWorkflowFn = mod['default']; - if (typeof workflowFn === 'function') { + if (isWorkflowFunctionWithOptions(workflowFn)) { + activator.workflow = workflowFn; + activator.versioningBehavior = workflowFn.options.versioningBehavior; + } else if (typeof workflowFn === 'function') { activator.workflow = workflowFn; } else if (typeof defaultWorkflowFn === 'function') { activator.workflow = defaultWorkflowFn; diff --git a/packages/workflow/src/workflow.ts b/packages/workflow/src/workflow.ts index 940b044c6..a8c741420 100644 --- a/packages/workflow/src/workflow.ts +++ b/packages/workflow/src/workflow.ts @@ -58,13 +58,12 @@ import { encodeChildWorkflowCancellationType, encodeParentClosePolicy, DefaultQueryHandler, - WorkflowDefinitionOptions, - WorkflowFunctionWithOptions, } from './interfaces'; import { LocalActivityDoBackoff } from './errors'; import { assertInWorkflowContext, getActivator, maybeGetActivator } from './global-attributes'; import { untrackPromise } from './stack-helpers'; import { ChildWorkflowHandle, ExternalWorkflowHandle } from './workflow-handle'; +import { WorkflowDefinitionOptions, WorkflowFunctionWithOptions } from '@temporalio/common'; // Avoid a circular dependency registerSleepImplementation(sleep); @@ -1604,7 +1603,10 @@ export function defineWorkflowWithOptions( options: WorkflowDefinitionOptions, fn: (...args: A) => Promise ): WorkflowFunctionWithOptions { - const wrappedFn = Object.assign(fn, { options, __temporal_is_workflow_function_with_options: true as const }); + const wrappedFn = Object.assign(fn, { + options, + __temporal_is_workflow_function_with_options: true as const, + }); return wrappedFn; } From f8af31f980fa7430bcf64366bde20d8a0ccc2bba Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Tue, 15 Apr 2025 16:13:43 -0700 Subject: [PATCH 06/11] Dynamic workflow support --- packages/common/src/workflow-options.ts | 1 + .../src/deployment-versioning-v1/index.ts | 6 + .../src/test-worker-deployment-versioning.ts | 116 ++++++++++++++++++ packages/worker/src/worker-options.ts | 7 +- packages/workflow/src/worker-interface.ts | 3 + 5 files changed, 129 insertions(+), 4 deletions(-) diff --git a/packages/common/src/workflow-options.ts b/packages/common/src/workflow-options.ts index 52d06bc3d..d725cd506 100644 --- a/packages/common/src/workflow-options.ts +++ b/packages/common/src/workflow-options.ts @@ -258,5 +258,6 @@ export function extractWorkflowType( } export function isWorkflowFunctionWithOptions(obj: any): obj is WorkflowFunctionWithOptions { + if (obj === undefined || obj === null) return false; return obj.__temporal_is_workflow_function_with_options === true; } diff --git a/packages/test/src/deployment-versioning-v1/index.ts b/packages/test/src/deployment-versioning-v1/index.ts index 955bf5710..889f9425d 100644 --- a/packages/test/src/deployment-versioning-v1/index.ts +++ b/packages/test/src/deployment-versioning-v1/index.ts @@ -9,3 +9,9 @@ export async function deploymentVersioning(): Promise { await condition(() => doFinish); return 'version-v1'; } + +// Dynamic/default workflow handler +export default defineWorkflowWithOptions({ versioningBehavior: 'pinned' }, _default); +async function _default(): Promise { + return 'dynamic'; +} diff --git a/packages/test/src/test-worker-deployment-versioning.ts b/packages/test/src/test-worker-deployment-versioning.ts index 5431a70bc..dfef4d367 100644 --- a/packages/test/src/test-worker-deployment-versioning.ts +++ b/packages/test/src/test-worker-deployment-versioning.ts @@ -12,6 +12,7 @@ import * as activities from './activities'; import { toCanonicalString, WorkerDeploymentVersion } from '@temporalio/common'; import { makeTestFunction } from './helpers-integration'; import { unblockSignal, versionQuery } from './workflows/'; +import { temporal } from '@temporalio/proto'; const test = makeTestFunction({ workflowsPath: __filename }); @@ -40,6 +41,7 @@ test('Worker deployment based versioning', async (t) => { workerDeploymentOptions: { useWorkerVersioning: true, version: w1DeploymentVersion, + defaultVersioningBehavior: 'pinned', }, }); const worker1Promise = worker1.run(); @@ -54,6 +56,7 @@ test('Worker deployment based versioning', async (t) => { workerDeploymentOptions: { useWorkerVersioning: true, version: w2DeploymentVersion, + defaultVersioningBehavior: 'pinned', }, }); const worker2Promise = worker2.run(); @@ -68,6 +71,7 @@ test('Worker deployment based versioning', async (t) => { workerDeploymentOptions: { useWorkerVersioning: true, version: w3DeploymentVersion, + defaultVersioningBehavior: 'pinned', }, }); const worker3Promise = worker3.run(); @@ -154,6 +158,7 @@ test('Worker deployment based versioning with ramping', async (t) => { workerDeploymentOptions: { useWorkerVersioning: true, version: v1, + defaultVersioningBehavior: 'pinned', }, }); const worker1Promise = worker1.run(); @@ -168,6 +173,7 @@ test('Worker deployment based versioning with ramping', async (t) => { workerDeploymentOptions: { useWorkerVersioning: true, version: v2, + defaultVersioningBehavior: 'pinned', }, }); const worker2Promise = worker2.run(); @@ -237,6 +243,116 @@ test('Worker deployment based versioning with ramping', async (t) => { t.pass(); }); +test('Worker deployment with dynamic workflow on run', async (t) => { + if (t.context.env.supportsTimeSkipping) { + t.pass("Test Server doesn't support worker deployments"); + return; + } + + const taskQueue = 'worker-deployment-dynamic-' + randomUUID(); + const deploymentName = 'deployment-dynamic-' + randomUUID(); + const client = t.context.env.client; + + const version = { + buildId: '1.0', + deploymentName: deploymentName, + }; + + const worker = await Worker.create({ + workflowsPath: require.resolve('./deployment-versioning-v1'), + activities, + taskQueue, + workerDeploymentOptions: { + useWorkerVersioning: true, + version: version, + defaultVersioningBehavior: 'auto-upgrade', + }, + }); + + const workerPromise = worker.run(); + workerPromise.catch((err) => { + t.fail('Worker run error: ' + JSON.stringify(err)); + }); + + const describeResp = await waitUntilWorkerDeploymentVisible(client, version); + await setCurrentDeploymentVersion(client, describeResp.conflictToken, version); + + const wf = await client.workflow.start('cooldynamicworkflow', { + taskQueue, + workflowId: 'dynamic-workflow-versioning-' + randomUUID(), + }); + + const result = await wf.result(); + assert.equal(result, 'dynamic'); + + // Check history for versioning behavior + const history = await wf.fetchHistory(); + + const hasPinnedVersioningBehavior = history.events!.some( + (event) => + event.workflowTaskCompletedEventAttributes && + event.workflowTaskCompletedEventAttributes.versioningBehavior === + temporal.api.enums.v1.VersioningBehavior.VERSIONING_BEHAVIOR_PINNED + ); + assert.ok(hasPinnedVersioningBehavior, 'Expected workflow to use pinned versioning behavior'); + + worker.shutdown(); + await workerPromise; + t.pass(); +}); + +test('Workflows can use default versioning behavior', async (t) => { + const taskQueue = 'task-queue-default-versioning-' + randomUUID(); + const deploymentName = 'deployment-default-versioning-' + randomUUID(); + const client = t.context.env.client; + + const workerV1 = { + buildId: '1.0', + deploymentName: deploymentName, + }; + + const worker = await Worker.create({ + workflowsPath: require.resolve('./deployment-versioning-no-annotations'), + activities, + taskQueue, + workerDeploymentOptions: { + useWorkerVersioning: true, + version: workerV1, + defaultVersioningBehavior: 'pinned', + }, + }); + + const workerPromise = worker.run(); + workerPromise.catch((err) => { + t.fail('Worker run error: ' + JSON.stringify(err)); + }); + + const describeResp = await waitUntilWorkerDeploymentVisible(client, workerV1); + await setCurrentDeploymentVersion(client, describeResp.conflictToken, workerV1); + + const wf = await client.workflow.start('noVersioningAnnotationWorkflow', { + taskQueue, + workflowId: 'default-versioning-behavior-' + randomUUID(), + }); + + await wf.result(); + + // Check history for versioning behavior + const history = await wf.fetchHistory(); + + const hasPinnedVersioningBehavior = history.events!.some( + (event) => + event.workflowTaskCompletedEventAttributes && + event.workflowTaskCompletedEventAttributes.versioningBehavior === + temporal.api.enums.v1.VersioningBehavior.VERSIONING_BEHAVIOR_PINNED + ); + assert.ok(hasPinnedVersioningBehavior, 'Expected workflow to use pinned versioning behavior'); + + worker.shutdown(); + await workerPromise; + t.pass(); +}); + async function setRampingVersion( client: Client, conflictToken: Uint8Array, diff --git a/packages/worker/src/worker-options.ts b/packages/worker/src/worker-options.ts index ddeaf25ba..595e669c9 100644 --- a/packages/worker/src/worker-options.ts +++ b/packages/worker/src/worker-options.ts @@ -138,11 +138,10 @@ export interface WorkerOptions { useWorkerVersioning: boolean; /** - * If specified, the default versioning behavior to use for all workflows on this worker. - * If not specified, and `useWorkerVersioning` is true, workflows that do not specify a - * versioning behavior via {@link TODO} will cause an error to be thrown on startup. + * The default versioning behavior to use for all workflows on this worker. Specifying a default + * behavior is required, */ - defaultVersioningBehavior?: VersioningBehavior; + defaultVersioningBehavior: VersioningBehavior; }; /** diff --git a/packages/workflow/src/worker-interface.ts b/packages/workflow/src/worker-interface.ts index 80bb0df8b..a9db0cf9f 100644 --- a/packages/workflow/src/worker-interface.ts +++ b/packages/workflow/src/worker-interface.ts @@ -82,6 +82,9 @@ export function initRuntime(options: WorkflowCreateOptionsInternal): void { if (isWorkflowFunctionWithOptions(workflowFn)) { activator.workflow = workflowFn; activator.versioningBehavior = workflowFn.options.versioningBehavior; + } else if (isWorkflowFunctionWithOptions(defaultWorkflowFn)) { + activator.workflow = defaultWorkflowFn; + activator.versioningBehavior = defaultWorkflowFn.options.versioningBehavior; } else if (typeof workflowFn === 'function') { activator.workflow = workflowFn; } else if (typeof defaultWorkflowFn === 'function') { From b097312eb87f2a77552f8ded1c7496afba3f8571 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Tue, 15 Apr 2025 17:13:05 -0700 Subject: [PATCH 07/11] Address todos / add missed new file --- packages/common/src/worker-deployments.ts | 15 +++++++- .../index.ts | 14 +++++++ packages/test/src/helpers-integration.ts | 5 --- .../src/test-worker-deployment-versioning.ts | 24 ++++++------ packages/worker/src/worker-options.ts | 2 +- packages/worker/src/workflow/vm-shared.ts | 2 +- packages/workflow/src/interfaces.ts | 2 + packages/workflow/src/internals.ts | 2 +- packages/workflow/src/workflow.ts | 38 +++++++++++++++---- 9 files changed, 74 insertions(+), 30 deletions(-) create mode 100644 packages/test/src/deployment-versioning-no-annotations/index.ts diff --git a/packages/common/src/worker-deployments.ts b/packages/common/src/worker-deployments.ts index 4722dd9ea..af7d3e4e8 100644 --- a/packages/common/src/worker-deployments.ts +++ b/packages/common/src/worker-deployments.ts @@ -1,15 +1,28 @@ /** * Represents the version of a specific worker deployment. * - * @experimental Worker deployments are experimental + * @experimental Deployment based versioning is experimental and may change in the future. */ export interface WorkerDeploymentVersion { readonly buildId: string; readonly deploymentName: string; } +/** + * @returns The canonical representation of a deployment version, which is a string in the format + * `deploymentName.buildId`. + */ export function toCanonicalString(version: WorkerDeploymentVersion): string { return `${version.deploymentName}.${version.buildId}`; } +/** + * Specifies when a workflow might move from a worker of one Build Id to another. + * + * * 'pinned' - The workflow will be pinned to the current Build ID unless manually moved. + * * 'auto-upgrade' - The workflow will automatically move to the latest version (default Build ID + * of the task queue) when the next task is dispatched. + * + * @experimental Deployment based versioning is experimental and may change in the future. + */ export type VersioningBehavior = 'pinned' | 'auto-upgrade'; diff --git a/packages/test/src/deployment-versioning-no-annotations/index.ts b/packages/test/src/deployment-versioning-no-annotations/index.ts new file mode 100644 index 000000000..d476dfd42 --- /dev/null +++ b/packages/test/src/deployment-versioning-no-annotations/index.ts @@ -0,0 +1,14 @@ +import { setHandler, condition } from '@temporalio/workflow'; +import { unblockSignal, versionQuery } from '../workflows'; + +export async function deploymentVersioning(): Promise { + let doFinish = false; + setHandler(unblockSignal, () => void (doFinish = true)); + setHandler(versionQuery, () => 'v1'); + await condition(() => doFinish); + return 'version-v1'; +} + +export default async function (): Promise { + return 'dynamic'; +} diff --git a/packages/test/src/helpers-integration.ts b/packages/test/src/helpers-integration.ts index f46c88cb0..9b5697ef6 100644 --- a/packages/test/src/helpers-integration.ts +++ b/packages/test/src/helpers-integration.ts @@ -101,11 +101,6 @@ export async function createLocalTestEnvironment( ...(opts || {}), // Use provided options or default to an empty object server: { searchAttributes: Object.values(defaultSAKeys), - // TODO: Remove after next CLI release - executable: { - type: 'cached-download', - version: 'v1.3.1-priority.0', - }, ...(opts?.server || {}), // Use provided server options or default to an empty object extraArgs: [ ...defaultDynamicConfigOptions.flatMap((opt) => ['--dynamic-config-value', opt]), diff --git a/packages/test/src/test-worker-deployment-versioning.ts b/packages/test/src/test-worker-deployment-versioning.ts index dfef4d367..a2786537a 100644 --- a/packages/test/src/test-worker-deployment-versioning.ts +++ b/packages/test/src/test-worker-deployment-versioning.ts @@ -7,12 +7,12 @@ import assert from 'assert'; import { randomUUID } from 'crypto'; import asyncRetry from 'async-retry'; import { Client } from '@temporalio/client'; +import { toCanonicalString, WorkerDeploymentVersion } from '@temporalio/common'; +import { temporal } from '@temporalio/proto'; import { Worker } from './helpers'; import * as activities from './activities'; -import { toCanonicalString, WorkerDeploymentVersion } from '@temporalio/common'; import { makeTestFunction } from './helpers-integration'; -import { unblockSignal, versionQuery } from './workflows/'; -import { temporal } from '@temporalio/proto'; +import { unblockSignal, versionQuery } from './workflows'; const test = makeTestFunction({ workflowsPath: __filename }); @@ -23,15 +23,15 @@ test('Worker deployment based versioning', async (t) => { const w1DeploymentVersion = { buildId: '1.0', - deploymentName: deploymentName, + deploymentName, }; const w2DeploymentVersion = { buildId: '2.0', - deploymentName: deploymentName, + deploymentName, }; const w3DeploymentVersion = { buildId: '3.0', - deploymentName: deploymentName, + deploymentName, }; const worker1 = await Worker.create({ @@ -144,11 +144,11 @@ test('Worker deployment based versioning with ramping', async (t) => { const v1 = { buildId: '1.0', - deploymentName: deploymentName, + deploymentName, }; const v2 = { buildId: '2.0', - deploymentName: deploymentName, + deploymentName, }; const worker1 = await Worker.create({ @@ -255,7 +255,7 @@ test('Worker deployment with dynamic workflow on run', async (t) => { const version = { buildId: '1.0', - deploymentName: deploymentName, + deploymentName, }; const worker = await Worker.create({ @@ -264,7 +264,7 @@ test('Worker deployment with dynamic workflow on run', async (t) => { taskQueue, workerDeploymentOptions: { useWorkerVersioning: true, - version: version, + version, defaultVersioningBehavior: 'auto-upgrade', }, }); @@ -308,7 +308,7 @@ test('Workflows can use default versioning behavior', async (t) => { const workerV1 = { buildId: '1.0', - deploymentName: deploymentName, + deploymentName, }; const worker = await Worker.create({ @@ -337,9 +337,7 @@ test('Workflows can use default versioning behavior', async (t) => { await wf.result(); - // Check history for versioning behavior const history = await wf.fetchHistory(); - const hasPinnedVersioningBehavior = history.events!.some( (event) => event.workflowTaskCompletedEventAttributes && diff --git a/packages/worker/src/worker-options.ts b/packages/worker/src/worker-options.ts index 595e669c9..619be92a2 100644 --- a/packages/worker/src/worker-options.ts +++ b/packages/worker/src/worker-options.ts @@ -123,7 +123,7 @@ export interface WorkerOptions { /** * Deployment options for the worker. Exclusive with `build_id` and `use_worker_versioning`. - + * * @experimental Deployment based versioning is still experimental. */ workerDeploymentOptions?: { diff --git a/packages/worker/src/workflow/vm-shared.ts b/packages/worker/src/workflow/vm-shared.ts index 165f3ce6d..96b76fd44 100644 --- a/packages/worker/src/workflow/vm-shared.ts +++ b/packages/worker/src/workflow/vm-shared.ts @@ -14,9 +14,9 @@ import * as internals from '@temporalio/workflow/lib/worker-interface'; import { Activator } from '@temporalio/workflow/lib/internals'; import { SdkFlags } from '@temporalio/workflow/lib/flags'; import { UnhandledRejectionError } from '../errors'; +import { convertDeploymentVersion } from '../utils'; import { Workflow } from './interface'; import { WorkflowBundleWithSourceMapAndFilename } from './workflow-worker-thread/input'; -import { convertDeploymentVersion } from '../utils'; // Best effort to catch unhandled rejections from workflow code. // We crash the thread if we cannot find the culprit. diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts index 706e90d39..c3edb77c0 100644 --- a/packages/workflow/src/interfaces.ts +++ b/packages/workflow/src/interfaces.ts @@ -193,6 +193,8 @@ export interface WorkflowInfo { * executing this task for the first time and has a Deployment Version set, then its ID will be * used. This value may change over the lifetime of the workflow run, but is deterministic and * safe to use for branching. + * + * @experimental Deployment based versioning is experimental and may change in the future. */ readonly currentDeploymentVersion?: WorkerDeploymentVersion; diff --git a/packages/workflow/src/internals.ts b/packages/workflow/src/internals.ts index 5b75a849b..7c88e0b38 100644 --- a/packages/workflow/src/internals.ts +++ b/packages/workflow/src/internals.ts @@ -498,7 +498,7 @@ export class Activator implements ActivationHandler { return { commands: this.commands.splice(0), usedInternalFlags: [...this.knownFlags], - versioningBehavior: versioningBehavior, + versioningBehavior, }; } diff --git a/packages/workflow/src/workflow.ts b/packages/workflow/src/workflow.ts index a8c741420..151c74dea 100644 --- a/packages/workflow/src/workflow.ts +++ b/packages/workflow/src/workflow.ts @@ -32,6 +32,7 @@ import { versioningIntentToProto } from '@temporalio/common/lib/versioning-inten import { Duration, msOptionalToTs, msToNumber, msToTs, requiredTsToMs } from '@temporalio/common/lib/time'; import { composeInterceptors } from '@temporalio/common/lib/interceptors'; import { temporal } from '@temporalio/proto'; +import { WorkflowDefinitionOptions, WorkflowFunctionWithOptions } from '@temporalio/common'; import { CancellationScope, registerSleepImplementation } from './cancellation-scope'; import { UpdateScope } from './update-scope'; import { @@ -63,7 +64,6 @@ import { LocalActivityDoBackoff } from './errors'; import { assertInWorkflowContext, getActivator, maybeGetActivator } from './global-attributes'; import { untrackPromise } from './stack-helpers'; import { ChildWorkflowHandle, ExternalWorkflowHandle } from './workflow-handle'; -import { WorkflowDefinitionOptions, WorkflowFunctionWithOptions } from '@temporalio/common'; // Avoid a circular dependency registerSleepImplementation(sleep); @@ -1592,13 +1592,35 @@ export function allHandlersFinished(): boolean { } /** -* Can be used to define workflow functions with certain options specified at definition time. - -* @param options Options for the workflow defintion. -* @param fn The workflow function. -* @returns The same passed in workflow function, with the specified options applied. You can export -* this function to make it available as a workflow function. -*/ + * Can be used to alter or define workflow functions with certain options specified at definition + * time. In order to ensure that workflows are loaded properly by their name, you typically will not + * need to use the return value of this function. + * + * @example + * For example: + * ```ts + * defineWorkflowWithOptions({ versioningBehavior: 'pinned' }, myWorkflow); + * export async function myWorkflow(): Promise { + * // Workflow code here + * return "hi"; + * } + * ``` + * + * @example + * To annotate a default or dynamic workflow: + * ```ts + * export default defineWorkflowWithOptions({ versioningBehavior: 'pinned' }, myDefaultWorkflow); + * async function myDefaultWorkflow(): Promise { + * // Workflow code here + * return "hi"; + * } + * ``` + * + * @param options Options for the workflow defintion. + * @param fn The workflow function. + * @returns The same passed in workflow function, with the specified options applied. You can export + * this function to make it available as a workflow function. + */ export function defineWorkflowWithOptions( options: WorkflowDefinitionOptions, fn: (...args: A) => Promise From f39ec3f7bed15625c1c2cf8e80771a30f1a1d1c0 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Thu, 17 Apr 2025 10:04:13 -0700 Subject: [PATCH 08/11] Fix review comments / lints --- packages/common/src/interfaces.ts | 1 - packages/common/src/workflow-options.ts | 3 ++- packages/test/src/deployment-versioning-v1/index.ts | 2 +- packages/test/src/deployment-versioning-v2/index.ts | 2 +- packages/test/src/deployment-versioning-v3/index.ts | 2 +- packages/test/src/test-integration-split-one.ts | 2 +- packages/test/src/test-integration-workflows.ts | 2 +- packages/test/src/test-sinks.ts | 2 +- packages/test/src/test-worker-deployment-versioning.ts | 8 -------- packages/worker/src/utils.ts | 2 +- packages/worker/src/worker-options.ts | 4 ++-- packages/worker/src/worker.ts | 3 ++- packages/workflow/src/flags.ts | 2 +- packages/workflow/src/interfaces.ts | 1 - packages/workflow/src/workflow.ts | 3 ++- 15 files changed, 16 insertions(+), 23 deletions(-) diff --git a/packages/common/src/interfaces.ts b/packages/common/src/interfaces.ts index f66a7ed82..34becc49b 100644 --- a/packages/common/src/interfaces.ts +++ b/packages/common/src/interfaces.ts @@ -1,5 +1,4 @@ import type { temporal } from '@temporalio/proto'; -import { WorkflowFunctionWithOptions } from './workflow-definition-options'; export type Payload = temporal.api.common.v1.IPayload; diff --git a/packages/common/src/workflow-options.ts b/packages/common/src/workflow-options.ts index d725cd506..4bb477f82 100644 --- a/packages/common/src/workflow-options.ts +++ b/packages/common/src/workflow-options.ts @@ -257,7 +257,8 @@ export function extractWorkflowType( ); } +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ export function isWorkflowFunctionWithOptions(obj: any): obj is WorkflowFunctionWithOptions { - if (obj === undefined || obj === null) return false; + if (obj == null) return false; return obj.__temporal_is_workflow_function_with_options === true; } diff --git a/packages/test/src/deployment-versioning-v1/index.ts b/packages/test/src/deployment-versioning-v1/index.ts index 889f9425d..f414dcc1b 100644 --- a/packages/test/src/deployment-versioning-v1/index.ts +++ b/packages/test/src/deployment-versioning-v1/index.ts @@ -1,4 +1,4 @@ -import { CancelledFailure, setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; +import { setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; import { unblockSignal, versionQuery } from '../workflows'; defineWorkflowWithOptions({ versioningBehavior: 'auto-upgrade' }, deploymentVersioning); diff --git a/packages/test/src/deployment-versioning-v2/index.ts b/packages/test/src/deployment-versioning-v2/index.ts index d10ca5d24..a2f793633 100644 --- a/packages/test/src/deployment-versioning-v2/index.ts +++ b/packages/test/src/deployment-versioning-v2/index.ts @@ -1,4 +1,4 @@ -import { CancelledFailure, setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; +import { setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; import { unblockSignal, versionQuery } from '../workflows'; defineWorkflowWithOptions({ versioningBehavior: 'pinned' }, deploymentVersioning); diff --git a/packages/test/src/deployment-versioning-v3/index.ts b/packages/test/src/deployment-versioning-v3/index.ts index eab1060fb..5e40fd6dc 100644 --- a/packages/test/src/deployment-versioning-v3/index.ts +++ b/packages/test/src/deployment-versioning-v3/index.ts @@ -1,4 +1,4 @@ -import { CancelledFailure, setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; +import { setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; import { unblockSignal, versionQuery } from '../workflows'; defineWorkflowWithOptions({ versioningBehavior: 'auto-upgrade' }, deploymentVersioning); diff --git a/packages/test/src/test-integration-split-one.ts b/packages/test/src/test-integration-split-one.ts index d8540f97f..014f3dd46 100644 --- a/packages/test/src/test-integration-split-one.ts +++ b/packages/test/src/test-integration-split-one.ts @@ -733,7 +733,7 @@ test('Workflow can read WorkflowInfo', configMacro, async (t, config) => { historySize: res.historySize, startTime: res.startTime, runStartTime: res.runStartTime, - currentBuildId: res.currentBuildId, + currentBuildId: res.currentBuildId, // eslint-disable-line deprecation/deprecation // unsafe.now is a function, so doesn't make it through serialization, but .now is required, so we need to cast unsafe: { isReplaying: false } as UnsafeWorkflowInfo, priority: {}, diff --git a/packages/test/src/test-integration-workflows.ts b/packages/test/src/test-integration-workflows.ts index 9d55c87ef..4c80f231e 100644 --- a/packages/test/src/test-integration-workflows.ts +++ b/packages/test/src/test-integration-workflows.ts @@ -494,7 +494,7 @@ export async function buildIdTester(): Promise { }); workflow.setHandler(getBuildIdQuery, () => { - return workflow.workflowInfo().currentBuildId ?? ''; + return workflow.workflowInfo().currentBuildId ?? ''; // eslint-disable-line deprecation/deprecation }); // The unblock signal will only be sent once we are in Worker 1.1. diff --git a/packages/test/src/test-sinks.ts b/packages/test/src/test-sinks.ts index f3991fccb..49009dc61 100644 --- a/packages/test/src/test-sinks.ts +++ b/packages/test/src/test-sinks.ts @@ -94,7 +94,7 @@ if (RUN_INTEGRATION_TESTS) { }); // Capture volatile values that are hard to predict - const { historySize, startTime, runStartTime, currentBuildId } = recordedCalls[0].info; + const { historySize, startTime, runStartTime, currentBuildId } = recordedCalls[0].info; // eslint-disable-line deprecation/deprecation t.true(historySize > 300); const info: WorkflowInfo = { diff --git a/packages/test/src/test-worker-deployment-versioning.ts b/packages/test/src/test-worker-deployment-versioning.ts index a2786537a..06d919a3f 100644 --- a/packages/test/src/test-worker-deployment-versioning.ts +++ b/packages/test/src/test-worker-deployment-versioning.ts @@ -10,7 +10,6 @@ import { Client } from '@temporalio/client'; import { toCanonicalString, WorkerDeploymentVersion } from '@temporalio/common'; import { temporal } from '@temporalio/proto'; import { Worker } from './helpers'; -import * as activities from './activities'; import { makeTestFunction } from './helpers-integration'; import { unblockSignal, versionQuery } from './workflows'; @@ -36,7 +35,6 @@ test('Worker deployment based versioning', async (t) => { const worker1 = await Worker.create({ workflowsPath: require.resolve('./deployment-versioning-v1'), - activities, taskQueue, workerDeploymentOptions: { useWorkerVersioning: true, @@ -51,7 +49,6 @@ test('Worker deployment based versioning', async (t) => { const worker2 = await Worker.create({ workflowsPath: require.resolve('./deployment-versioning-v2'), - activities, taskQueue, workerDeploymentOptions: { useWorkerVersioning: true, @@ -66,7 +63,6 @@ test('Worker deployment based versioning', async (t) => { const worker3 = await Worker.create({ workflowsPath: require.resolve('./deployment-versioning-v3'), - activities, taskQueue, workerDeploymentOptions: { useWorkerVersioning: true, @@ -153,7 +149,6 @@ test('Worker deployment based versioning with ramping', async (t) => { const worker1 = await Worker.create({ workflowsPath: require.resolve('./deployment-versioning-v1'), - activities, taskQueue, workerDeploymentOptions: { useWorkerVersioning: true, @@ -168,7 +163,6 @@ test('Worker deployment based versioning with ramping', async (t) => { const worker2 = await Worker.create({ workflowsPath: require.resolve('./deployment-versioning-v2'), - activities, taskQueue, workerDeploymentOptions: { useWorkerVersioning: true, @@ -260,7 +254,6 @@ test('Worker deployment with dynamic workflow on run', async (t) => { const worker = await Worker.create({ workflowsPath: require.resolve('./deployment-versioning-v1'), - activities, taskQueue, workerDeploymentOptions: { useWorkerVersioning: true, @@ -313,7 +306,6 @@ test('Workflows can use default versioning behavior', async (t) => { const worker = await Worker.create({ workflowsPath: require.resolve('./deployment-versioning-no-annotations'), - activities, taskQueue, workerDeploymentOptions: { useWorkerVersioning: true, diff --git a/packages/worker/src/utils.ts b/packages/worker/src/utils.ts index bddd5fbc5..02bfa893f 100644 --- a/packages/worker/src/utils.ts +++ b/packages/worker/src/utils.ts @@ -33,7 +33,7 @@ export function convertToParentWorkflowType( export function convertDeploymentVersion( v: coresdk.common.IWorkerDeploymentVersion | null | undefined ): WorkerDeploymentVersion | undefined { - if (!v) { + if (v == null) { return undefined; } diff --git a/packages/worker/src/worker-options.ts b/packages/worker/src/worker-options.ts index 619be92a2..30f1dc8f3 100644 --- a/packages/worker/src/worker-options.ts +++ b/packages/worker/src/worker-options.ts @@ -730,8 +730,8 @@ function compileWorkerInterceptors({ function addDefaultWorkerOptions(options: WorkerOptions, logger: Logger): WorkerOptionsWithDefaults { const { - buildId, - useVersioning, + buildId, // eslint-disable-line deprecation/deprecation + useVersioning, // eslint-disable-line deprecation/deprecation maxCachedWorkflows, showStackTraceSources, namespace, diff --git a/packages/worker/src/worker.ts b/packages/worker/src/worker.ts index bbff78a2a..d38d04ad1 100644 --- a/packages/worker/src/worker.ts +++ b/packages/worker/src/worker.ts @@ -165,7 +165,8 @@ interface WorkflowWithLogAttributes { } function addBuildIdIfMissing(options: CompiledWorkerOptions, bundleCode?: string): CompiledWorkerOptionsWithBuildId { - if (options.buildId != null) { + const bid = options.buildId; // eslint-disable-line deprecation/deprecation + if (bid != null) { return options as CompiledWorkerOptionsWithBuildId; } const suffix = bundleCode ? `+${crypto.createHash('sha256').update(bundleCode).digest('hex')}` : ''; diff --git a/packages/workflow/src/flags.ts b/packages/workflow/src/flags.ts index 139ee8a5e..1ed209b13 100644 --- a/packages/workflow/src/flags.ts +++ b/packages/workflow/src/flags.ts @@ -72,5 +72,5 @@ type AltConditionFn = (ctx: { info: WorkflowInfo }) => boolean; function buildIdSdkVersionMatches(version: RegExp): AltConditionFn { const regex = new RegExp(`^@temporalio/worker@(${version.source})[+]`); - return ({ info }) => info.currentBuildId != null && regex.test(info.currentBuildId); + return ({ info }) => info.currentBuildId != null && regex.test(info.currentBuildId); // eslint-disable-line deprecation/deprecation } diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts index c3edb77c0..4d05a9bdf 100644 --- a/packages/workflow/src/interfaces.ts +++ b/packages/workflow/src/interfaces.ts @@ -14,7 +14,6 @@ import { SearchAttributePair, Priority, WorkerDeploymentVersion, - VersioningBehavior, } from '@temporalio/common'; import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers'; import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow/enums-helpers'; diff --git a/packages/workflow/src/workflow.ts b/packages/workflow/src/workflow.ts index 151c74dea..c002199a1 100644 --- a/packages/workflow/src/workflow.ts +++ b/packages/workflow/src/workflow.ts @@ -23,6 +23,8 @@ import { WorkflowUpdateValidatorType, SearchAttributeUpdatePair, compilePriority, + WorkflowDefinitionOptions, + WorkflowFunctionWithOptions, } from '@temporalio/common'; import { encodeUnifiedSearchAttributes, @@ -32,7 +34,6 @@ import { versioningIntentToProto } from '@temporalio/common/lib/versioning-inten import { Duration, msOptionalToTs, msToNumber, msToTs, requiredTsToMs } from '@temporalio/common/lib/time'; import { composeInterceptors } from '@temporalio/common/lib/interceptors'; import { temporal } from '@temporalio/proto'; -import { WorkflowDefinitionOptions, WorkflowFunctionWithOptions } from '@temporalio/common'; import { CancellationScope, registerSleepImplementation } from './cancellation-scope'; import { UpdateScope } from './update-scope'; import { From c9d28ed10837f0d23654a52c08074507eb5e664f Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Thu, 17 Apr 2025 14:07:32 -0700 Subject: [PATCH 09/11] Use more standard enum representation / fix related errors --- packages/common/src/worker-deployments.ts | 24 ++++++++++++++++++- packages/core-bridge/src/conversions.rs | 4 ++-- .../src/deployment-versioning-v1/index.ts | 4 ++-- .../src/deployment-versioning-v2/index.ts | 2 +- .../src/deployment-versioning-v3/index.ts | 2 +- .../src/test-worker-deployment-versioning.ts | 14 +++++------ packages/workflow/src/interfaces.ts | 5 ++-- packages/workflow/src/internals.ts | 10 ++------ packages/workflow/src/worker-interface.ts | 15 +++++++++--- 9 files changed, 53 insertions(+), 27 deletions(-) diff --git a/packages/common/src/worker-deployments.ts b/packages/common/src/worker-deployments.ts index af7d3e4e8..b9091a8ef 100644 --- a/packages/common/src/worker-deployments.ts +++ b/packages/common/src/worker-deployments.ts @@ -1,3 +1,6 @@ +import { temporal } from '@temporalio/proto'; +import { makeProtoEnumConverters } from './internal-workflow'; + /** * Represents the version of a specific worker deployment. * @@ -25,4 +28,23 @@ export function toCanonicalString(version: WorkerDeploymentVersion): string { * * @experimental Deployment based versioning is experimental and may change in the future. */ -export type VersioningBehavior = 'pinned' | 'auto-upgrade'; +export const VersioningBehavior = { + PINNED: 'PINNED', + AUTO_UPGRADE: 'AUTO_UPGRADE', +} as const; +export type VersioningBehavior = (typeof VersioningBehavior)[keyof typeof VersioningBehavior]; + +export const [encodeVersioningBehavior, decodeVersioningBehavior] = makeProtoEnumConverters< + temporal.api.enums.v1.VersioningBehavior, + typeof temporal.api.enums.v1.VersioningBehavior, + keyof typeof temporal.api.enums.v1.VersioningBehavior, + typeof VersioningBehavior, + 'VERSIONING_BEHAVIOR_' +>( + { + [VersioningBehavior.PINNED]: 1, + [VersioningBehavior.AUTO_UPGRADE]: 2, + UNSPECIFIED: 0, + } as const, + 'VERSIONING_BEHAVIOR_' +); diff --git a/packages/core-bridge/src/conversions.rs b/packages/core-bridge/src/conversions.rs index 504807168..af2c57c3a 100644 --- a/packages/core-bridge/src/conversions.rs +++ b/packages/core-bridge/src/conversions.rs @@ -531,8 +531,8 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> { let default_versioning_behavior = js_optional_value_getter!(cx, &dopts, "defaultVersioningBehavior", JsString); let default_versioning_behavior = match default_versioning_behavior.as_deref() { - Some("pinned") => Some(VersioningBehavior::Pinned), - Some("auto-upgrade") => Some(VersioningBehavior::AutoUpgrade), + Some("PINNED") => Some(VersioningBehavior::Pinned), + Some("AUTO_UPGRADE") => Some(VersioningBehavior::AutoUpgrade), None => None, _ => return cx.throw_error("Invalid default versioning behavior"), }; diff --git a/packages/test/src/deployment-versioning-v1/index.ts b/packages/test/src/deployment-versioning-v1/index.ts index f414dcc1b..7e11c1856 100644 --- a/packages/test/src/deployment-versioning-v1/index.ts +++ b/packages/test/src/deployment-versioning-v1/index.ts @@ -1,7 +1,7 @@ import { setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; import { unblockSignal, versionQuery } from '../workflows'; -defineWorkflowWithOptions({ versioningBehavior: 'auto-upgrade' }, deploymentVersioning); +defineWorkflowWithOptions({ versioningBehavior: 'AUTO_UPGRADE' }, deploymentVersioning); export async function deploymentVersioning(): Promise { let doFinish = false; setHandler(unblockSignal, () => void (doFinish = true)); @@ -11,7 +11,7 @@ export async function deploymentVersioning(): Promise { } // Dynamic/default workflow handler -export default defineWorkflowWithOptions({ versioningBehavior: 'pinned' }, _default); +export default defineWorkflowWithOptions({ versioningBehavior: 'PINNED' }, _default); async function _default(): Promise { return 'dynamic'; } diff --git a/packages/test/src/deployment-versioning-v2/index.ts b/packages/test/src/deployment-versioning-v2/index.ts index a2f793633..87fd5863a 100644 --- a/packages/test/src/deployment-versioning-v2/index.ts +++ b/packages/test/src/deployment-versioning-v2/index.ts @@ -1,7 +1,7 @@ import { setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; import { unblockSignal, versionQuery } from '../workflows'; -defineWorkflowWithOptions({ versioningBehavior: 'pinned' }, deploymentVersioning); +defineWorkflowWithOptions({ versioningBehavior: 'PINNED' }, deploymentVersioning); export async function deploymentVersioning(): Promise { let doFinish = false; setHandler(unblockSignal, () => void (doFinish = true)); diff --git a/packages/test/src/deployment-versioning-v3/index.ts b/packages/test/src/deployment-versioning-v3/index.ts index 5e40fd6dc..b499779b2 100644 --- a/packages/test/src/deployment-versioning-v3/index.ts +++ b/packages/test/src/deployment-versioning-v3/index.ts @@ -1,7 +1,7 @@ import { setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; import { unblockSignal, versionQuery } from '../workflows'; -defineWorkflowWithOptions({ versioningBehavior: 'auto-upgrade' }, deploymentVersioning); +defineWorkflowWithOptions({ versioningBehavior: 'AUTO_UPGRADE' }, deploymentVersioning); export async function deploymentVersioning(): Promise { let doFinish = false; setHandler(unblockSignal, () => void (doFinish = true)); diff --git a/packages/test/src/test-worker-deployment-versioning.ts b/packages/test/src/test-worker-deployment-versioning.ts index 06d919a3f..e5eeef8e4 100644 --- a/packages/test/src/test-worker-deployment-versioning.ts +++ b/packages/test/src/test-worker-deployment-versioning.ts @@ -39,7 +39,7 @@ test('Worker deployment based versioning', async (t) => { workerDeploymentOptions: { useWorkerVersioning: true, version: w1DeploymentVersion, - defaultVersioningBehavior: 'pinned', + defaultVersioningBehavior: 'PINNED', }, }); const worker1Promise = worker1.run(); @@ -53,7 +53,7 @@ test('Worker deployment based versioning', async (t) => { workerDeploymentOptions: { useWorkerVersioning: true, version: w2DeploymentVersion, - defaultVersioningBehavior: 'pinned', + defaultVersioningBehavior: 'PINNED', }, }); const worker2Promise = worker2.run(); @@ -67,7 +67,7 @@ test('Worker deployment based versioning', async (t) => { workerDeploymentOptions: { useWorkerVersioning: true, version: w3DeploymentVersion, - defaultVersioningBehavior: 'pinned', + defaultVersioningBehavior: 'PINNED', }, }); const worker3Promise = worker3.run(); @@ -153,7 +153,7 @@ test('Worker deployment based versioning with ramping', async (t) => { workerDeploymentOptions: { useWorkerVersioning: true, version: v1, - defaultVersioningBehavior: 'pinned', + defaultVersioningBehavior: 'PINNED', }, }); const worker1Promise = worker1.run(); @@ -167,7 +167,7 @@ test('Worker deployment based versioning with ramping', async (t) => { workerDeploymentOptions: { useWorkerVersioning: true, version: v2, - defaultVersioningBehavior: 'pinned', + defaultVersioningBehavior: 'PINNED', }, }); const worker2Promise = worker2.run(); @@ -258,7 +258,7 @@ test('Worker deployment with dynamic workflow on run', async (t) => { workerDeploymentOptions: { useWorkerVersioning: true, version, - defaultVersioningBehavior: 'auto-upgrade', + defaultVersioningBehavior: 'AUTO_UPGRADE', }, }); @@ -310,7 +310,7 @@ test('Workflows can use default versioning behavior', async (t) => { workerDeploymentOptions: { useWorkerVersioning: true, version: workerV1, - defaultVersioningBehavior: 'pinned', + defaultVersioningBehavior: 'PINNED', }, }); diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts index 4d05a9bdf..1e4101f1d 100644 --- a/packages/workflow/src/interfaces.ts +++ b/packages/workflow/src/interfaces.ts @@ -14,10 +14,11 @@ import { SearchAttributePair, Priority, WorkerDeploymentVersion, + VersioningBehavior, } from '@temporalio/common'; import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers'; import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow/enums-helpers'; -import type { coresdk, temporal } from '@temporalio/proto'; +import type { coresdk } from '@temporalio/proto'; /** * Workflow Execution information @@ -612,5 +613,5 @@ export type UpdateHandlerOptions = { export interface ActivationCompletion { commands: coresdk.workflow_commands.IWorkflowCommand[]; usedInternalFlags: number[]; - versioningBehavior?: temporal.api.enums.v1.VersioningBehavior; + versioningBehavior?: VersioningBehavior; } diff --git a/packages/workflow/src/internals.ts b/packages/workflow/src/internals.ts index 7c88e0b38..9cb233032 100644 --- a/packages/workflow/src/internals.ts +++ b/packages/workflow/src/internals.ts @@ -29,7 +29,7 @@ import { } from '@temporalio/common/lib/converter/payload-search-attributes'; import { composeInterceptors } from '@temporalio/common/lib/interceptors'; import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow'; -import { coresdk, temporal } from '@temporalio/proto'; +import type { coresdk, temporal } from '@temporalio/proto'; import { alea, RNG } from './alea'; import { RootCancellationScope } from './cancellation-scope'; import { UpdateScope } from './update-scope'; @@ -489,16 +489,10 @@ export class Activator implements ActivationHandler { } concludeActivation(): ActivationCompletion { - let versioningBehavior; - if (this.versioningBehavior === 'auto-upgrade') { - versioningBehavior = temporal.api.enums.v1.VersioningBehavior.VERSIONING_BEHAVIOR_AUTO_UPGRADE; - } else if (this.versioningBehavior === 'pinned') { - versioningBehavior = temporal.api.enums.v1.VersioningBehavior.VERSIONING_BEHAVIOR_PINNED; - } return { commands: this.commands.splice(0), usedInternalFlags: [...this.knownFlags], - versioningBehavior, + versioningBehavior: this.versioningBehavior, }; } diff --git a/packages/workflow/src/worker-interface.ts b/packages/workflow/src/worker-interface.ts index a9db0cf9f..399e5c79b 100644 --- a/packages/workflow/src/worker-interface.ts +++ b/packages/workflow/src/worker-interface.ts @@ -3,9 +3,14 @@ * * @module */ -import { IllegalStateError, isWorkflowFunctionWithOptions } from '@temporalio/common'; +import { + encodeVersioningBehavior, + IllegalStateError, + isWorkflowFunctionWithOptions, + VersioningBehavior, +} from '@temporalio/common'; import { composeInterceptors } from '@temporalio/common/lib/interceptors'; -import { coresdk } from '@temporalio/proto'; +import { coresdk, temporal } from '@temporalio/proto'; import { disableStorage } from './cancellation-scope'; import { disableUpdateStorage } from './update-scope'; import { WorkflowInterceptorsFactory } from './interceptors'; @@ -209,7 +214,11 @@ export function concludeActivation(): coresdk.workflow_completion.IWorkflowActiv } return { runId: activator.info.runId, - successful: { ...activationCompletion, commands }, + successful: { + ...activationCompletion, + commands, + versioningBehavior: encodeVersioningBehavior(activationCompletion.versioningBehavior), + }, }; } finally { activator.rethrowSynchronously = false; From e59e2d0403ede82887de0c671e3c97efb5370b7e Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Thu, 17 Apr 2025 15:09:07 -0700 Subject: [PATCH 10/11] Test fixes --- packages/test/src/test-integration-split-one.ts | 1 + packages/test/src/test-sinks.ts | 3 ++- .../test/src/test-worker-deployment-versioning.ts | 15 +++++++++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/test/src/test-integration-split-one.ts b/packages/test/src/test-integration-split-one.ts index 014f3dd46..41c391e9a 100644 --- a/packages/test/src/test-integration-split-one.ts +++ b/packages/test/src/test-integration-split-one.ts @@ -734,6 +734,7 @@ test('Workflow can read WorkflowInfo', configMacro, async (t, config) => { startTime: res.startTime, runStartTime: res.runStartTime, currentBuildId: res.currentBuildId, // eslint-disable-line deprecation/deprecation + currentDeploymentVersion: res.currentDeploymentVersion, // unsafe.now is a function, so doesn't make it through serialization, but .now is required, so we need to cast unsafe: { isReplaying: false } as UnsafeWorkflowInfo, priority: {}, diff --git a/packages/test/src/test-sinks.ts b/packages/test/src/test-sinks.ts index 49009dc61..ab5009b54 100644 --- a/packages/test/src/test-sinks.ts +++ b/packages/test/src/test-sinks.ts @@ -94,7 +94,7 @@ if (RUN_INTEGRATION_TESTS) { }); // Capture volatile values that are hard to predict - const { historySize, startTime, runStartTime, currentBuildId } = recordedCalls[0].info; // eslint-disable-line deprecation/deprecation + const { historySize, startTime, runStartTime, currentBuildId, currentDeploymentVersion } = recordedCalls[0].info; // eslint-disable-line deprecation/deprecation t.true(historySize > 300); const info: WorkflowInfo = { @@ -129,6 +129,7 @@ if (RUN_INTEGRATION_TESTS) { startTime, runStartTime, currentBuildId, + currentDeploymentVersion, // unsafe.now() doesn't make it through serialization, but .now is required, so we need to cast unsafe: { isReplaying: false, diff --git a/packages/test/src/test-worker-deployment-versioning.ts b/packages/test/src/test-worker-deployment-versioning.ts index e5eeef8e4..8f5f2484a 100644 --- a/packages/test/src/test-worker-deployment-versioning.ts +++ b/packages/test/src/test-worker-deployment-versioning.ts @@ -18,7 +18,7 @@ const test = makeTestFunction({ workflowsPath: __filename }); test('Worker deployment based versioning', async (t) => { const taskQueue = 'worker-deployment-based-versioning-' + randomUUID(); const deploymentName = 'deployment-' + randomUUID(); - const client = t.context.env.client; + const { client, nativeConnection } = t.context.env; const w1DeploymentVersion = { buildId: '1.0', @@ -41,6 +41,7 @@ test('Worker deployment based versioning', async (t) => { version: w1DeploymentVersion, defaultVersioningBehavior: 'PINNED', }, + connection: nativeConnection, }); const worker1Promise = worker1.run(); worker1Promise.catch((err) => { @@ -55,6 +56,7 @@ test('Worker deployment based versioning', async (t) => { version: w2DeploymentVersion, defaultVersioningBehavior: 'PINNED', }, + connection: nativeConnection, }); const worker2Promise = worker2.run(); worker2Promise.catch((err) => { @@ -69,6 +71,7 @@ test('Worker deployment based versioning', async (t) => { version: w3DeploymentVersion, defaultVersioningBehavior: 'PINNED', }, + connection: nativeConnection, }); const worker3Promise = worker3.run(); worker3Promise.catch((err) => { @@ -136,7 +139,7 @@ test('Worker deployment based versioning', async (t) => { test('Worker deployment based versioning with ramping', async (t) => { const taskQueue = 'worker-deployment-based-ramping-' + randomUUID(); const deploymentName = 'deployment-ramping-' + randomUUID(); - const client = t.context.env.client; + const { client, nativeConnection } = t.context.env; const v1 = { buildId: '1.0', @@ -155,6 +158,7 @@ test('Worker deployment based versioning with ramping', async (t) => { version: v1, defaultVersioningBehavior: 'PINNED', }, + connection: nativeConnection, }); const worker1Promise = worker1.run(); worker1Promise.catch((err) => { @@ -169,6 +173,7 @@ test('Worker deployment based versioning with ramping', async (t) => { version: v2, defaultVersioningBehavior: 'PINNED', }, + connection: nativeConnection, }); const worker2Promise = worker2.run(); worker2Promise.catch((err) => { @@ -245,7 +250,7 @@ test('Worker deployment with dynamic workflow on run', async (t) => { const taskQueue = 'worker-deployment-dynamic-' + randomUUID(); const deploymentName = 'deployment-dynamic-' + randomUUID(); - const client = t.context.env.client; + const { client, nativeConnection } = t.context.env; const version = { buildId: '1.0', @@ -260,6 +265,7 @@ test('Worker deployment with dynamic workflow on run', async (t) => { version, defaultVersioningBehavior: 'AUTO_UPGRADE', }, + connection: nativeConnection, }); const workerPromise = worker.run(); @@ -297,7 +303,7 @@ test('Worker deployment with dynamic workflow on run', async (t) => { test('Workflows can use default versioning behavior', async (t) => { const taskQueue = 'task-queue-default-versioning-' + randomUUID(); const deploymentName = 'deployment-default-versioning-' + randomUUID(); - const client = t.context.env.client; + const { client, nativeConnection } = t.context.env; const workerV1 = { buildId: '1.0', @@ -312,6 +318,7 @@ test('Workflows can use default versioning behavior', async (t) => { version: workerV1, defaultVersioningBehavior: 'PINNED', }, + connection: nativeConnection, }); const workerPromise = worker.run(); From f18b1cadf2ec89890a6f553c00399aea36393157 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Wed, 23 Apr 2025 16:46:44 -0700 Subject: [PATCH 11/11] Options getter function --- .../common/src/workflow-definition-options.ts | 3 ++- .../src/deployment-versioning-v1/index.ts | 13 ++++++++++- .../src/test-worker-deployment-versioning.ts | 23 ++++++++++++++----- packages/workflow/src/internals.ts | 5 ++++ packages/workflow/src/worker-interface.ts | 21 +++++++++-------- packages/workflow/src/workflow.ts | 8 ++++--- 6 files changed, 53 insertions(+), 20 deletions(-) diff --git a/packages/common/src/workflow-definition-options.ts b/packages/common/src/workflow-definition-options.ts index 979d2b20a..63bda6258 100644 --- a/packages/common/src/workflow-definition-options.ts +++ b/packages/common/src/workflow-definition-options.ts @@ -8,11 +8,12 @@ export interface WorkflowDefinitionOptions { } type AsyncFunction = (...args: Args) => Promise; +export type WorkflowDefinitionOptionsOrGetter = WorkflowDefinitionOptions | (() => WorkflowDefinitionOptions); /** * A workflow function that has been defined with options from {@link WorkflowDefinitionOptions}. */ export interface WorkflowFunctionWithOptions extends AsyncFunction { __temporal_is_workflow_function_with_options: true; - options: WorkflowDefinitionOptions; + options: WorkflowDefinitionOptionsOrGetter; } diff --git a/packages/test/src/deployment-versioning-v1/index.ts b/packages/test/src/deployment-versioning-v1/index.ts index 7e11c1856..e9d6b08d0 100644 --- a/packages/test/src/deployment-versioning-v1/index.ts +++ b/packages/test/src/deployment-versioning-v1/index.ts @@ -1,4 +1,4 @@ -import { setHandler, condition, defineWorkflowWithOptions } from '@temporalio/workflow'; +import { setHandler, condition, defineWorkflowWithOptions, workflowInfo } from '@temporalio/workflow'; import { unblockSignal, versionQuery } from '../workflows'; defineWorkflowWithOptions({ versioningBehavior: 'AUTO_UPGRADE' }, deploymentVersioning); @@ -15,3 +15,14 @@ export default defineWorkflowWithOptions({ versioningBehavior: 'PINNED' }, _defa async function _default(): Promise { return 'dynamic'; } + +defineWorkflowWithOptions(() => { + // Need to ensure accessing workflow context still works in here + workflowInfo(); + return { + versioningBehavior: 'PINNED', + }; +}, usesGetter); +export async function usesGetter(): Promise { + return 'usesGetter'; +} diff --git a/packages/test/src/test-worker-deployment-versioning.ts b/packages/test/src/test-worker-deployment-versioning.ts index 8f5f2484a..0a75800d8 100644 --- a/packages/test/src/test-worker-deployment-versioning.ts +++ b/packages/test/src/test-worker-deployment-versioning.ts @@ -6,11 +6,12 @@ import assert from 'assert'; import { randomUUID } from 'crypto'; import asyncRetry from 'async-retry'; +import { ExecutionContext } from 'ava'; import { Client } from '@temporalio/client'; import { toCanonicalString, WorkerDeploymentVersion } from '@temporalio/common'; import { temporal } from '@temporalio/proto'; import { Worker } from './helpers'; -import { makeTestFunction } from './helpers-integration'; +import { Context, makeTestFunction } from './helpers-integration'; import { unblockSignal, versionQuery } from './workflows'; const test = makeTestFunction({ workflowsPath: __filename }); @@ -242,7 +243,11 @@ test('Worker deployment based versioning with ramping', async (t) => { t.pass(); }); -test('Worker deployment with dynamic workflow on run', async (t) => { +async function testWorkerDeploymentWithDynamicBehavior( + t: ExecutionContext, + workflowName: string, + expectedResult: string +) { if (t.context.env.supportsTimeSkipping) { t.pass("Test Server doesn't support worker deployments"); return; @@ -276,17 +281,15 @@ test('Worker deployment with dynamic workflow on run', async (t) => { const describeResp = await waitUntilWorkerDeploymentVisible(client, version); await setCurrentDeploymentVersion(client, describeResp.conflictToken, version); - const wf = await client.workflow.start('cooldynamicworkflow', { + const wf = await client.workflow.start(workflowName, { taskQueue, workflowId: 'dynamic-workflow-versioning-' + randomUUID(), }); const result = await wf.result(); - assert.equal(result, 'dynamic'); + assert.equal(result, expectedResult); - // Check history for versioning behavior const history = await wf.fetchHistory(); - const hasPinnedVersioningBehavior = history.events!.some( (event) => event.workflowTaskCompletedEventAttributes && @@ -298,6 +301,14 @@ test('Worker deployment with dynamic workflow on run', async (t) => { worker.shutdown(); await workerPromise; t.pass(); +} + +test('Worker deployment with dynamic workflow static behavior', async (t) => { + await testWorkerDeploymentWithDynamicBehavior(t, 'cooldynamicworkflow', 'dynamic'); +}); + +test('Worker deployment with behavior in getter', async (t) => { + await testWorkerDeploymentWithDynamicBehavior(t, 'usesGetter', 'usesGetter'); }); test('Workflows can use default versioning behavior', async (t) => { diff --git a/packages/workflow/src/internals.ts b/packages/workflow/src/internals.ts index 9cb233032..f01c9cf61 100644 --- a/packages/workflow/src/internals.ts +++ b/packages/workflow/src/internals.ts @@ -22,6 +22,7 @@ import { fromPayloadsAtIndex, WorkflowFunctionWithOptions, VersioningBehavior, + WorkflowDefinitionOptions, } from '@temporalio/common'; import { decodeSearchAttributes, @@ -419,6 +420,7 @@ export class Activator implements ActivationHandler { public readonly registeredActivityNames: Set; public versioningBehavior?: VersioningBehavior; + public workflowDefinitionOptionsGetter?: () => WorkflowDefinitionOptions; constructor({ info, @@ -534,6 +536,9 @@ export class Activator implements ActivationHandler { ? this.failureConverter.failureToError(continuedFailure, this.payloadConverter) : undefined, })); + if (this.workflowDefinitionOptionsGetter) { + this.versioningBehavior = this.workflowDefinitionOptionsGetter().versioningBehavior; + } } public cancelWorkflow(_activation: coresdk.workflow_activation.ICancelWorkflow): void { diff --git a/packages/workflow/src/worker-interface.ts b/packages/workflow/src/worker-interface.ts index 399e5c79b..25eaa8fb3 100644 --- a/packages/workflow/src/worker-interface.ts +++ b/packages/workflow/src/worker-interface.ts @@ -3,14 +3,9 @@ * * @module */ -import { - encodeVersioningBehavior, - IllegalStateError, - isWorkflowFunctionWithOptions, - VersioningBehavior, -} from '@temporalio/common'; +import { encodeVersioningBehavior, IllegalStateError, isWorkflowFunctionWithOptions } from '@temporalio/common'; import { composeInterceptors } from '@temporalio/common/lib/interceptors'; -import { coresdk, temporal } from '@temporalio/proto'; +import { coresdk } from '@temporalio/proto'; import { disableStorage } from './cancellation-scope'; import { disableUpdateStorage } from './update-scope'; import { WorkflowInterceptorsFactory } from './interceptors'; @@ -86,10 +81,18 @@ export function initRuntime(options: WorkflowCreateOptionsInternal): void { if (isWorkflowFunctionWithOptions(workflowFn)) { activator.workflow = workflowFn; - activator.versioningBehavior = workflowFn.options.versioningBehavior; + if (typeof workflowFn.options === 'object') { + activator.versioningBehavior = workflowFn.options.versioningBehavior; + } else { + activator.workflowDefinitionOptionsGetter = workflowFn.options; + } } else if (isWorkflowFunctionWithOptions(defaultWorkflowFn)) { activator.workflow = defaultWorkflowFn; - activator.versioningBehavior = defaultWorkflowFn.options.versioningBehavior; + if (typeof defaultWorkflowFn.options === 'object') { + activator.versioningBehavior = defaultWorkflowFn.options.versioningBehavior; + } else { + activator.workflowDefinitionOptionsGetter = defaultWorkflowFn.options; + } } else if (typeof workflowFn === 'function') { activator.workflow = workflowFn; } else if (typeof defaultWorkflowFn === 'function') { diff --git a/packages/workflow/src/workflow.ts b/packages/workflow/src/workflow.ts index c002199a1..aa230db55 100644 --- a/packages/workflow/src/workflow.ts +++ b/packages/workflow/src/workflow.ts @@ -23,7 +23,7 @@ import { WorkflowUpdateValidatorType, SearchAttributeUpdatePair, compilePriority, - WorkflowDefinitionOptions, + WorkflowDefinitionOptionsOrGetter, WorkflowFunctionWithOptions, } from '@temporalio/common'; import { @@ -1617,13 +1617,15 @@ export function allHandlersFinished(): boolean { * } * ``` * - * @param options Options for the workflow defintion. + * @param options Options for the workflow defintion, or a function that returns options. If a + * function is provided, it will be called once just before the workflow function is called for the + * first time. * @param fn The workflow function. * @returns The same passed in workflow function, with the specified options applied. You can export * this function to make it available as a workflow function. */ export function defineWorkflowWithOptions( - options: WorkflowDefinitionOptions, + options: WorkflowDefinitionOptionsOrGetter, fn: (...args: A) => Promise ): WorkflowFunctionWithOptions { const wrappedFn = Object.assign(fn, {