@temporalio/core-bridge 1.12.0 → 1.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/Cargo.lock +64 -119
  2. package/Cargo.toml +1 -1
  3. package/index.js +3 -2
  4. package/package.json +3 -3
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.cargo/config.toml +1 -2
  11. package/sdk-core/.github/workflows/per-pr.yml +2 -0
  12. package/sdk-core/AGENTS.md +7 -0
  13. package/sdk-core/Cargo.toml +9 -5
  14. package/sdk-core/README.md +6 -5
  15. package/sdk-core/client/Cargo.toml +3 -2
  16. package/sdk-core/client/src/lib.rs +17 -8
  17. package/sdk-core/client/src/metrics.rs +57 -23
  18. package/sdk-core/client/src/raw.rs +33 -15
  19. package/sdk-core/core/Cargo.toml +11 -9
  20. package/sdk-core/core/benches/workflow_replay.rs +114 -15
  21. package/sdk-core/core/src/core_tests/activity_tasks.rs +18 -18
  22. package/sdk-core/core/src/core_tests/child_workflows.rs +4 -4
  23. package/sdk-core/core/src/core_tests/determinism.rs +6 -6
  24. package/sdk-core/core/src/core_tests/local_activities.rs +20 -20
  25. package/sdk-core/core/src/core_tests/mod.rs +40 -5
  26. package/sdk-core/core/src/core_tests/queries.rs +25 -16
  27. package/sdk-core/core/src/core_tests/replay_flag.rs +3 -3
  28. package/sdk-core/core/src/core_tests/updates.rs +3 -3
  29. package/sdk-core/core/src/core_tests/workers.rs +9 -7
  30. package/sdk-core/core/src/core_tests/workflow_tasks.rs +40 -42
  31. package/sdk-core/core/src/ephemeral_server/mod.rs +1 -19
  32. package/sdk-core/core/src/lib.rs +10 -1
  33. package/sdk-core/core/src/pollers/poll_buffer.rs +2 -2
  34. package/sdk-core/core/src/replay/mod.rs +3 -3
  35. package/sdk-core/core/src/telemetry/metrics.rs +306 -152
  36. package/sdk-core/core/src/telemetry/mod.rs +11 -4
  37. package/sdk-core/core/src/telemetry/otel.rs +134 -131
  38. package/sdk-core/core/src/telemetry/prometheus_meter.rs +885 -0
  39. package/sdk-core/core/src/telemetry/prometheus_server.rs +48 -28
  40. package/sdk-core/core/src/test_help/mod.rs +27 -12
  41. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +7 -7
  42. package/sdk-core/core/src/worker/activities.rs +4 -4
  43. package/sdk-core/core/src/worker/client/mocks.rs +10 -3
  44. package/sdk-core/core/src/worker/client.rs +68 -5
  45. package/sdk-core/core/src/worker/heartbeat.rs +229 -0
  46. package/sdk-core/core/src/worker/mod.rs +35 -14
  47. package/sdk-core/core/src/worker/tuner/resource_based.rs +4 -4
  48. package/sdk-core/core/src/worker/workflow/history_update.rs +71 -19
  49. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +1 -2
  50. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -1
  51. package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +31 -48
  52. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +1 -2
  53. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +3 -3
  54. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +4 -1
  55. package/sdk-core/core/src/worker/workflow/managed_run.rs +1 -1
  56. package/sdk-core/core/src/worker/workflow/mod.rs +15 -15
  57. package/sdk-core/core-api/Cargo.toml +2 -2
  58. package/sdk-core/core-api/src/envconfig.rs +204 -99
  59. package/sdk-core/core-api/src/lib.rs +9 -0
  60. package/sdk-core/core-api/src/telemetry/metrics.rs +548 -100
  61. package/sdk-core/core-api/src/worker.rs +11 -5
  62. package/sdk-core/core-c-bridge/Cargo.toml +49 -0
  63. package/sdk-core/core-c-bridge/build.rs +26 -0
  64. package/sdk-core/core-c-bridge/include/temporal-sdk-core-c-bridge.h +817 -0
  65. package/sdk-core/core-c-bridge/src/client.rs +679 -0
  66. package/sdk-core/core-c-bridge/src/lib.rs +245 -0
  67. package/sdk-core/core-c-bridge/src/metric.rs +682 -0
  68. package/sdk-core/core-c-bridge/src/random.rs +61 -0
  69. package/sdk-core/core-c-bridge/src/runtime.rs +445 -0
  70. package/sdk-core/core-c-bridge/src/testing.rs +282 -0
  71. package/sdk-core/core-c-bridge/src/tests/context.rs +644 -0
  72. package/sdk-core/core-c-bridge/src/tests/mod.rs +178 -0
  73. package/sdk-core/core-c-bridge/src/tests/utils.rs +108 -0
  74. package/sdk-core/core-c-bridge/src/worker.rs +1069 -0
  75. package/sdk-core/etc/deps.svg +64 -64
  76. package/sdk-core/sdk/src/activity_context.rs +6 -4
  77. package/sdk-core/sdk/src/lib.rs +49 -27
  78. package/sdk-core/sdk/src/workflow_future.rs +18 -25
  79. package/sdk-core/sdk-core-protos/protos/api_upstream/README.md +4 -0
  80. package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +0 -2
  81. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +630 -83
  82. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +632 -78
  83. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/batch/v1/message.proto +4 -4
  84. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/command/v1/message.proto +6 -4
  85. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +2 -2
  86. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +32 -2
  87. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/common.proto +10 -1
  88. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/deployment.proto +26 -0
  89. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
  90. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +4 -4
  91. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -2
  92. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +47 -31
  93. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +4 -4
  94. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +7 -1
  95. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/worker/v1/message.proto +134 -0
  96. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +14 -11
  97. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +148 -37
  98. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +21 -0
  99. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -4
  100. package/sdk-core/sdk-core-protos/src/history_builder.rs +9 -5
  101. package/sdk-core/sdk-core-protos/src/lib.rs +96 -6
  102. package/sdk-core/test-utils/src/lib.rs +11 -3
  103. package/sdk-core/tests/cloud_tests.rs +3 -3
  104. package/sdk-core/tests/heavy_tests.rs +11 -3
  105. package/sdk-core/tests/integ_tests/client_tests.rs +12 -13
  106. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +1 -1
  107. package/sdk-core/tests/integ_tests/metrics_tests.rs +188 -83
  108. package/sdk-core/tests/integ_tests/polling_tests.rs +1 -1
  109. package/sdk-core/tests/integ_tests/queries_tests.rs +56 -40
  110. package/sdk-core/tests/integ_tests/update_tests.rs +2 -7
  111. package/sdk-core/tests/integ_tests/worker_tests.rs +3 -4
  112. package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +3 -7
  113. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +3 -5
  114. package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +24 -17
  115. package/src/client.rs +6 -0
  116. package/src/metrics.rs +6 -6
