11use std:: time:: Instant ;
2+ use std:: time:: { SystemTime , UNIX_EPOCH } ;
23
34use futures_lite:: StreamExt ;
45use prost:: Message ;
56use taurus_core:: runtime:: engine:: { EmitType , ExecutionEngine , ExecutionId , RespondEmitter } ;
7+ use taurus_core:: types:: errors:: runtime_error:: RuntimeError ;
8+ use taurus_core:: types:: signal:: Signal ;
69use taurus_provider:: providers:: emitter:: nats_emitter:: NATSRespondEmitter ;
710use taurus_provider:: providers:: remote:: nats_remote_runtime:: NATSRemoteRuntime ;
811use tokio:: task:: JoinHandle ;
9- use tucana:: shared:: { ExecutionFlow , RuntimeUsage , Value } ;
12+ use tucana:: shared:: execution_result;
13+ use tucana:: shared:: { ExecutionFlow , ExecutionResult , RuntimeUsage , Value } ;
1014
1115use crate :: client:: runtime_usage:: TaurusRuntimeUsageService ;
1216
@@ -18,7 +22,7 @@ pub fn spawn_worker(
1822 runtime_usage_service : Option < TaurusRuntimeUsageService > ,
1923) -> JoinHandle < ( ) > {
2024 tokio:: spawn ( async move {
21- let mut subscription = match client
25+ let mut execution_subscription = match client
2226 . queue_subscribe ( String :: from ( "execution.*" ) , "taurus" . into ( ) )
2327 . await
2428 {
@@ -32,30 +36,75 @@ pub fn spawn_worker(
3236 }
3337 } ;
3438
35- while let Some ( message) = subscription. next ( ) . await {
36- process_message (
37- message,
38- & engine,
39- & nats_remote,
40- & runtime_emitter,
41- runtime_usage_service. as_ref ( ) ,
42- )
43- . await ;
39+ let mut test_execution_subscription = match client
40+ . queue_subscribe ( String :: from ( "test_executions.*" ) , "taurus" . into ( ) )
41+ . await
42+ {
43+ Ok ( subscription) => {
44+ log:: info!( "Subscribed to 'test_executions.*'" ) ;
45+ subscription
46+ }
47+ Err ( err) => {
48+ log:: error!( "Failed to subscribe to 'test_executions.*': {:?}" , err) ;
49+ return ;
50+ }
51+ } ;
52+
53+ let mut execution_closed = false ;
54+ let mut test_execution_closed = false ;
55+
56+ while !( execution_closed && test_execution_closed) {
57+ tokio:: select! {
58+ message = execution_subscription. next( ) , if !execution_closed => {
59+ match message {
60+ Some ( message) => {
61+ process_execution_message(
62+ message,
63+ & engine,
64+ & nats_remote,
65+ & runtime_emitter,
66+ runtime_usage_service. as_ref( ) ,
67+ ) . await ;
68+ }
69+ None => {
70+ execution_closed = true ;
71+ log:: warn!( "Subscription 'execution.*' ended" ) ;
72+ }
73+ }
74+ }
75+ message = test_execution_subscription. next( ) , if !test_execution_closed => {
76+ match message {
77+ Some ( message) => {
78+ process_test_execution_message(
79+ & client,
80+ message,
81+ & engine,
82+ & nats_remote,
83+ runtime_usage_service. as_ref( ) ,
84+ ) . await ;
85+ }
86+ None => {
87+ test_execution_closed = true ;
88+ log:: warn!( "Subscription 'test_executions.*' ended" ) ;
89+ }
90+ }
91+ }
92+ }
4493 }
4594
4695 log:: info!( "NATS worker loop ended" ) ;
4796 } )
4897}
4998
50- async fn process_message (
99+ async fn process_execution_message (
51100 message : async_nats:: Message ,
52101 engine : & ExecutionEngine ,
53102 nats_remote : & NATSRemoteRuntime ,
54103 runtime_emitter : & NATSRespondEmitter ,
55104 runtime_usage_service : Option < & TaurusRuntimeUsageService > ,
56105) {
57- let requested_execution_id =
58- parse_execution_id_from_subject ( & message . subject ) . unwrap_or_else ( || {
106+ let requested_execution_id = parse_execution_id_from_subject ( & message . subject , "execution" )
107+ . unwrap_or_else ( || {
59108 let generated = ExecutionId :: new_v4 ( ) ;
60109 log:: warn!(
61110 "Expected subject format 'execution.<uuid>', got '{}'; generated execution id {}" ,
@@ -83,7 +132,7 @@ async fn process_message(
83132 let respond_emitter = |execution_id, emit_type : EmitType , value : Value | {
84133 runtime_emitter. emit ( execution_id, emit_type, value) ;
85134 } ;
86- let runtime_usage = execute_flow (
135+ let run_result = execute_flow (
87136 requested_execution_id,
88137 flow,
89138 engine,
@@ -96,39 +145,200 @@ async fn process_message(
96145 ) ;
97146
98147 if let Some ( usage_service) = runtime_usage_service {
99- usage_service. update_runtime_usage ( runtime_usage) . await ;
148+ usage_service
149+ . update_runtime_usage ( run_result. runtime_usage )
150+ . await ;
100151 }
101152}
102153
154+ async fn process_test_execution_message (
155+ client : & async_nats:: Client ,
156+ message : async_nats:: Message ,
157+ engine : & ExecutionEngine ,
158+ nats_remote : & NATSRemoteRuntime ,
159+ runtime_usage_service : Option < & TaurusRuntimeUsageService > ,
160+ ) {
161+ let requested_execution_id =
162+ match parse_execution_id_from_subject ( & message. subject , "test_executions" ) {
163+ Some ( res) => res,
164+ None => {
165+ log:: error!( "Failed to extract execution uuid from {}" , & message. subject) ;
166+ return ;
167+ }
168+ } ;
169+
170+ let flow: ExecutionFlow = match ExecutionFlow :: decode ( & * message. payload ) {
171+ Ok ( flow) => flow,
172+ Err ( err) => {
173+ log:: error!(
174+ "Failed to deserialize test execution flow: {:?}, payload: {:?}" ,
175+ err,
176+ & message. payload
177+ ) ;
178+ let result = build_decode_error_result ( requested_execution_id) ;
179+ respond_to_test_execution_request ( client, & message, result) . await ;
180+ return ;
181+ }
182+ } ;
183+
184+ let run_result = execute_flow ( requested_execution_id, flow, engine, nats_remote, None ) ;
185+
186+ if let Some ( usage_service) = runtime_usage_service {
187+ usage_service
188+ . update_runtime_usage ( run_result. runtime_usage . clone ( ) )
189+ . await ;
190+ }
191+
192+ let execution_result = build_execution_result (
193+ run_result. execution_id ,
194+ run_result. flow_id ,
195+ run_result. started_at ,
196+ run_result. finished_at ,
197+ run_result. input ,
198+ run_result. signal ,
199+ ) ;
200+ respond_to_test_execution_request ( client, & message, execution_result) . await ;
201+ }
202+
203+ #[ derive( Clone ) ]
204+ struct FlowRunResult {
205+ execution_id : ExecutionId ,
206+ flow_id : i64 ,
207+ started_at : i64 ,
208+ finished_at : i64 ,
209+ input : Option < Value > ,
210+ signal : Signal ,
211+ runtime_usage : RuntimeUsage ,
212+ }
213+
103214fn execute_flow (
104215 execution_id : ExecutionId ,
105216 flow : ExecutionFlow ,
106217 engine : & ExecutionEngine ,
107218 nats_remote : & NATSRemoteRuntime ,
108219 respond_emitter : Option < & dyn RespondEmitter > ,
109- ) -> RuntimeUsage {
220+ ) -> FlowRunResult {
221+ let started_at = now_unix_ms ( ) ;
110222 let start = Instant :: now ( ) ;
111223 let flow_id = flow. flow_id ;
112- let ( _signal, _reason) = engine. execute_flow_with_execution_id (
224+ let input = flow. input_value . clone ( ) ;
225+ let ( signal, _reason) = engine. execute_flow_with_execution_id (
113226 execution_id,
114227 flow,
115228 Some ( nats_remote) ,
116229 respond_emitter,
117230 true ,
118231 ) ;
232+ let finished_at = now_unix_ms ( ) ;
119233 let duration_millis = start. elapsed ( ) . as_millis ( ) as i64 ;
120234
121- RuntimeUsage {
235+ FlowRunResult {
236+ execution_id,
122237 flow_id,
123- duration : duration_millis,
238+ started_at,
239+ finished_at,
240+ input,
241+ signal,
242+ runtime_usage : RuntimeUsage {
243+ flow_id,
244+ duration : duration_millis,
245+ } ,
124246 }
125247}
126248
127- fn parse_execution_id_from_subject ( subject : & async_nats:: Subject ) -> Option < ExecutionId > {
249+ fn parse_execution_id_from_subject (
250+ subject : & async_nats:: Subject ,
251+ prefix : & str ,
252+ ) -> Option < ExecutionId > {
128253 let raw = subject. as_str ( ) ;
129254 let mut parts = raw. split ( '.' ) ;
130255 match ( parts. next ( ) , parts. next ( ) , parts. next ( ) ) {
131- ( Some ( "execution" ) , Some ( uuid) , None ) => ExecutionId :: parse_str ( uuid) . ok ( ) ,
256+ ( Some ( found_prefix) , Some ( uuid) , None ) if found_prefix == prefix => {
257+ ExecutionId :: parse_str ( uuid) . ok ( )
258+ }
132259 _ => None ,
133260 }
134261}
262+
263+ fn build_execution_result (
264+ execution_id : ExecutionId ,
265+ flow_id : i64 ,
266+ started_at : i64 ,
267+ finished_at : i64 ,
268+ input : Option < Value > ,
269+ signal : Signal ,
270+ ) -> ExecutionResult {
271+ let result = match signal {
272+ Signal :: Success ( value) | Signal :: Return ( value) | Signal :: Respond ( value) => {
273+ Some ( execution_result:: Result :: Success ( value) )
274+ }
275+ Signal :: Failure ( err) => Some ( execution_result:: Result :: Error ( err. as_tucana_error ( ) ) ) ,
276+ Signal :: Stop => Some ( execution_result:: Result :: Success ( Value {
277+ kind : Some ( tucana:: shared:: value:: Kind :: NullValue ( 0 ) ) ,
278+ } ) ) ,
279+ } ;
280+
281+ ExecutionResult {
282+ execution_identifier : execution_id. to_string ( ) ,
283+ flow_id,
284+ started_at,
285+ finished_at,
286+ input,
287+ node_execution_results : Vec :: new ( ) ,
288+ result,
289+ }
290+ }
291+
292+ fn build_decode_error_result ( execution_id : ExecutionId ) -> ExecutionResult {
293+ let now = now_unix_ms ( ) ;
294+ let runtime_error = RuntimeError :: new (
295+ "T-TAURUS-000001" ,
296+ "ExecutionFlowDecodeError" ,
297+ "Failed to decode test execution flow payload" ,
298+ ) ;
299+
300+ ExecutionResult {
301+ execution_identifier : execution_id. to_string ( ) ,
302+ flow_id : 0 ,
303+ started_at : now,
304+ finished_at : now,
305+ input : None ,
306+ node_execution_results : Vec :: new ( ) ,
307+ result : Some ( execution_result:: Result :: Error (
308+ runtime_error. as_tucana_error ( ) ,
309+ ) ) ,
310+ }
311+ }
312+
313+ async fn respond_to_test_execution_request (
314+ client : & async_nats:: Client ,
315+ message : & async_nats:: Message ,
316+ result : ExecutionResult ,
317+ ) {
318+ let Some ( reply_subject) = message. reply . as_ref ( ) else {
319+ log:: warn!(
320+ "Received test execution request without reply subject on '{}'; cannot return ExecutionResult" ,
321+ message. subject
322+ ) ;
323+ return ;
324+ } ;
325+
326+ if let Err ( err) = client
327+ . publish ( reply_subject. clone ( ) , result. encode_to_vec ( ) . into ( ) )
328+ . await
329+ {
330+ log:: error!(
331+ "Failed to publish test execution response on '{}': {:?}" ,
332+ reply_subject,
333+ err
334+ ) ;
335+ }
336+ }
337+
338+ fn now_unix_ms ( ) -> i64 {
339+ SystemTime :: now ( )
340+ . duration_since ( UNIX_EPOCH )
341+ . map ( |it| it. as_millis ( ) as i64 )
342+ . unwrap_or ( 0 )
343+ }
344+
0 commit comments