1
+ use anyhow:: Context ;
1
2
use prost:: Message ;
2
3
use pyo3:: exceptions:: { PyException , PyRuntimeError , PyValueError } ;
3
4
use pyo3:: prelude:: * ;
@@ -34,9 +35,7 @@ pub struct WorkerConfig {
34
35
build_id : String ,
35
36
identity_override : Option < String > ,
36
37
max_cached_workflows : usize ,
37
- max_outstanding_workflow_tasks : usize ,
38
- max_outstanding_activities : usize ,
39
- max_outstanding_local_activities : usize ,
38
+ tuner : TunerHolder ,
40
39
max_concurrent_workflow_task_polls : usize ,
41
40
nonsticky_to_sticky_poll_ratio : f32 ,
42
41
max_concurrent_activity_task_polls : usize ,
@@ -52,6 +51,39 @@ pub struct WorkerConfig {
52
51
nondeterminism_as_workflow_fail_for_types : HashSet < String > ,
53
52
}
54
53
54
+ #[ derive( FromPyObject ) ]
55
+ pub struct TunerHolder {
56
+ workflow_slot_supplier : SlotSupplier ,
57
+ activity_slot_supplier : SlotSupplier ,
58
+ local_activity_slot_supplier : SlotSupplier ,
59
+ }
60
+
61
+ #[ derive( FromPyObject ) ]
62
+ pub enum SlotSupplier {
63
+ FixedSize ( FixedSizeSlotSupplier ) ,
64
+ ResourceBased ( ResourceBasedSlotSupplier ) ,
65
+ }
66
+
67
+ #[ derive( FromPyObject ) ]
68
+ pub struct FixedSizeSlotSupplier {
69
+ num_slots : usize ,
70
+ }
71
+
72
+ #[ derive( FromPyObject ) ]
73
+ pub struct ResourceBasedSlotSupplier {
74
+ minimum_slots : usize ,
75
+ maximum_slots : usize ,
76
+ // Need pyo3 0.21+ for this to be std Duration
77
+ ramp_throttle_ms : u64 ,
78
+ tuner_config : ResourceBasedTunerConfig ,
79
+ }
80
+
81
+ #[ derive( FromPyObject , Clone , Copy , PartialEq ) ]
82
+ pub struct ResourceBasedTunerConfig {
83
+ target_memory_usage : f64 ,
84
+ target_cpu_usage : f64 ,
85
+ }
86
+
55
87
macro_rules! enter_sync {
56
88
( $runtime: expr) => {
57
89
if let Some ( subscriber) = $runtime. core. telemetry( ) . trace_subscriber( ) {
@@ -73,7 +105,7 @@ pub fn new_worker(
73
105
config,
74
106
client. retry_client . clone ( ) . into_inner ( ) ,
75
107
)
76
- . map_err ( |err| PyValueError :: new_err ( format ! ( "Failed creating worker: {}" , err ) ) ) ?;
108
+ . context ( "Failed creating worker" ) ?;
77
109
Ok ( WorkerRef {
78
110
worker : Some ( Arc :: new ( worker) ) ,
79
111
runtime : runtime_ref. runtime . clone ( ) ,
@@ -107,9 +139,11 @@ impl WorkerRef {
107
139
fn validate < ' p > ( & self , py : Python < ' p > ) -> PyResult < & ' p PyAny > {
108
140
let worker = self . worker . as_ref ( ) . unwrap ( ) . clone ( ) ;
109
141
self . runtime . future_into_py ( py, async move {
110
- worker. validate ( ) . await . map_err ( |err| {
111
- PyRuntimeError :: new_err ( format ! ( "Worker validation failed: {}" , err) )
112
- } )
142
+ worker
143
+ . validate ( )
144
+ . await
145
+ . context ( "Worker validation failed" )
146
+ . map_err ( Into :: into)
113
147
} )
114
148
}
115
149
@@ -151,10 +185,8 @@ impl WorkerRef {
151
185
worker
152
186
. complete_workflow_activation ( completion)
153
187
. await
154
- . map_err ( |err| {
155
- // TODO(cretz): More error types
156
- PyRuntimeError :: new_err ( format ! ( "Completion failure: {}" , err) )
157
- } )
188
+ . context ( "Completion failure" )
189
+ . map_err ( Into :: into)
158
190
} )
159
191
}
160
192
@@ -166,10 +198,8 @@ impl WorkerRef {
166
198
worker
167
199
. complete_activity_task ( completion)
168
200
. await
169
- . map_err ( |err| {
170
- // TODO(cretz): More error types
171
- PyRuntimeError :: new_err ( format ! ( "Completion failure: {}" , err) )
172
- } )
201
+ . context ( "Completion failure" )
202
+ . map_err ( Into :: into)
173
203
} )
174
204
}
175
205
@@ -226,16 +256,15 @@ impl TryFrom<WorkerConfig> for temporal_sdk_core::WorkerConfig {
226
256
type Error = PyErr ;
227
257
228
258
fn try_from ( conf : WorkerConfig ) -> PyResult < Self > {
259
+ let converted_tuner: temporal_sdk_core:: TunerHolder = conf. tuner . try_into ( ) ?;
229
260
temporal_sdk_core:: WorkerConfigBuilder :: default ( )
230
261
. namespace ( conf. namespace )
231
262
. task_queue ( conf. task_queue )
232
263
. worker_build_id ( conf. build_id )
233
264
. client_identity_override ( conf. identity_override )
234
265
. max_cached_workflows ( conf. max_cached_workflows )
235
- . max_outstanding_workflow_tasks ( conf. max_outstanding_workflow_tasks )
236
- . max_outstanding_activities ( conf. max_outstanding_activities )
237
- . max_outstanding_local_activities ( conf. max_outstanding_local_activities )
238
266
. max_concurrent_wft_polls ( conf. max_concurrent_workflow_task_polls )
267
+ . tuner ( Arc :: new ( converted_tuner) )
239
268
. nonsticky_to_sticky_poll_ratio ( conf. nonsticky_to_sticky_poll_ratio )
240
269
. max_concurrent_at_polls ( conf. max_concurrent_activity_task_polls )
241
270
. no_remote_activities ( conf. no_remote_activities )
@@ -276,6 +305,90 @@ impl TryFrom<WorkerConfig> for temporal_sdk_core::WorkerConfig {
276
305
}
277
306
}
278
307
308
+ impl TryFrom < TunerHolder > for temporal_sdk_core:: TunerHolder {
309
+ type Error = PyErr ;
310
+
311
+ fn try_from ( holder : TunerHolder ) -> PyResult < Self > {
312
+ // Verify all resource-based options are the same if any are set
313
+ let maybe_wf_resource_opts =
314
+ if let SlotSupplier :: ResourceBased ( ref ss) = holder. workflow_slot_supplier {
315
+ Some ( & ss. tuner_config )
316
+ } else {
317
+ None
318
+ } ;
319
+ let maybe_act_resource_opts =
320
+ if let SlotSupplier :: ResourceBased ( ref ss) = holder. activity_slot_supplier {
321
+ Some ( & ss. tuner_config )
322
+ } else {
323
+ None
324
+ } ;
325
+ let maybe_local_act_resource_opts =
326
+ if let SlotSupplier :: ResourceBased ( ref ss) = holder. local_activity_slot_supplier {
327
+ Some ( & ss. tuner_config )
328
+ } else {
329
+ None
330
+ } ;
331
+ let all_resource_opts = [
332
+ maybe_wf_resource_opts,
333
+ maybe_act_resource_opts,
334
+ maybe_local_act_resource_opts,
335
+ ] ;
336
+ let mut set_resource_opts = all_resource_opts. iter ( ) . flatten ( ) ;
337
+ let first = set_resource_opts. next ( ) ;
338
+ let all_are_same = if let Some ( first) = first {
339
+ set_resource_opts. all ( |elem| elem == first)
340
+ } else {
341
+ true
342
+ } ;
343
+ if !all_are_same {
344
+ return Err ( PyValueError :: new_err (
345
+ "All resource-based slot suppliers must have the same ResourceBasedTunerOptions" ,
346
+ ) ) ;
347
+ }
348
+
349
+ let mut options = temporal_sdk_core:: TunerHolderOptionsBuilder :: default ( ) ;
350
+ if let Some ( first) = first {
351
+ options. resource_based_options (
352
+ temporal_sdk_core:: ResourceBasedSlotsOptionsBuilder :: default ( )
353
+ . target_mem_usage ( first. target_memory_usage )
354
+ . target_cpu_usage ( first. target_cpu_usage )
355
+ . build ( )
356
+ . expect ( "Building ResourceBasedSlotsOptions is infallible" ) ,
357
+ ) ;
358
+ } ;
359
+ options
360
+ . workflow_slot_options ( holder. workflow_slot_supplier . try_into ( ) ?)
361
+ . activity_slot_options ( holder. activity_slot_supplier . try_into ( ) ?)
362
+ . local_activity_slot_options ( holder. local_activity_slot_supplier . try_into ( ) ?) ;
363
+ Ok ( options
364
+ . build ( )
365
+ . map_err ( |e| PyValueError :: new_err ( format ! ( "Invalid tuner holder options: {}" , e) ) ) ?
366
+ . build_tuner_holder ( )
367
+ . context ( "Failed building tuner holder" ) ?)
368
+ }
369
+ }
370
+
371
+ impl TryFrom < SlotSupplier > for temporal_sdk_core:: SlotSupplierOptions {
372
+ type Error = PyErr ;
373
+
374
+ fn try_from ( supplier : SlotSupplier ) -> PyResult < temporal_sdk_core:: SlotSupplierOptions > {
375
+ Ok ( match supplier {
376
+ SlotSupplier :: FixedSize ( fs) => temporal_sdk_core:: SlotSupplierOptions :: FixedSize {
377
+ slots : fs. num_slots ,
378
+ } ,
379
+ SlotSupplier :: ResourceBased ( ss) => {
380
+ temporal_sdk_core:: SlotSupplierOptions :: ResourceBased (
381
+ temporal_sdk_core:: ResourceSlotOptions :: new (
382
+ ss. minimum_slots ,
383
+ ss. maximum_slots ,
384
+ Duration :: from_millis ( ss. ramp_throttle_ms ) ,
385
+ ) ,
386
+ )
387
+ }
388
+ } )
389
+ }
390
+ }
391
+
279
392
/// For feeding histories into core during replay
280
393
#[ pyclass]
281
394
pub struct HistoryPusher {
0 commit comments