@temporalio/core-bridge 0.20.2 → 0.22.0

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 (50) hide show
  1. package/Cargo.lock +137 -127
  2. package/index.d.ts +7 -2
  3. package/package.json +3 -3
  4. package/releases/aarch64-apple-darwin/index.node +0 -0
  5. package/releases/x86_64-apple-darwin/index.node +0 -0
  6. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  7. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  8. package/sdk-core/.buildkite/docker/docker-compose.yaml +5 -4
  9. package/sdk-core/client/Cargo.toml +1 -0
  10. package/sdk-core/client/src/lib.rs +52 -9
  11. package/sdk-core/client/src/raw.rs +9 -1
  12. package/sdk-core/client/src/retry.rs +12 -1
  13. package/sdk-core/client/src/workflow_handle/mod.rs +183 -0
  14. package/sdk-core/core/src/abstractions.rs +10 -3
  15. package/sdk-core/core/src/core_tests/child_workflows.rs +7 -9
  16. package/sdk-core/core/src/core_tests/determinism.rs +8 -19
  17. package/sdk-core/core/src/core_tests/local_activities.rs +22 -32
  18. package/sdk-core/core/src/core_tests/queries.rs +272 -5
  19. package/sdk-core/core/src/core_tests/workers.rs +4 -34
  20. package/sdk-core/core/src/core_tests/workflow_tasks.rs +197 -41
  21. package/sdk-core/core/src/pending_activations.rs +11 -0
  22. package/sdk-core/core/src/telemetry/mod.rs +1 -1
  23. package/sdk-core/core/src/test_help/mod.rs +57 -7
  24. package/sdk-core/core/src/worker/mod.rs +64 -15
  25. package/sdk-core/core/src/workflow/machines/mod.rs +1 -1
  26. package/sdk-core/core/src/workflow/machines/timer_state_machine.rs +2 -2
  27. package/sdk-core/core/src/workflow/machines/workflow_machines.rs +14 -3
  28. package/sdk-core/core/src/workflow/mod.rs +5 -2
  29. package/sdk-core/core/src/workflow/workflow_tasks/cache_manager.rs +47 -2
  30. package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +16 -2
  31. package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +252 -125
  32. package/sdk-core/core-api/src/worker.rs +9 -0
  33. package/sdk-core/sdk/Cargo.toml +1 -0
  34. package/sdk-core/sdk/src/activity_context.rs +223 -0
  35. package/sdk-core/sdk/src/interceptors.rs +8 -2
  36. package/sdk-core/sdk/src/lib.rs +167 -122
  37. package/sdk-core/sdk-core-protos/src/history_info.rs +3 -7
  38. package/sdk-core/test-utils/Cargo.toml +1 -0
  39. package/sdk-core/test-utils/src/lib.rs +78 -37
  40. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +11 -4
  41. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +0 -1
  42. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +0 -3
  43. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +33 -17
  44. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +10 -1
  45. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +0 -1
  46. package/sdk-core/tests/integ_tests/workflow_tests.rs +71 -3
  47. package/sdk-core/tests/load_tests.rs +80 -6
  48. package/src/errors.rs +9 -2
  49. package/src/lib.rs +39 -16
  50. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
@@ -9,7 +9,7 @@
9
9
  //! An example of running an activity worker:
10
10
  //! ```no_run
11
11
  //! use std::{sync::Arc, str::FromStr};
12
- //! use temporal_sdk::{sdk_client_options, Worker};
12
+ //! use temporal_sdk::{sdk_client_options, Worker, ActContext};
13
13
  //! use temporal_sdk_core::{init_worker, Url};
14
14
  //! use temporal_sdk_core_api::worker::{WorkerConfig, WorkerConfigBuilder};
15
15
  //!
@@ -23,7 +23,7 @@
23
23
  //! let mut worker = Worker::new_from_core(Arc::new(core_worker), "task_queue");
24
24
  //! worker.register_activity(
25
25
  //! "echo_activity",
26
- //! |echo_me: String| async move { Ok(echo_me) },
26
+ //! |_ctx: ActContext, echo_me: String| async move { Ok(echo_me) },
27
27
  //! );
28
28
  //! worker.run().await?;
29
29
  //! Ok(())
