Skip to content

Commit 72b9982

Browse files
committed
Progress.
1 parent c547568 commit 72b9982

File tree

11 files changed

+708
-235
lines changed

11 files changed

+708
-235
lines changed

Cargo.lock

Lines changed: 457 additions & 145 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ edition = "2021"
55

66
[dependencies]
77
anyhow = "1"
8-
ctrlc-async = "3"
8+
ctrlc2 = { version = "3", features = ["termination", "tokio"] }
99
derive_builder = "0.20"
1010
envy = "0.4"
1111
futures-util = "0.3"
@@ -17,9 +17,14 @@ serde = { version = "1", features = ["derive"] }
1717
serde_json = "1"
1818
thiserror = "1"
1919
tokio = { version = "1", features = ["fs", "macros", "rt-multi-thread", "sync"] }
20-
tonic = { version = "0.12", features = ["tls"] }
20+
tonic = { version = "0.12", features = ["tls", "tls-native-roots"] }
2121
tracing = "0.1"
2222

2323
[build-dependencies]
2424
tonic-build = "0.12"
2525
tempfile = "3"
26+
27+
[dev-dependencies]
28+
dotenv = "0.15"
29+
rstest = "0.23"
30+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

build.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ fn prepend_package_name_and_build(
1414
writeln!(temp_file, "package {package_name};")?;
1515

1616
let target_path = temp_file.into_temp_path();
17-
tonic_build::compile_protos(target_path)?;
17+
tonic_build::configure()
18+
.disable_package_emission()
19+
.compile_protos(
20+
&[&target_path],
21+
&[target_path.parent().expect("must succeed")],
22+
)?;
1823
Ok(())
1924
}
2025

examples/fibonacci.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use hatchet_sdk::{Client, StepBuilder, WorkflowBuilder};
2+
3+
fn fibonacci(n: u32) -> u32 {
4+
(1..=n)
5+
.fold((0, 1), |(last, current), _| (current, last + current))
6+
.0
7+
}
8+
9+
#[cfg(test)]
10+
mod tests {
11+
use rstest::rstest;
12+
13+
#[rstest]
14+
#[case(0, 0)]
15+
#[case(1, 1)]
16+
#[case(2, 1)]
17+
#[case(3, 2)]
18+
#[case(4, 3)]
19+
#[case(5, 5)]
20+
#[case(6, 8)]
21+
fn fibonacci_test(#[case] input: u32, #[case] expected: u32) {
22+
assert_eq!(expected, super::fibonacci(input))
23+
}
24+
}
25+
26+
#[derive(serde::Deserialize)]
27+
struct Input {
28+
n: u32,
29+
}
30+
31+
#[derive(serde::Serialize)]
32+
struct Output {
33+
result: u32,
34+
}
35+
36+
async fn execute(Input { n }: Input) -> anyhow::Result<Output> {
37+
Ok(Output {
38+
result: fibonacci(n),
39+
})
40+
}
41+
42+
#[tokio::main]
43+
async fn main() -> anyhow::Result<()> {
44+
dotenv::dotenv().ok();
45+
tracing_subscriber::fmt()
46+
.with_target(false)
47+
.with_env_filter(
48+
tracing_subscriber::EnvFilter::from_default_env()
49+
.add_directive("hatchet_sdk=debug".parse()?),
50+
)
51+
.init();
52+
53+
let client = Client::new()?;
54+
let mut worker = client.worker("example").build();
55+
worker.register_workflow(
56+
WorkflowBuilder::default()
57+
.name("fibonacci")
58+
.step(
59+
StepBuilder::default()
60+
.name("compute")
61+
.function(&execute)
62+
.build()?,
63+
)
64+
.build()?,
65+
);
66+
worker.start().await?;
67+
Ok(())
68+
}

hatchet

Submodule hatchet updated 421 files