@@ -0,0 +1,644 @@
1
+ use crate::client::{
2
+ Client, ClientHttpConnectProxyOptions, ClientKeepAliveOptions, ClientRetryOptions,
3
+ ClientTlsOptions, RpcCallOptions, temporal_core_client_connect, temporal_core_client_free,
4
+ temporal_core_client_rpc_call,
5
+ };
6
+ use crate::runtime::{
7
+ Runtime, RuntimeOptions, RuntimeOrFail, temporal_core_byte_array_free,
8
+ temporal_core_runtime_free, temporal_core_runtime_new,
9
+ };
10
+ use crate::testing::{
11
+ DevServerOptions, EphemeralServer, TestServerOptions, temporal_core_ephemeral_server_free,
12
+ temporal_core_ephemeral_server_shutdown, temporal_core_ephemeral_server_start_dev_server,
13
+ };
14
+
15
+ use crate::tests::utils::{
16
+ MetadataMap, OwnedRpcCallOptions, RpcCallError, byte_array_to_string, byte_array_to_vec,
17
+ pointer_or_null,
18
+ };
19
+ use crate::{ByteArray, ByteArrayRef};
20
+ use anyhow::anyhow;
21
+ use std::any::Any;
22
+ use std::panic::{AssertUnwindSafe, UnwindSafe};
23
+ use std::ptr::NonNull;
24
+ use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError, Weak};
25
+ use std::time::Duration;
26
+ use temporal_client::ClientOptions;
27
+ use temporal_sdk_core::ephemeral_server::{
28
+ EphemeralExe, EphemeralExeVersion, TemporalDevServerConfig,
29
+ };
30
+
31
+ #[derive(Debug)]
32
+ enum ContextOperationState {
33
+ Available,
34
+ InProgress,
35
+ CallbackOk(Option<Box<dyn Any + Send>>),
36
+ CallbackError(anyhow::Error),
37
+ }
38
+
39
+ struct InnerContext {
40
+ is_disposed: bool,
41
+ operation_state: ContextOperationState,
42
+ runtime: *mut Runtime,
43
+ ephemeral_server: *mut EphemeralServer,
44
+ ephemeral_server_target: String,
45
+ client: *mut Client,
46
+ }
47
+
48
+ unsafe impl Send for InnerContext {}
49
+
50
+ impl Default for InnerContext {
51
+ fn default() -> Self {
52
+ Self {
53
+ is_disposed: false,
54
+ operation_state: ContextOperationState::Available,
55
+ runtime: std::ptr::null_mut(),
56
+ ephemeral_server: std::ptr::null_mut(),
57
+ ephemeral_server_target: String::new(),
58
+ client: std::ptr::null_mut(),
59
+ }
60
+ }
61
+ }
62
+
63
+ pub struct Context {
64
+ inner: Mutex<InnerContext>,
65
+ condvar: Condvar,
66
+ }
67
+
68
+ impl Context {
69
+ fn new(inner: InnerContext) -> Arc<Self> {
70
+ Arc::new(Self {
71
+ inner: Mutex::new(inner),
72
+ condvar: Condvar::new(),
73
+ })
74
+ }
75
+
76
+ pub fn with(func: impl FnOnce(&Arc<Self>) + UnwindSafe) {
77
+ let context = Self::new(Default::default());
78
+ let func_panic = std::panic::catch_unwind(|| func(&context));
79
+ let dispose_panic = std::panic::catch_unwind(|| context.dispose());
80
+ if let Err(e) = func_panic.and(dispose_panic) {
81
+ std::panic::resume_unwind(e);
82
+ }
83
+ }
84
+
85
+ #[allow(dead_code)]
86
+ pub fn runtime(&self) -> anyhow::Result<Option<NonNull<Runtime>>> {
87
+ Ok(NonNull::new(self.wait_for_available()?.runtime))
88
+ }
89
+
90
+ #[allow(dead_code)]
91
+ pub fn ephemeral_server(&self) -> anyhow::Result<Option<NonNull<EphemeralServer>>> {
92
+ Ok(NonNull::new(self.wait_for_available()?.ephemeral_server))
93
+ }
94
+
95
+ pub fn ephemeral_server_target(&self) -> anyhow::Result<Option<String>> {
96
+ let guard = self.wait_for_available()?;
97
+ Ok((!guard.ephemeral_server.is_null()).then(|| guard.ephemeral_server_target.clone()))
98
+ }
99
+
100
+ #[allow(dead_code)]
101
+ pub fn client(&self) -> anyhow::Result<Option<NonNull<Client>>> {
102
+ Ok(NonNull::new(self.wait_for_available()?.client))
103
+ }
104
+
105
+ pub fn dispose(self: Arc<Context>) -> anyhow::Result<()> {
106
+ let inner = {
107
+ let mut guard = self
108
+ .inner
109
+ .lock()
110
+ .and_then(|lock| {
111
+ self.condvar.wait_while(lock, |inner| {
112
+ !matches!(inner.operation_state, ContextOperationState::Available)
113
+ })
114
+ })
115
+ .unwrap_or_else(PoisonError::into_inner);
116
+ if guard.is_disposed {
117
+ return Err(anyhow!("Context already disposed"));
118
+ }
119
+ guard.is_disposed = true;
120
+ std::mem::take(&mut *guard)
121
+ };
122
+
123
+ if !inner.client.is_null() {
124
+ temporal_core_client_free(inner.client);
125
+ }
126
+ if !inner.ephemeral_server.is_null() {
127
+ // Creating new non-disposed context to execute server shutdown in
128
+ let _ = Self::new(InnerContext {
129
+ runtime: inner.runtime,
130
+ ephemeral_server: inner.ephemeral_server,
131
+ ..Default::default()
132
+ })
133
+ .ephemeral_server_shutdown();
134
+ }
135
+ if !inner.runtime.is_null() {
136
+ temporal_core_runtime_free(inner.runtime);
137
+ }
138
+
139
+ Ok(())
140
+ }
141
+
142
+ pub fn runtime_new(self: &Arc<Self>) -> anyhow::Result<()> {
143
+ let mut guard = self.wait_for_available()?;
144
+ if !guard.runtime.is_null() {
145
+ return Err(anyhow!("Runtime already exists"));
146
+ }
147
+
148
+ let RuntimeOrFail { runtime, fail } = temporal_core_runtime_new(&RuntimeOptions {
149
+ telemetry: std::ptr::null(),
150
+ });
151
+
152
+ if let Some(fail) = byte_array_to_string(runtime, fail) {
153
+ let runtime_is_null = if runtime.is_null() {
154
+ "(!!! runtime is null) "
155
+ } else {
156
+ temporal_core_runtime_free(runtime);
157
+ ""
158
+ };
159
+ Err(anyhow!(
160
+ "Runtime creation failed: {}{}",
161
+ runtime_is_null,
162
+ fail
163
+ ))
164
+ } else if runtime.is_null() {
165
+ Err(anyhow!("Runtime creation failed: runtime is null"))
166
+ } else {
167
+ guard.runtime = runtime;
168
+ Ok(())
169
+ }
170
+ }
171
+
172
+ pub fn start_dev_server(
173
+ self: &Arc<Self>,
174
+ config: Box<TemporalDevServerConfig>,
175
+ ) -> anyhow::Result<()> {
176
+ let extra_args = config.extra_args.join("\n");
177
+
178
+ let mut test_server_options = Box::new(TestServerOptions {
179
+ existing_path: ByteArrayRef::empty(),
180
+ sdk_name: ByteArrayRef::empty(),
181
+ sdk_version: ByteArrayRef::empty(),
182
+ download_version: ByteArrayRef::empty(),
183
+ download_dest_dir: ByteArrayRef::empty(),
184
+ port: config.port.unwrap_or(0),
185
+ extra_args: extra_args.as_str().into(),
186
+ download_ttl_seconds: 0,
187
+ });
188
+
189
+ match config.exe {
190
+ EphemeralExe::ExistingPath(ref path) => {
191
+ test_server_options.existing_path = path.as_str().into();
192
+ }
193
+ EphemeralExe::CachedDownload {
194
+ ref version,
195
+ ref dest_dir,
196
+ ref ttl,
197
+ } => {
198
+ test_server_options.download_dest_dir = dest_dir.as_deref().into();
199
+ test_server_options.download_ttl_seconds =
200
+ ttl.as_ref().map(Duration::as_secs).unwrap_or(0);
201
+ match version {
202
+ EphemeralExeVersion::SDKDefault {
203
+ sdk_name,
204
+ sdk_version,
205
+ } => {
206
+ test_server_options.download_version = "default".into();
207
+ test_server_options.sdk_name = sdk_name.as_str().into();
208
+ test_server_options.sdk_version = sdk_version.as_str().into();
209
+ }
210
+ EphemeralExeVersion::Fixed(version) => {
211
+ test_server_options.download_version = version.as_str().into();
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ let dev_server_options = Box::new(DevServerOptions {
218
+ test_server: &*test_server_options,
219
+ namespace: config.namespace.as_str().into(),
220
+ ip: config.ip.as_str().into(),
221
+ database_filename: config.db_filename.as_deref().into(),
222
+ ui: config.ui,
223
+ ui_port: config.ui_port.unwrap_or(0),
224
+ log_format: config.log.0.as_str().into(),
225
+ log_level: config.log.1.as_str().into(),
226
+ });
227
+
228
+ let dev_server_options_ptr = &*dev_server_options as *const _;
229
+ let user_data = Box::into_raw(Box::new(CallbackUserData {
230
+ data: (),
231
+ context: Arc::downgrade(self),
232
+ _allocations: Box::new((config, extra_args, test_server_options, dev_server_options)),
233
+ })) as *mut libc::c_void;
234
+
235
+ let runtime = {
236
+ let mut guard = self.wait_for_available()?;
237
+ if !guard.ephemeral_server.is_null() {
238
+ return Err(anyhow!("Ephemeral server already started"));
239
+ }
240
+ guard.operation_state = ContextOperationState::InProgress;
241
+ guard.runtime
242
+ };
243
+
244
+ temporal_core_ephemeral_server_start_dev_server(
245
+ runtime,
246
+ dev_server_options_ptr,
247
+ user_data,
248
+ ephemeral_server_start_callback,
249
+ );
250
+ self.wait_for_operation()
251
+ }
252
+
253
+ pub fn ephemeral_server_shutdown(self: &Arc<Context>) -> anyhow::Result<()> {
254
+ let server = {
255
+ let mut guard = self.wait_for_available()?;
256
+ let server = guard.ephemeral_server;
257
+ if server.is_null() {
258
+ return Err(anyhow!("Ephemeral server is null"));
259
+ }
260
+ guard.ephemeral_server = std::ptr::null_mut();
261
+ guard.ephemeral_server_target = String::new();
262
+ guard.operation_state = ContextOperationState::InProgress;
263
+ server
264
+ };
265
+
266
+ let user_data = Box::into_raw(Box::new(CallbackUserData {
267
+ data: server,
268
+ context: Arc::downgrade(self),
269
+ _allocations: Box::new(()),
270
+ })) as *mut libc::c_void;
271
+ temporal_core_ephemeral_server_shutdown(
272
+ server,
273
+ user_data,
274
+ ephemeral_server_shutdown_callback,
275
+ );
276
+ self.wait_for_operation()
277
+ }
278
+
279
+ pub fn client_connect(self: &Arc<Self>, options: Box<ClientOptions>) -> anyhow::Result<()> {
280
+ let metadata = options
281
+ .headers
282
+ .as_ref()
283
+ .map(MetadataMap::serialize_from_map);
284
+
285
+ let tls_options = options.tls_cfg.as_ref().map(|tls_cfg| {
286
+ let client_tls_cfg = tls_cfg.client_tls_config.as_ref();
287
+ Box::new(ClientTlsOptions {
288
+ server_root_ca_cert: tls_cfg.server_root_ca_cert.as_deref().into(),
289
+ domain: tls_cfg.domain.as_deref().into(),
290
+ client_cert: client_tls_cfg.map(|c| c.client_cert.as_slice()).into(),
291
+ client_private_key: client_tls_cfg
292
+ .map(|c| c.client_private_key.as_slice())
293
+ .into(),
294
+ })
295
+ });
296
+
297
+ let retry_options = Box::new(ClientRetryOptions {
298
+ initial_interval_millis: options.retry_config.initial_interval.as_millis() as u64,
299
+ randomization_factor: options.retry_config.randomization_factor,
300
+ multiplier: options.retry_config.multiplier,
301
+ max_interval_millis: options.retry_config.max_interval.as_millis() as u64,
302
+ max_elapsed_time_millis: options
303
+ .retry_config
304
+ .max_elapsed_time
305
+ .as_ref()
306
+ .map(Duration::as_millis)
307
+ .unwrap_or(0) as u64,
308
+ max_retries: options.retry_config.max_retries,
309
+ });
310
+
311
+ let keep_alive_options = options.keep_alive.as_ref().map(|keep_alive| {
312
+ Box::new(ClientKeepAliveOptions {
313
+ interval_millis: keep_alive.interval.as_millis() as u64,
314
+ timeout_millis: keep_alive.timeout.as_millis() as u64,
315
+ })
316
+ });
317
+
318
+ let proxy_options = options.http_connect_proxy.as_ref().map(|proxy| {
319
+ let (username, password) = match &proxy.basic_auth {
320
+ Some((username, password)) => (username.as_str().into(), password.as_str().into()),
321
+ None => (ByteArrayRef::empty(), ByteArrayRef::empty()),
322
+ };
323
+
324
+ Box::new(ClientHttpConnectProxyOptions {
325
+ target_host: proxy.target_addr.as_str().into(),
326
+ username,
327
+ password,
328
+ })
329
+ });
330
+
331
+ let client_options = Box::new(crate::client::ClientOptions {
332
+ target_url: options.target_url.as_str().into(),
333
+ client_name: options.client_name.as_str().into(),
334
+ client_version: options.client_version.as_str().into(),
335
+ metadata: metadata.as_deref().into(),
336
+ api_key: options.api_key.as_deref().into(),
337
+ identity: options.identity.as_str().into(),
338
+ tls_options: pointer_or_null(tls_options.as_deref()),
339
+ retry_options: &*retry_options,
340
+ keep_alive_options: pointer_or_null(keep_alive_options.as_deref()),
341
+ http_connect_proxy_options: pointer_or_null(proxy_options.as_deref()),
342
+ });
343
+
344
+ let client_options_ptr = &*client_options as *const _;
345
+ let user_data = Box::into_raw(Box::new(CallbackUserData {
346
+ data: (),
347
+ context: Arc::downgrade(self),
348
+ _allocations: Box::new((
349
+ options,
350
+ metadata,
351
+ tls_options,
352
+ retry_options,
353
+ keep_alive_options,
354
+ proxy_options,
355
+ client_options,
356
+ )),
357
+ })) as *mut libc::c_void;
358
+
359
+ let runtime = {
360
+ let mut guard = self.wait_for_available()?;
361
+ if !guard.client.is_null() {
362
+ return Err(anyhow!("Client already exists"));
363
+ }
364
+ guard.operation_state = ContextOperationState::InProgress;
365
+ guard.runtime
366
+ };
367
+
368
+ temporal_core_client_connect(
369
+ runtime,
370
+ client_options_ptr,
371
+ user_data,
372
+ client_connect_callback,
373
+ );
374
+ self.wait_for_operation()
375
+ }
376
+
377
+ pub fn rpc_call(
378
+ self: &Arc<Self>,
379
+ mut options: Box<OwnedRpcCallOptions>,
380
+ ) -> anyhow::Result<Vec<u8>> {
381
+ let c_options = Box::new(RpcCallOptions {
382
+ service: options.service,
383
+ rpc: options.rpc.as_str().into(),
384
+ req: options.req.as_slice().into(),
385
+ retry: options.retry,
386
+ metadata: options.metadata.as_mut().map(MetadataMap::as_str).into(),
387
+ timeout_millis: options.timeout_millis,
388
+ cancellation_token: options
389
+ .cancellation_token
390
+ .unwrap_or_else(std::ptr::null_mut),
391
+ });
392
+
393
+ let (runtime, client) = {
394
+ let mut guard = self.wait_for_available()?;
395
+ let client = guard.client;
396
+ if client.is_null() {
397
+ return Err(anyhow!("Client is null"));
398
+ }
399
+ guard.operation_state = ContextOperationState::InProgress;
400
+ (guard.runtime, client)
401
+ };
402
+
403
+ let c_options_ptr = &*c_options as *const _;
404
+ let user_data = Box::into_raw(Box::new(CallbackUserData {
405
+ data: runtime,
406
+ context: Arc::downgrade(self),
407
+ _allocations: Box::new((options, c_options)),
408
+ })) as *mut libc::c_void;
409
+
410
+ temporal_core_client_rpc_call(client, c_options_ptr, user_data, rpc_call_callback);
411
+ self.wait_for_operation_result().map(|r| *r)
412
+ }
413
+
414
+ fn wait_while(
415
+ &self,
416
+ condition: impl FnMut(&mut InnerContext) -> bool,
417
+ ) -> anyhow::Result<MutexGuard<'_, InnerContext>> {
418
+ self.inner
419
+ .lock()
420
+ .and_then(|lock| self.condvar.wait_while(lock, condition))
421
+ .map_err(|_| anyhow!("Context mutex poisoned"))
422
+ }
423
+
424
+ fn wait_for_available(&self) -> anyhow::Result<MutexGuard<'_, InnerContext>> {
425
+ let guard = self.wait_while(|inner| {
426
+ !matches!(inner.operation_state, ContextOperationState::Available) && !inner.is_disposed
427
+ })?;
428
+
429
+ if guard.is_disposed {
430
+ Err(anyhow!("Context already disposed"))
431
+ } else {
432
+ Ok(guard)
433
+ }
434
+ }
435
+
436
+ /// First, runs provided function under context mutex lock. Then, asserts `operation_state` is
437
+ /// `InProgress` and updates it. Finally, notifies the context condvar. Panic inside `func` or wrong
438
+ /// `operation_state` will poison the mutex.
439
+ fn complete_operation_catch_unwind(
440
+ &self,
441
+ func: impl FnOnce(&mut MutexGuard<InnerContext>) -> ContextOperationState,
442
+ ) -> std::thread::Result<()> {
443
+ let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
444
+ let mut guard = self.inner.lock().unwrap();
445
+ let new_state = func(&mut guard);
446
+ assert!(matches!(
447
+ guard.operation_state,
448
+ ContextOperationState::InProgress
449
+ ));
450
+ guard.operation_state = new_state;
451
+ }));
452
+ self.condvar.notify_all();
453
+ result
454
+ }
455
+
456
+ /// Waits for operation state to become `CallbackOk` or `CallbackError`, then changes it to `Available`.
457
+ /// Panics on wrong operation state or if `CallbackOk` contains a result.
458
+ fn wait_for_operation(&self) -> anyhow::Result<()> {
459
+ self.wait_for_operation_any()
460
+ .map(|r| assert!(r.is_none(), "Expected empty callback result"))
461
+ }
462
+
463
+ /// Waits for operation state to become `CallbackOk` or `CallbackError`, then changes it to `Available`.
464
+ /// Panics on wrong operation state or if `CallbackOk` has no result or the result is wrong type.
465
+ fn wait_for_operation_result<T>(&self) -> anyhow::Result<Box<T>>
466
+ where
467
+ T: Send + 'static,
468
+ {
469
+ self.wait_for_operation_any()
470
+ .map(|r| r.unwrap().downcast().unwrap())
471
+ }
472
+
473
+ /// Waits for operation state to become `CallbackOk` or `CallbackError`, then changes it to `Available`.
474
+ /// Panics on wrong operation state.
475
+ fn wait_for_operation_any(&self) -> anyhow::Result<Option<Box<dyn Any + Send>>> {
476
+ let mut guard = self
477
+ .wait_while(|inner| matches!(inner.operation_state, ContextOperationState::InProgress))
478
+ .unwrap();
479
+ match std::mem::replace(&mut guard.operation_state, ContextOperationState::Available) {
480
+ ContextOperationState::CallbackOk(result) => Ok(result),
481
+ ContextOperationState::CallbackError(e) => Err(e),
482
+ other => panic!("Unexpected operation state {other:?}"),
483
+ }
484
+ }
485
+ }
486
+
487
+ struct CallbackUserData<T, C> {
488
+ data: T,
489
+ context: Weak<C>,
490
+ _allocations: Box<dyn Any + 'static>,
491
+ }
492
+
493
+ extern "C" fn ephemeral_server_start_callback(
494
+ user_data: *mut libc::c_void,
495
+ mut server: *mut EphemeralServer,
496
+ mut server_target: *const ByteArray,
497
+ mut fail: *const ByteArray,
498
+ ) {
499
+ let user_data = unsafe { Box::from_raw(user_data as *mut CallbackUserData<(), Context>) };
500
+ if let Some(context) = user_data.context.upgrade() {
501
+ let _ = context.complete_operation_catch_unwind(|guard| {
502
+ let server_target =
503
+ byte_array_to_string(guard.runtime, std::mem::take(&mut server_target));
504
+ let fail = byte_array_to_string(guard.runtime, std::mem::take(&mut fail));
505
+
506
+ if let Some(fail) = fail {
507
+ ContextOperationState::CallbackError(anyhow!(
508
+ "Ephemeral server start failed: {}",
509
+ fail
510
+ ))
511
+ } else if server.is_null() {
512
+ ContextOperationState::CallbackError(anyhow!(
513
+ "Ephemeral server start failed: server is null"
514
+ ))
515
+ } else if let Some(server_target) = server_target {
516
+ guard.ephemeral_server = std::mem::take(&mut server);
517
+ guard.ephemeral_server_target = server_target;
518
+ ContextOperationState::CallbackOk(None)
519
+ } else {
520
+ ContextOperationState::CallbackError(anyhow!(
521
+ "Ephemeral server start failed: server target is null"
522
+ ))
523
+ }
524
+ });
525
+ }
526
+
527
+ if !server_target.is_null() {
528
+ temporal_core_byte_array_free(std::ptr::null_mut(), server_target);
529
+ }
530
+ if !fail.is_null() {
531
+ temporal_core_byte_array_free(std::ptr::null_mut(), fail);
532
+ }
533
+ if !server.is_null() {
534
+ // Creating new context as to not conflict with operation_status in original context
535
+ let shutdown_context = Context::new(InnerContext {
536
+ ephemeral_server: server,
537
+ ..Default::default()
538
+ });
539
+ let _ = shutdown_context.ephemeral_server_shutdown();
540
+ }
541
+ }
542
+
543
+ extern "C" fn ephemeral_server_shutdown_callback(
544
+ user_data: *mut libc::c_void,
545
+ mut fail: *const ByteArray,
546
+ ) {
547
+ let user_data = unsafe { Box::from_raw(user_data as *mut CallbackUserData<_, Context>) };
548
+ temporal_core_ephemeral_server_free(user_data.data);
549
+
550
+ if let Some(context) = user_data.context.upgrade() {
551
+ let _ = context.complete_operation_catch_unwind(|guard| {
552
+ if let Some(fail) = byte_array_to_string(guard.runtime, std::mem::take(&mut fail)) {
553
+ ContextOperationState::CallbackError(anyhow!(
554
+ "Ephemeral server shutdown failed: {}",
555
+ fail
556
+ ))
557
+ } else {
558
+ ContextOperationState::CallbackOk(None)
559
+ }
560
+ });
561
+ }
562
+
563
+ if !fail.is_null() {
564
+ temporal_core_byte_array_free(std::ptr::null_mut(), fail);
565
+ }
566
+ }
567
+
568
+ extern "C" fn client_connect_callback(
569
+ user_data: *mut libc::c_void,
570
+ mut client: *mut Client,
571
+ mut fail: *const ByteArray,
572
+ ) {
573
+ let user_data = unsafe { Box::from_raw(user_data as *mut CallbackUserData<(), Context>) };
574
+ if let Some(context) = user_data.context.upgrade() {
575
+ let _ = context.complete_operation_catch_unwind(|guard| {
576
+ if let Some(fail) = byte_array_to_string(guard.runtime, std::mem::take(&mut fail)) {
577
+ ContextOperationState::CallbackError(anyhow!("Client connect failed: {}", fail))
578
+ } else {
579
+ guard.client = std::mem::take(&mut client);
580
+ ContextOperationState::CallbackOk(None)
581
+ }
582
+ });
583
+ }
584
+
585
+ if !fail.is_null() {
586
+ temporal_core_byte_array_free(std::ptr::null_mut(), fail);
587
+ }
588
+ if !client.is_null() {
589
+ temporal_core_client_free(client);
590
+ }
591
+ }
592
+
593
+ extern "C" fn rpc_call_callback(
594
+ user_data: *mut libc::c_void,
595
+ mut success: *const ByteArray,
596
+ status_code: u32,
597
+ mut failure_message: *const ByteArray,
598
+ mut failure_details: *const ByteArray,
599
+ ) {
600
+ let user_data = unsafe { Box::from_raw(user_data as *mut CallbackUserData<(), Context>) };
601
+
602
+ if let Some(context) = user_data.context.upgrade() {
603
+ let _ = context.complete_operation_catch_unwind(|guard| {
604
+ let success = byte_array_to_vec(guard.runtime, std::mem::take(&mut success));
605
+ let mut failure_message =
606
+ byte_array_to_string(guard.runtime, std::mem::take(&mut failure_message));
607
+ let failure_details =
608
+ byte_array_to_vec(guard.runtime, std::mem::take(&mut failure_details));
609
+
610
+ if failure_message.is_none() {
611
+ if status_code != 0 {
612
+ failure_message = Some("No message".into());
613
+ } else if failure_details.is_some() {
614
+ failure_message = Some("No message, failure details not empty".into());
615
+ } else if success.is_none() {
616
+ failure_message = Some("No message, response is null".into());
617
+ }
618
+ }
619
+
620
+ if let Some(failure_message) = failure_message {
621
+ ContextOperationState::CallbackError(
622
+ RpcCallError {
623
+ status_code,
624
+ message: failure_message,
625
+ details: failure_details,
626
+ }
627
+ .into(),
628
+ )
629
+ } else {
630
+ ContextOperationState::CallbackOk(Some(Box::new(success.unwrap()) as _))
631
+ }
632
+ });
633
+ }
634
+
635
+ if !success.is_null() {
636
+ temporal_core_byte_array_free(std::ptr::null_mut(), success);
637
+ }
638
+ if !failure_message.is_null() {
639
+ temporal_core_byte_array_free(std::ptr::null_mut(), failure_message);
640
+ }
641
+ if !failure_details.is_null() {
642
+ temporal_core_byte_array_free(std::ptr::null_mut(), failure_details);
643
+ }
644
+ }