@@ -33,12 +33,15 @@
33
33
  #[macro_use]
34
34
  extern crate tracing;
35
35
 
36
+ mod activity_context;
36
37
  mod conversions;
37
38
  pub mod interceptors;
38
39
  mod payload_converter;
39
40
  mod workflow_context;
40
41
  mod workflow_future;
41
42
 
43
+ pub use activity_context::ActContext;
44
+
42
45
  pub use workflow_context::{
43
46
  ActivityOptions, CancellableFuture, ChildWorkflow, ChildWorkflowOptions, LocalActivityOptions,
44
47
  Signal, SignalData, SignalWorkflowOptions, WfContext,
@@ -49,9 +52,10 @@ use crate::{
49
52
  workflow_context::{ChildWfCommon, PendingChildWorkflow},
50
53
  };
51
54
  use anyhow::{anyhow, bail};
52
- use futures::{future::BoxFuture, stream::FuturesUnordered, FutureExt, StreamExt};
55
+ use futures::{future::BoxFuture, FutureExt, StreamExt, TryFutureExt, TryStreamExt};
53
56
  use once_cell::sync::OnceCell;
54
57
  use std::{
58
+ cell::RefCell,
55
59
  collections::HashMap,
56
60
  fmt::{Debug, Display, Formatter},
57
61
  future::Future,
@@ -82,12 +86,12 @@ use temporal_sdk_core_protos::{
82
86
  };
83
87
  use tokio::{
84
88
  sync::{
85
- mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
86
- oneshot, watch,
87
- watch::Receiver,
89
+ mpsc::{unbounded_channel, UnboundedSender},
90
+ oneshot,
88
91
  },
89
92
  task::JoinError,
90
93
  };
94
+ use tokio_stream::wrappers::UnboundedReceiverStream;
91
95
  use tokio_util::sync::CancellationToken;
92
96
 
93
97
  const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -120,13 +124,19 @@ struct CommonWorker {
120
124
  }
121
125
 
122
126
  struct WorkflowHalf {
123
- /// Maps run id to the driver
124
- workflows: HashMap<String, UnboundedSender<WorkflowActivation>>,
127
+ /// Maps run id to cached workflow state
128
+ workflows: RefCell<HashMap<String, WorkflowData>>,
125
129
  /// Maps workflow type to the function for executing workflow runs with that ID
126
- workflow_fns: HashMap<String, WorkflowFunction>,
127
- /// Handles for each spawned workflow run are inserted here to be cleaned up when all runs
128
- /// are finished
129
- join_handles: FuturesUnordered<BoxFuture<'static, Result<WorkflowResult<()>, JoinError>>>,
130
+ workflow_fns: RefCell<HashMap<String, WorkflowFunction>>,
131
+ }
132
+ struct WorkflowData {
133
+ /// Channel used to send the workflow activations
134
+ activation_chan: UnboundedSender<WorkflowActivation>,
135
+ }
136
+
137
+ struct WorkflowFutureHandle<F: Future<Output = Result<WorkflowResult<()>, JoinError>>> {
138
+ join_handle: F,
139
+ run_id: String,
130
140
  }
131
141
 
132
142
  struct ActivityHalf {
@@ -136,8 +146,6 @@ struct ActivityHalf {
136
146
  }
137
147
 
138
148
  impl Worker {
139
- // pub fn new(cfg: WorkerConfig) -> Self {}
140
-
141
149
  #[doc(hidden)]
142
150
  /// Create a new rust worker from a core worker
143
151
  pub fn new_from_core(worker: Arc<dyn CoreWorker>, task_queue: impl Into<String>) -> Self {
@@ -150,7 +158,6 @@ impl Worker {
150
158
  workflow_half: WorkflowHalf {
151
159
  workflows: Default::default(),
152
160
  workflow_fns: Default::default(),
153
- join_handles: FuturesUnordered::new(),
154
161
  },
155
162
  activity_half: ActivityHalf {
156
163
  activity_fns: Default::default(),
@@ -180,6 +187,7 @@ impl Worker {
180
187
  ) {
181
188
  self.workflow_half
182
189
  .workflow_fns
190
+ .get_mut()
183
191
  .insert(workflow_type.into(), wf_function.into());
184
192
  }
185
193
 
@@ -201,67 +209,103 @@ impl Worker {
201
209
  /// Runs the worker. Eventually resolves after the worker has been explicitly shut down,
202
210
  /// or may return early with an error in the event of some unresolvable problem.
203
211
  pub async fn run(&mut self) -> Result<(), anyhow::Error> {
204
- let (shutdown_tx, shutdown_rx) = watch::channel(false);
205
- let pollers = async move {
206
- let (common, wf_half, act_half) = self.split_apart();
207
- let (completions_tx, mut completions_rx) = unbounded_channel();
208
- let (wf_poll_res, act_poll_res) = tokio::join!(
209
- // Workflow polling loop
210
- async {
211
- loop {
212
- info!("Polling");
213
- let activation = match common.worker.poll_workflow_activation().await {
214
- Err(PollWfError::ShutDown) => {
215
- break Result::<_, anyhow::Error>::Ok(());
216
- }
217
- o => o?,
218
- };
219
- wf_half
220
- .workflow_activation_handler(
221
- common,
222
- &shutdown_rx,
223
- &completions_tx,
224
- &mut completions_rx,
225
- activation,
226
- )
227
- .await?;
212
+ let shutdown_token = CancellationToken::new();
213
+ let (common, wf_half, act_half) = self.split_apart();
214
+ let (wf_future_tx, wf_future_rx) = unbounded_channel();
215
+ let (completions_tx, completions_rx) = unbounded_channel();
216
+ let wf_future_joiner = async {
217
+ UnboundedReceiverStream::new(wf_future_rx)
218
+ .map(Result::<_, anyhow::Error>::Ok)
219
+ .try_for_each_concurrent(
220
+ None,
221
+ |WorkflowFutureHandle {
222
+ join_handle,
223
+ run_id,
224
+ }| {
225
+ let wf_half = &*wf_half;
226
+ async move {
227
+ join_handle.await??;
228
+ wf_half.workflows.borrow_mut().remove(&run_id);
229
+ Ok(())
230
+ }
231
+ },
232
+ )
233
+ .await
234
+ };
235
+ let wf_completion_processor = async {
236
+ let r = UnboundedReceiverStream::new(completions_rx)
237
+ .map(Ok)
238
+ .try_for_each_concurrent(None, |completion| async {
239
+ if let Some(ref i) = common.worker_interceptor {
240
+ i.on_workflow_activation_completion(&completion).await;
228
241
  }
229
- },
230
- // Only poll on the activity queue if activity functions have been registered. This
231
- // makes tests which use mocks dramatically more manageable.
232
- async {
233
- let mut shutdown_rx = shutdown_rx.clone();
234
- if !act_half.activity_fns.is_empty() {
235
- loop {
236
- tokio::select! {
237
- activity = common.worker.poll_activity_task() => {
238
- if matches!(activity, Err(PollActivityError::ShutDown)) {
239
- break;
240
- }
241
- act_half.activity_task_handler(common.worker.clone(),
242
- activity?)?;
243
- },
244
- _ = shutdown_rx.changed() => { break }
245
- }
242
+ common.worker.complete_workflow_activation(completion).await
243
+ })
244
+ .map_err(Into::into)
245
+ .await;
246
+ r
247
+ };
248
+ tokio::try_join!(
249
+ // Workflow polling loop
250
+ async {
251
+ loop {
252
+ let activation = match common.worker.poll_workflow_activation().await {
253
+ Err(PollWfError::ShutDown) => {
254
+ break;
246
255
  }
256
+ o => o?,
247
257
  };
248
- Result::<_, anyhow::Error>::Ok(())
258
+ if let Some(wf_fut) = wf_half.workflow_activation_handler(
259
+ common,
260
+ shutdown_token.clone(),
261
+ activation,
262
+ &completions_tx,
263
+ )? {
264
+ if wf_future_tx.send(wf_fut).is_err() {
265
+ panic!(
266
+ "Receive half of completion processor channel cannot be dropped"
267
+ );
268
+ }
269
+ }
249
270
  }
250
- );
251
- wf_poll_res?;
252
- // TODO: Activity loop errors don't show up until wf loop exits/errors
253
- act_poll_res?;
254
- Result::<_, anyhow::Error>::Ok(self)
255
- };
271
+ // Tell still-alive workflows to evict themselves
272
+ shutdown_token.cancel();
273
+ // It's important to drop these so the future and completion processors will
274
+ // terminate.
275
+ drop(wf_future_tx);
276
+ drop(completions_tx);
277
+ Result::<_, anyhow::Error>::Ok(())
278
+ },
279
+ // Only poll on the activity queue if activity functions have been registered. This
280
+ // makes tests which use mocks dramatically more manageable.
281
+ async {
282
+ if !act_half.activity_fns.is_empty() {
283
+ let shutdown_token = shutdown_token.clone();
284
+ loop {
285
+ tokio::select! {
286
+ activity = common.worker.poll_activity_task() => {
287
+ if matches!(activity, Err(PollActivityError::ShutDown)) {
288
+ break;
289
+ }
290
+ act_half.activity_task_handler(common.worker.clone(),
291
+ common.task_queue.clone(),
292
+ activity?)?;
293
+ },
294
+ _ = shutdown_token.cancelled() => { break }
295
+ }
296
+ }
297
+ };
298
+ Result::<_, anyhow::Error>::Ok(())
299
+ },
300
+ wf_future_joiner,
301
+ wf_completion_processor,
302
+ )?;
256
303
 
257
- let myself = pollers.await?;
258
- info!("Polling loop exited");
259
- let _ = shutdown_tx.send(true);
260
- while let Some(h) = myself.workflow_half.join_handles.next().await {
261
- h??;
304
+ info!("Polling loops exited");
305
+ if let Some(i) = self.common.worker_interceptor.as_ref() {
306
+ i.on_shutdown(self);
262
307
  }
263
- myself.common.worker.shutdown().await;
264
- myself.workflow_half.workflows.clear();
308
+ self.common.worker.shutdown().await;
265
309
  Ok(())
266
310
  }
267
311
 
@@ -277,6 +321,12 @@ impl Worker {
277
321
  self.common.worker = new_core_worker;
278
322
  }
279
323
 
324
+ /// Returns number of currently cached workflows as understood by the SDK. Importantly, this
325
+ /// is not the same as understood by core, though they *should* always be in sync.
326
+ pub fn cached_workflows(&self) -> usize {
327
+ self.workflow_half.workflows.borrow().len()
328
+ }
329
+
280
330
  fn split_apart(&mut self) -> (&mut CommonWorker, &mut WorkflowHalf, &mut ActivityHalf) {
281
331
  (
282
332
  &mut self.common,
@@ -287,14 +337,19 @@ impl Worker {
287
337
  }
288
338
 
289
339
  impl WorkflowHalf {
290
- async fn workflow_activation_handler(
291
- &mut self,
340
+ fn workflow_activation_handler(
341
+ &self,
292
342
  common: &CommonWorker,
293
- shutdown_rx: &Receiver<bool>,
294
- completions_tx: &UnboundedSender<WorkflowActivationCompletion>,
295
- completions_rx: &mut UnboundedReceiver<WorkflowActivationCompletion>,
343
+ shutdown_token: CancellationToken,
296
344
  activation: WorkflowActivation,
297
- ) -> Result<(), anyhow::Error> {
345
+ completions_tx: &UnboundedSender<WorkflowActivationCompletion>,
346
+ ) -> Result<
347
+ Option<WorkflowFutureHandle<impl Future<Output = Result<WorkflowResult<()>, JoinError>>>>,
348
+ anyhow::Error,
349
+ > {
350
+ let mut res = None;
351
+ let run_id = activation.run_id.clone();
352
+
298
353
  // If the activation is to start a workflow, create a new workflow driver for it,
299
354
  // using the function associated with that workflow id
300
355
  if let Some(WorkflowActivationJob {
@@ -302,8 +357,8 @@ impl WorkflowHalf {
302
357
  }) = activation.jobs.get(0)
303
358
  {
304
359
  let workflow_type = &sw.workflow_type;
305
- let wf_function = self
306
- .workflow_fns
360
+ let wf_fns_borrow = self.workflow_fns.borrow();
361
+ let wf_function = wf_fns_borrow
307
362
  .get(workflow_type)
308
363
  .ok_or_else(|| anyhow!("Workflow type {workflow_type} not found"))?;
309
364
 
@@ -314,60 +369,48 @@ impl WorkflowHalf {
314
369
  sw.arguments.clone(),
315
370
  completions_tx.clone(),
316
371
  );
317
- let mut shutdown_rx = shutdown_rx.clone();
318
372
  let jh = tokio::spawn(async move {
319
373
  tokio::select! {
320
374
  r = wff => r,
321
- _ = shutdown_rx.changed() => Ok(WfExitValue::Evicted)
375
+ // TODO: This probably shouldn't abort early, as it could cause an in-progress
376
+ // complete to abort. Send synthetic remove activation
377
+ _ = shutdown_token.cancelled() => {
378
+ Ok(WfExitValue::Evicted)
379
+ }
322
380
  }
323
381
  });
324
- self.workflows
325
- .insert(activation.run_id.clone(), activations);
326
- self.join_handles.push(jh.boxed());
382
+ res = Some(WorkflowFutureHandle {
383
+ join_handle: jh,
384
+ run_id: run_id.clone(),
385
+ });
386
+ self.workflows.borrow_mut().insert(
387
+ run_id.clone(),
388
+ WorkflowData {
389
+ activation_chan: activations,
390
+ },
391
+ );
327
392
  }
328
393
 
329
394
  // The activation is expected to apply to some workflow we know about. Use it to
330
395
  // unblock things and advance the workflow.
331
- if let Some(tx) = self.workflows.get_mut(&activation.run_id) {
332
- tx.send(activation)
396
+ if let Some(dat) = self.workflows.borrow_mut().get_mut(&run_id) {
397
+ dat.activation_chan
398
+ .send(activation)
333
399
  .expect("Workflow should exist if we're sending it an activation");
334
400
  } else {
335
401
  bail!("Got activation for unknown workflow");
336
402
  };
337
403
 
338
- let completion = completions_rx.recv().await.expect("No workflows left?");
339
- if let Some(ref i) = common.worker_interceptor {
340
- i.on_workflow_activation_completion(&completion);
341
- }
342
- common
343
- .worker
344
- .complete_workflow_activation(completion)
345
- .await?;
346
- Ok(())
404
+ Ok(res)
347
405
  }
348
406
  }
349
407
 
350
- tokio::task_local! {
351
- // This works, but maybe just passing a context object for activities like WFs is better
352
- static ACT_CANCEL_TOK: CancellationToken
353
- }
354
-
355
- /// Returns a future the completes if and when the activity this was called inside has been
356
- /// cancelled
357
- pub async fn act_cancelled() {
358
- ACT_CANCEL_TOK.with(|ct| ct.clone()).cancelled().await
359
- }
360
-
361
- /// Returns true if this activity has already been cancelled
362
- pub fn act_is_cancelled() -> bool {
363
- ACT_CANCEL_TOK.with(|ct| ct.is_cancelled())
364
- }
365
-
366
408
  impl ActivityHalf {
367
409
  /// Spawns off a task to handle the provided activity task
368
410
  fn activity_task_handler(
369
411
  &mut self,
370
412
  worker: Arc<dyn CoreWorker>,
413
+ task_queue: String,
371
414
  activity: ActivityTask,
372
415
  ) -> Result<(), anyhow::Error> {
373
416
  match activity.variant {
@@ -383,13 +426,14 @@ impl ActivityHalf {
383
426
  })?
384
427
  .clone();
385
428
  let ct = CancellationToken::new();
429
+ let task_token = activity.task_token;
386
430
  self.task_tokens_to_cancels
387
- .insert(activity.task_token.clone().into(), ct.clone());
431
+ .insert(task_token.clone().into(), ct.clone());
388
432
 
389
- tokio::spawn(ACT_CANCEL_TOK.scope(ct, async move {
390
- let mut inputs = start.input;
391
- let arg = inputs.pop().unwrap_or_default();
392
- let output = (act_fn.act_func)(arg).await;
433
+ let (ctx, arg) =
434
+ ActContext::new(worker.clone(), ct, task_queue, task_token.clone(), start);
435
+ tokio::spawn(async move {
436
+ let output = (act_fn.act_func)(ctx, arg).await;
393
437
  let result = match output {
394
438
  Ok(res) => ActivityExecutionResult::ok(res),
395
439
  Err(err) => match err.downcast::<ActivityCancelledError>() {
@@ -399,12 +443,12 @@ impl ActivityHalf {
399
443
  };
400
444
  worker
401
445
  .complete_activity_task(ActivityTaskCompletion {
402
- task_token: activity.task_token,
446
+ task_token,
403
447
  result: Some(result),
404
448
  })
405
449
  .await?;
406
450
  Result::<_, anyhow::Error>::Ok(())
407
- }));
451
+ });
408
452
  }
409
453
  Some(activity_task::Variant::Cancel(_)) => {
410
454
  if let Some(ct) = self.task_tokens_to_cancels.get(&activity.task_token.into()) {
@@ -622,8 +666,9 @@ impl<T: Debug> WfExitValue<T> {
622
666
  }
623
667
  }
624
668
 
625
- type BoxActFn =
626
- Arc<dyn Fn(Payload) -> BoxFuture<'static, Result<Payload, anyhow::Error>> + Send + Sync>;
669
+ type BoxActFn = Arc<
670
+ dyn Fn(ActContext, Payload) -> BoxFuture<'static, Result<Payload, anyhow::Error>> + Send + Sync,
671
+ >;
627
672
  /// Container for user-defined activity functions
628
673
  #[derive(Clone)]
629
674
  pub struct ActivityFunction {
@@ -650,16 +695,16 @@ pub trait IntoActivityFunc<Args, Res> {
650
695
 
651
696
  impl<A, Rf, R, F> IntoActivityFunc<A, Rf> for F
652
697
  where
653
- F: (Fn(A) -> Rf) + Sync + Send + 'static,
698
+ F: (Fn(ActContext, A) -> Rf) + Sync + Send + 'static,
654
699
  A: FromJsonPayloadExt + Send,
655
700
  Rf: Future<Output = Result<R, anyhow::Error>> + Send + 'static,
656
701
  R: AsJsonPayloadExt,
657
702
  {
658
703
  fn into_activity_fn(self) -> BoxActFn {
659
- let wrapper = move |input: Payload| {
704
+ let wrapper = move |ctx: ActContext, input: Payload| {
660
705
  // Some minor gymnastics are required to avoid needing to clone the function
661
706
  match A::from_json_payload(&input) {
662
- Ok(deser) => (self)(deser)
707
+ Ok(deser) => (self)(ctx, deser)
663
708
  .map(|r| r.map(|r| r.as_json_payload())?)
664
709
  .boxed(),
665
710
  Err(e) => async move { Err(e.into()) }.boxed(),
@@ -117,17 +117,13 @@ impl HistoryInfo {
117
117
  /// Remove events from the beginning of this history such that it looks like what would've been
118
118
  /// delivered on a sticky queue where the previously started task was the one before the last
119
119
  /// task in this history.
120
- ///
121
- /// This is not *fully* accurate in that it will include commands that were part of the last
122
- /// WFT completion, which the server would typically not include, but it's good enough for
123
- /// testing.
124
120
  pub fn make_incremental(&mut self) {
125
121
  let last_complete_ix = self
126
122
  .events
127
123
  .iter()
128
124
  .rposition(|he| he.event_type() == EventType::WorkflowTaskCompleted)
129
125
  .expect("Must be a WFT completed event in history");
130
- self.events.drain(0..=last_complete_ix);
126
+ self.events.drain(0..last_complete_ix);
131
127
  }
132
128
 
133
129
  pub fn events(&self) -> &[HistoryEvent] {
@@ -223,7 +219,7 @@ mod tests {
223
219
  fn incremental_works() {
224
220
  let t = single_timer("timer1");
225
221
  let hi = t.get_one_wft(2).unwrap();
226
- assert_eq!(hi.events().len(), 4);
227
- assert_eq!(hi.events()[0].event_id, 5);
222
+ assert_eq!(hi.events().len(), 5);
223
+ assert_eq!(hi.events()[0].event_id, 4);
228
224
  }
229
225
  }
@@ -14,6 +14,7 @@ async-trait = "0.1"
14
14
  base64 = "0.13"
15
15
  futures = "0.3"
16
16
  log = "0.4"
17
+ parking_lot = "0.12"
17
18
  prost = "0.9"
18
19
  prost-types = "0.9"
19
20
  rand = "0.8"