src/client.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ use crate::worker::WorkerBuilder;
66
pub(crate) struct Environment {
77
pub(crate) token: SecretString,
88
pub(crate) host_port: Option<String>,
9-
#[serde(default)]
10-
pub(crate) listener_v2_timeout: u64,
9+
pub(crate) listener_v2_timeout: Option<u64>,
1110
#[serde(default)]
1211
pub(crate) tls_strategy: crate::ClientTlStrategy,
1312
pub(crate) tls_cert_file: Option<String>,

src/error.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,27 @@
22
pub enum Error {
33
#[error("failed to load configuration from the environment: {0}")]
44
Environment(#[from] envy::Error),
5-
#[error("transport error: {0}")]
6-
TonicTransport(#[from] tonic::transport::Error),
7-
#[error("status: {0}")]
8-
TonicStatus(#[from] tonic::Status),
5+
#[error("worker registration request: {0}")]
6+
CouldNotRegisterWorker(tonic::Status),
7+
#[error("workflow registration request:: {0}")]
8+
CouldNotPutWorkflow(tonic::Status),
9+
#[error("dispatcher listen error: {0}")]
10+
CouldNotListenToDispatcher(tonic::Status),
11+
#[error("step status send error: {0}")]
12+
CouldNotSendStepStatus(tonic::Status),
13+
#[error("heartbeat error: {0}")]
14+
CouldNotSendHeartbeat(tonic::Status),
15+
#[error("dispatcher connection error: {0}")]
16+
CouldNotConnectToDispatcher(tonic::transport::Error),
17+
#[error("workflow service connection error: {0:?}")]
18+
CouldNotConnectToWorkflowService(tonic::transport::Error),
919
#[error("could not read file under `{1}`: {0}")]
1020
CouldNotReadFile(std::io::Error, String),
1121
#[error("environment variables {0} and {1} cannot be set simultaneously")]
1222
CantSetBothEnvironmentVariables(&'static str, &'static str),
1323
#[error("could not subscribe to actions after {0} retries")]
1424
CouldNotSubscribeToActions(usize),
15-
#[error("could not decode the provided token to retrieve the host/port pair")]
25+
#[error("could not decode the provided token to retrieve the host/port pair: {0}")]
1626
CouldNotDecodeToken(jsonwebtoken::errors::Error),
1727
#[error("could not decode action payload: {0}")]
1828
CouldNotDecodeActionPayload(serde_json::Error),

src/worker/heartbeat.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ where
2323
heartbeat_at: Some(std::time::SystemTime::now().into()),
2424
worker_id: worker_id.clone(),
2525
})
26-
.await?;
26+
.await
27+
.map_err(crate::Error::CouldNotSendHeartbeat)?;
2728

2829
tokio::select! {
2930
_ = interval.tick() => {

src/worker/listener.rs

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
use futures_util::FutureExt;
12
use tonic::IntoRequest;
2-
use tracing::{error, warn};
3+
use tracing::{debug, error, info, warn};
34

45
use crate::{
56
worker::{grpc::ActionType, DEFAULT_ACTION_TIMEOUT},
@@ -37,6 +38,11 @@ fn step_action_event(
3738
}
3839
}
3940

41+
#[derive(serde::Deserialize)]
42+
struct ActionInput<T> {
43+
input: T,
44+
}
45+
4046
async fn handle_start_step_run<F>(
4147
dispatcher: &mut DispatcherClient<
4248
tonic::service::interceptor::InterceptedService<tonic::transport::Channel, F>,
@@ -59,22 +65,25 @@ where
5965
return Ok(());
6066
};
6167

68+
debug!("Received a new action: {action:?}.");
69+
6270
dispatcher
6371
.send_step_action_event(step_action_event(
6472
worker_id,
6573
&action,
6674
StepActionEventType::StepEventTypeStarted,
6775
Default::default(),
6876
))
69-
.await?
77+
.await
78+
.map_err(crate::Error::CouldNotSendStepStatus)?
7079
.into_inner();
7180

72-
let input = serde_json::from_str(&action.action_payload)
81+
let input: ActionInput<serde_json::Value> = serde_json::from_str(&action.action_payload)
7382
.map_err(crate::Error::CouldNotDecodeActionPayload)?;
7483

7584
// FIXME: Obviously, run this asynchronously rather than blocking the main listening loop.
7685
let action_event =
77-
match tokio::task::spawn_local(async move { action_callable(input).await }).await {
86+
match tokio::task::spawn(async move { action_callable(input.input).await }).await {
7887
Ok(Ok(output_value)) => step_action_event(
7988
worker_id,
8089
&action,
@@ -97,7 +106,8 @@ where
97106

98107
dispatcher
99108
.send_step_action_event(action_event)
100-
.await?
109+
.await
110+
.map_err(crate::Error::CouldNotSendStepStatus)?
101111
.into_inner();
102112

103113
Ok(())
@@ -110,7 +120,7 @@ pub(crate) async fn run<F>(
110120
namespace: &str,
111121
worker_id: &str,
112122
workflows: Vec<Workflow>,
113-
listener_v2_timeout: u64,
123+
listener_v2_timeout: Option<u64>,
114124
mut interrupt_receiver: tokio::sync::mpsc::Receiver<()>,
115125
_heartbeat_interrupt_sender: tokio::sync::mpsc::Sender<()>,
116126
) -> crate::Result<()>
@@ -125,6 +135,8 @@ where
125135
let connection_attempt = tokio::time::Instant::now();
126136

127137
'main_loop: loop {
138+
info!("Listening…");
139+
128140
if connection_attempt.elapsed() > DEFAULT_ACTION_LISTENER_RETRY_INTERVAL {
129141
retries = 0;
130142
}
@@ -134,22 +146,41 @@ where
134146
));
135147
}
136148

137-
let mut stream = match listen_strategy {
149+
let response = match listen_strategy {
138150
ListenStrategy::V1 => {
151+
info!("Using strategy v1");
152+
139153
let mut request = WorkerListenRequest {
140154
worker_id: worker_id.to_owned(),
141155
}
142156
.into_request();
143157
request.set_timeout(DEFAULT_ACTION_TIMEOUT);
144-
dispatcher.listen(request).await?.into_inner()
158+
Box::new(dispatcher.listen(request)).boxed()
145159
}
146160
ListenStrategy::V2 => {
161+
info!("Using strategy v2");
162+
147163
let mut request = WorkerListenRequest {
148164
worker_id: worker_id.to_owned(),
149165
}
150166
.into_request();
151-
request.set_timeout(std::time::Duration::from_millis(listener_v2_timeout));
152-
dispatcher.listen_v2(request).await?.into_inner()
167+
if let Some(listener_v2_timeout) = listener_v2_timeout {
168+
request.set_timeout(std::time::Duration::from_millis(listener_v2_timeout));
169+
}
170+
dispatcher.listen_v2(request).boxed()
171+
}
172+
};
173+
174+
let mut stream = tokio::select! {
175+
response = response => {
176+
response
177+
.map_err(crate::Error::CouldNotListenToDispatcher)?
178+
.into_inner()
179+
}
180+
result = interrupt_receiver.recv() => {
181+
assert!(result.is_some());
182+
warn!("Interrupt received.");
183+
break 'main_loop;
153184
}
154185
};
155186

@@ -200,7 +231,9 @@ where
200231
}
201232
}
202233
}
203-
_ = interrupt_receiver.recv() => {
234+
result = interrupt_receiver.recv() => {
235+
assert!(result.is_some());
236+
warn!("Interrupt received.");
204237
break 'main_loop;
205238
}
206239
}

0 commit comments

Comments
 (0)