1use super::config::get_config;
126use crate::jobs::JobProducerTrait;
127use crate::models::{
128 NetworkRepoModel, NotificationRepoModel, RelayerRepoModel, SignerRepoModel, ThinDataAppState,
129 TransactionRepoModel,
130};
131use crate::repositories::{
132 ApiKeyRepositoryTrait, NetworkRepository, PluginRepositoryTrait, RelayerRepository, Repository,
133 TransactionCounterTrait, TransactionRepository,
134};
135use crate::services::plugins::relayer_api::{RelayerApi, Request};
136use scc::HashMap as SccHashMap;
137use serde::{Deserialize, Serialize};
138use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
139use std::sync::Arc;
140use std::time::{Duration, Instant};
141use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
142use tokio::net::{UnixListener, UnixStream};
143use tokio::sync::{mpsc, watch, Semaphore};
144use tracing::{debug, info, warn};
145
146use super::PluginError;
147
148fn log_socket_write_error(context: &str, error: &std::io::Error) {
152 match error.kind() {
153 std::io::ErrorKind::BrokenPipe | std::io::ErrorKind::ConnectionReset => {
154 debug!(
155 "Failed to write {}: {} (plugin likely timed out)",
156 context, error
157 );
158 }
159 _ => {
160 warn!("Failed to write {}: {}", context, error);
161 }
162 }
163}
164
165#[derive(Debug, Serialize, Deserialize, Clone)]
167#[serde(tag = "type", rename_all = "snake_case")]
168pub enum PluginMessage {
169 Register { execution_id: String },
171 ApiRequest {
173 request_id: String,
174 relayer_id: String,
175 method: crate::services::plugins::relayer_api::PluginMethod,
176 payload: serde_json::Value,
177 },
178 ApiResponse {
180 request_id: String,
181 result: Option<serde_json::Value>,
182 error: Option<String>,
183 },
184 Trace { trace: serde_json::Value },
186 Shutdown,
188}
189
190struct ExecutionContext {
192 traces_tx: Option<mpsc::Sender<Vec<serde_json::Value>>>,
195 created_at: Instant,
197 #[allow(dead_code)] bound_execution_id: String,
201}
202
203pub struct ExecutionGuard {
205 execution_id: String,
206 executions: Arc<SccHashMap<String, ExecutionContext>>,
207 rx: Option<mpsc::Receiver<Vec<serde_json::Value>>>,
208 active_count: Arc<AtomicUsize>,
210 registered: bool,
213}
214
215impl ExecutionGuard {
216 pub fn into_receiver(mut self) -> Option<mpsc::Receiver<Vec<serde_json::Value>>> {
219 self.rx.take()
220 }
221}
222
223impl Drop for ExecutionGuard {
224 fn drop(&mut self) {
225 if self.registered && self.executions.remove(&self.execution_id).is_some() {
236 self.active_count.fetch_sub(1, Ordering::AcqRel);
237 }
238 }
239}
240
241pub struct SharedSocketService {
243 socket_path: String,
245 executions: Arc<SccHashMap<String, ExecutionContext>>,
248 active_count: Arc<AtomicUsize>,
250 started: AtomicBool,
252 shutdown_tx: watch::Sender<bool>,
254 connection_semaphore: Arc<Semaphore>,
256}
257
258static PIPELINE_HANDLE: std::sync::OnceLock<tokio::runtime::Handle> = std::sync::OnceLock::new();
265
266pub fn set_pipeline_handle(handle: tokio::runtime::Handle) {
269 let _ = PIPELINE_HANDLE.set(handle);
270}
271
272fn spawn_on_pipeline<F>(fut: F) -> tokio::task::JoinHandle<F::Output>
275where
276 F: std::future::Future + Send + 'static,
277 F::Output: Send + 'static,
278{
279 match PIPELINE_HANDLE.get() {
280 Some(handle) => handle.spawn(fut),
281 None => tokio::spawn(fut),
282 }
283}
284
285impl SharedSocketService {
286 pub fn new(socket_path: &str) -> Result<Self, PluginError> {
288 let _ = std::fs::remove_file(socket_path);
290
291 let (shutdown_tx, _) = watch::channel(false);
292
293 let config = get_config();
295 let max_connections = config.socket_max_connections;
296
297 let executions: Arc<SccHashMap<String, ExecutionContext>> = Arc::new(SccHashMap::new());
298 let active_count = Arc::new(AtomicUsize::new(0));
299
300 let executions_clone = executions.clone();
302 let active_count_clone = active_count.clone();
303 let mut cleanup_shutdown_rx = shutdown_tx.subscribe();
304 spawn_on_pipeline(async move {
305 let mut interval = tokio::time::interval(Duration::from_secs(60));
306 loop {
307 tokio::select! {
308 _ = interval.tick() => {}
309 _ = cleanup_shutdown_rx.changed() => {
310 if *cleanup_shutdown_rx.borrow() {
311 break;
312 }
313 }
314 }
315 let now = Instant::now();
316 let mut removed = 0usize;
318 executions_clone.retain(|_, ctx| {
319 let keep = now.duration_since(ctx.created_at) < Duration::from_secs(300);
320 if !keep {
321 removed += 1;
322 }
323 keep
324 });
325 if removed > 0 {
326 active_count_clone.fetch_sub(removed, Ordering::AcqRel);
327 }
328 }
329 });
330
331 Ok(Self {
332 socket_path: socket_path.to_string(),
333 executions,
334 active_count,
335 started: AtomicBool::new(false),
336 shutdown_tx,
337 connection_semaphore: Arc::new(Semaphore::new(max_connections)),
338 })
339 }
340
341 pub fn socket_path(&self) -> &str {
342 &self.socket_path
343 }
344
345 pub async fn register_execution(
352 &self,
353 execution_id: String,
354 emit_traces: bool,
355 ) -> ExecutionGuard {
356 let (tx, rx) = if emit_traces {
358 let (tx, rx) = mpsc::channel(1);
359 (Some(tx), Some(rx))
360 } else {
361 (None, None)
362 };
363
364 let ctx = ExecutionContext {
365 traces_tx: tx,
366 created_at: Instant::now(),
367 bound_execution_id: execution_id.clone(),
368 };
369
370 let registered = match self.executions.insert(execution_id.clone(), ctx) {
372 Ok(_) => {
373 self.active_count.fetch_add(1, Ordering::AcqRel);
374 true
375 }
376 Err((existing_key, _)) => {
377 tracing::warn!(
378 execution_id = %existing_key,
379 "Duplicate execution_id detected during registration, guard will not decrement counter"
380 );
381 false
382 }
383 };
384
385 ExecutionGuard {
386 execution_id,
387 executions: self.executions.clone(),
388 rx,
389 registered,
390 active_count: self.active_count.clone(),
391 }
392 }
393
394 pub fn available_connection_slots(&self) -> usize {
396 self.connection_semaphore.available_permits()
397 }
398
399 pub fn active_connection_count(&self) -> usize {
401 get_config().socket_max_connections - self.connection_semaphore.available_permits()
402 }
403
404 pub async fn registered_executions_count(&self) -> usize {
406 self.active_count.load(Ordering::Relaxed)
407 }
408
409 pub async fn shutdown(&self) {
411 let _ = self.shutdown_tx.send(true);
412 info!("Shared socket service: shutdown signal sent");
413
414 let max_wait = Duration::from_secs(30);
416 let start = Instant::now();
417
418 while start.elapsed() < max_wait {
419 let available = self.connection_semaphore.available_permits();
420 if available == get_config().socket_max_connections {
421 break;
423 }
424 tokio::time::sleep(Duration::from_millis(100)).await;
425 }
426
427 let _ = std::fs::remove_file(&self.socket_path);
429 info!("Shared socket service: shutdown complete");
430 }
431
432 #[allow(clippy::type_complexity)]
436 pub async fn start<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
437 self: Arc<Self>,
438 state: Arc<ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
439 ) -> Result<(), PluginError>
440 where
441 J: JobProducerTrait + Send + Sync + 'static,
442 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
443 TR: TransactionRepository
444 + Repository<TransactionRepoModel, String>
445 + Send
446 + Sync
447 + 'static,
448 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
449 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
450 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
451 TCR: TransactionCounterTrait + Send + Sync + 'static,
452 PR: PluginRepositoryTrait + Send + Sync + 'static,
453 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
454 {
455 if self.started.swap(true, Ordering::Acquire) {
457 return Ok(());
458 }
459
460 let listener = UnixListener::bind(&self.socket_path)
462 .map_err(|e| PluginError::SocketError(format!("Failed to bind listener: {e}")))?;
463 let executions = self.executions.clone();
464 let relayer_api = Arc::new(RelayerApi);
465 let socket_path = self.socket_path.clone();
466 let mut shutdown_rx = self.shutdown_tx.subscribe();
467 let connection_semaphore = self.connection_semaphore.clone();
468
469 debug!(
470 "Shared socket service: starting listener on {}",
471 socket_path
472 );
473
474 spawn_on_pipeline(async move {
476 debug!("Shared socket service: listener task started");
477 const MAX_CONSECUTIVE_ACCEPT_ERRORS: u32 = 10;
480 let mut consecutive_accept_errors: u32 = 0;
481 loop {
482 tokio::select! {
483 _ = shutdown_rx.changed() => {
485 if *shutdown_rx.borrow() {
486 info!("Shared socket service: shutting down listener");
487 break;
488 }
489 }
490 accept_result = listener.accept() => {
492 match accept_result {
493 Ok((stream, _)) => {
494 consecutive_accept_errors = 0;
495 match connection_semaphore.clone().try_acquire_owned() {
497 Ok(permit) => {
498 debug!("Shared socket service: accepted new connection");
499
500 let relayer_api_clone = relayer_api.clone();
501 let state_clone = Arc::clone(&state);
502 let executions_clone = executions.clone();
503
504 spawn_on_pipeline(async move {
505 let _permit = permit;
507
508 let result = Self::handle_connection(
509 stream,
510 relayer_api_clone,
511 state_clone,
512 executions_clone,
513 )
514 .await;
515
516 if let Err(e) = result {
517 debug!("Connection handler finished with error: {}", e);
518 }
519 });
520 }
521 Err(_) => {
522 warn!(
523 "Connection limit reached, rejecting new connection. \
524 Consider increasing PLUGIN_MAX_CONCURRENCY or PLUGIN_SOCKET_MAX_CONCURRENT_CONNECTIONS."
525 );
526 drop(stream);
527 }
528 }
529 }
530 Err(e) => {
531 if *shutdown_rx.borrow() {
534 info!("Shared socket service: shutting down listener (accept interrupted)");
535 break;
536 }
537 consecutive_accept_errors += 1;
538 warn!(
539 "Error accepting connection (attempt {}): {}",
540 consecutive_accept_errors, e
541 );
542 if consecutive_accept_errors >= MAX_CONSECUTIVE_ACCEPT_ERRORS {
548 warn!(
549 "Shared socket service: {} consecutive accept errors; stopping listener",
550 consecutive_accept_errors
551 );
552 break;
553 }
554 tokio::time::sleep(Duration::from_millis(20)).await;
555 }
556 }
557 }
558 }
559 }
560
561 let _ = std::fs::remove_file(&socket_path);
563 info!("Shared socket service: listener stopped");
564 });
565
566 Ok(())
567 }
568
569 #[allow(clippy::type_complexity)]
579 async fn handle_connection<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
580 stream: UnixStream,
581 relayer_api: Arc<RelayerApi>,
582 state: Arc<ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
583 executions: Arc<SccHashMap<String, ExecutionContext>>,
584 ) -> Result<(), PluginError>
585 where
586 J: JobProducerTrait + Send + Sync + 'static,
587 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
588 TR: TransactionRepository
589 + Repository<TransactionRepoModel, String>
590 + Send
591 + Sync
592 + 'static,
593 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
594 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
595 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
596 TCR: TransactionCounterTrait + Send + Sync + 'static,
597 PR: PluginRepositoryTrait + Send + Sync + 'static,
598 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
599 {
600 let (r, mut w) = stream.into_split();
601 let mut reader = BufReader::new(r).lines();
602
603 let mut traces: Option<Vec<serde_json::Value>> = None;
605 let mut traces_enabled = false;
607
608 let mut bound_execution_id: Option<String> = None;
611
612 let safety_timeout =
617 Duration::from_secs(crate::constants::DEFAULT_PLUGIN_TIMEOUT_SECONDS + 60);
618
619 loop {
620 let line = match tokio::time::timeout(safety_timeout, reader.next_line()).await {
624 Ok(Ok(Some(line))) => line,
625 Ok(Ok(None)) => break, Ok(Err(e)) => {
627 warn!("Error reading from connection: {}", e);
628 break;
629 }
630 Err(_) => {
631 debug!(
632 "Connection safety timeout reached ({}s with no message)",
633 safety_timeout.as_secs()
634 );
635 break;
636 }
637 };
638
639 debug!("Shared socket service: received message");
640
641 let json_value: serde_json::Value = match serde_json::from_str(&line) {
643 Ok(v) => v,
644 Err(e) => {
645 warn!("Failed to parse JSON: {}", e);
646 continue;
647 }
648 };
649
650 let has_type_field = json_value.get("type").is_some();
651
652 if has_type_field {
653 let message: PluginMessage = match serde_json::from_value(json_value) {
655 Ok(msg) => msg,
656 Err(e) => {
657 warn!("Failed to parse PluginMessage: {}", e);
658 continue;
659 }
660 };
661
662 match message {
664 PluginMessage::Register { execution_id } => {
665 if bound_execution_id.is_some() {
667 warn!("Attempted to re-register connection (security violation)");
668 break;
669 }
670
671 if let Some(has_traces) =
674 executions.read(&execution_id, |_, ctx| ctx.traces_tx.is_some())
675 {
676 traces_enabled = has_traces;
677 } else {
678 warn!("Unknown execution_id: {}", execution_id);
679 break;
680 }
681
682 debug!(
683 execution_id = %execution_id,
684 traces_enabled = traces_enabled,
685 "Connection registered"
686 );
687 bound_execution_id = Some(execution_id);
688 }
689
690 PluginMessage::ApiRequest {
691 request_id,
692 relayer_id,
693 method,
694 payload,
695 } => {
696 let exec_id = match &bound_execution_id {
698 Some(id) => id,
699 None => {
700 warn!("ApiRequest before Register (security violation)");
701 break;
702 }
703 };
704
705 let request = Request {
707 request_id: request_id.clone(),
708 relayer_id,
709 method,
710 payload,
711 http_request_id: Some(exec_id.clone()),
712 };
713
714 let response = relayer_api.handle_request(request, &state).await;
716
717 let api_response = PluginMessage::ApiResponse {
719 request_id: response.request_id,
720 result: response.result,
721 error: response.error,
722 };
723
724 let response_str = serde_json::to_string(&api_response)
725 .map_err(|e| PluginError::PluginError(e.to_string()))?
726 + "\n";
727
728 if let Err(e) = w.write_all(response_str.as_bytes()).await {
729 log_socket_write_error("API response", &e);
730 break;
731 }
732
733 if let Err(e) = w.flush().await {
734 log_socket_write_error("API response flush", &e);
735 break;
736 }
737 }
738
739 PluginMessage::Trace { trace } => {
740 if traces_enabled {
742 if traces.is_none() {
743 traces = Some(Vec::new());
744 }
745 if let Some(ref mut t) = traces {
746 t.push(trace);
747 }
748 }
749 }
751
752 PluginMessage::Shutdown => {
753 debug!("Plugin requested shutdown");
754 break;
755 }
756
757 PluginMessage::ApiResponse { .. } => {
758 warn!("Received ApiResponse from plugin (invalid direction)");
759 continue;
760 }
761 }
762 } else {
763 if let Ok(request) = serde_json::from_value::<Request>(json_value.clone()) {
765 if bound_execution_id.is_none() {
769 let candidate_id = request
770 .http_request_id
771 .clone()
772 .or_else(|| Some(request.request_id.clone()));
773
774 if let Some(ref id) = candidate_id {
777 if let Some(has_traces) =
778 executions.read(id, |_, ctx| ctx.traces_tx.is_some())
779 {
780 traces_enabled = has_traces;
781 bound_execution_id = candidate_id;
782 } else {
783 debug!("Legacy request with unknown execution_id: {}", id);
784 }
785 }
786 }
787
788 let response = relayer_api.handle_request(request, &state).await;
790 let response_str = serde_json::to_string(&response)
791 .map_err(|e| PluginError::PluginError(e.to_string()))?
792 + "\n";
793
794 if let Err(e) = w.write_all(response_str.as_bytes()).await {
795 log_socket_write_error("response", &e);
796 break;
797 }
798
799 if let Err(e) = w.flush().await {
800 log_socket_write_error("response flush", &e);
801 break;
802 }
803 } else {
804 warn!("Failed to parse message as either PluginMessage or legacy Request");
805 }
806 }
807 }
808
809 if traces_enabled {
811 if let Some(exec_id) = bound_execution_id {
812 let traces_tx = executions
814 .read(&exec_id, |_, ctx| ctx.traces_tx.clone())
815 .flatten();
816
817 if let Some(tx) = traces_tx {
818 let collected_traces = traces.unwrap_or_default();
819 let trace_count = collected_traces.len();
820 match tokio::time::timeout(
823 Duration::from_millis(100),
824 tx.send(collected_traces),
825 )
826 .await
827 {
828 Ok(Ok(())) => {}
829 Ok(Err(_)) => {
830 if trace_count > 0 {
831 warn!(
832 "Trace channel closed for execution_id: {} ({} traces lost)",
833 exec_id, trace_count
834 );
835 }
836 }
837 Err(_) => warn!("Timeout sending traces for execution_id: {}", exec_id),
838 }
839 }
840 }
841 }
842 debug!("Shared socket service: connection closed");
845 Ok(())
846 }
847}
848
849impl Drop for SharedSocketService {
850 fn drop(&mut self) {
851 let _ = self.shutdown_tx.send(true);
853 }
856}
857
858static SHARED_SOCKET: std::sync::OnceLock<Result<Arc<SharedSocketService>, String>> =
860 std::sync::OnceLock::new();
861
862pub fn get_shared_socket_service() -> Result<Arc<SharedSocketService>, PluginError> {
865 let socket_path = "/tmp/relayer-plugin-shared.sock";
866
867 let result = SHARED_SOCKET.get_or_init(|| {
868 let _ = std::fs::remove_file(socket_path);
870
871 match SharedSocketService::new(socket_path) {
872 Ok(service) => Ok(Arc::new(service)),
873 Err(e) => Err(e.to_string()),
874 }
875 });
876
877 match result {
878 Ok(service) => Ok(service.clone()),
879 Err(e) => Err(PluginError::SocketError(format!(
880 "Failed to create shared socket service: {e}"
881 ))),
882 }
883}
884
885pub async fn shutdown_shared_socket_service() {
893 if let Some(Ok(service)) = SHARED_SOCKET.get() {
894 service.shutdown().await;
895 }
896}
897
898#[allow(clippy::type_complexity)]
900pub async fn ensure_shared_socket_started<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
901 state: Arc<ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
902) -> Result<(), PluginError>
903where
904 J: JobProducerTrait + Send + Sync + 'static,
905 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
906 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
907 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
908 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
909 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
910 TCR: TransactionCounterTrait + Send + Sync + 'static,
911 PR: PluginRepositoryTrait + Send + Sync + 'static,
912 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
913{
914 let service = get_shared_socket_service()?;
915 service.start(state).await
916}
917
918#[cfg(test)]
919mod tests {
920 use super::*;
921 use crate::utils::mocks::mockutils::create_mock_app_state;
922 use actix_web::web;
923 use tempfile::tempdir;
924 use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
925 use tokio::net::UnixStream;
926
927 #[tokio::test]
928 async fn test_unified_protocol_register_and_api_request() {
929 let temp_dir = tempdir().unwrap();
930 let socket_path = temp_dir.path().join("shared.sock");
931
932 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
933 let state = create_mock_app_state(None, None, None, None, None, None).await;
934
935 service
937 .clone()
938 .start(Arc::new(web::ThinData(state)))
939 .await
940 .unwrap();
941
942 let execution_id = "test-exec-123".to_string();
944 let _guard = service.register_execution(execution_id.clone(), true).await;
945
946 tokio::time::sleep(Duration::from_millis(50)).await;
948
949 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
951 .await
952 .unwrap();
953
954 let register_msg = PluginMessage::Register {
956 execution_id: execution_id.clone(),
957 };
958 let msg_json = serde_json::to_string(®ister_msg).unwrap() + "\n";
959 client.write_all(msg_json.as_bytes()).await.unwrap();
960
961 let api_request = PluginMessage::ApiRequest {
963 request_id: "req-1".to_string(),
964 relayer_id: "relayer-1".to_string(),
965 method: crate::services::plugins::relayer_api::PluginMethod::GetRelayerStatus,
966 payload: serde_json::json!({}),
967 };
968 let req_json = serde_json::to_string(&api_request).unwrap() + "\n";
969 client.write_all(req_json.as_bytes()).await.unwrap();
970 client.flush().await.unwrap();
971
972 let (r, _w) = client.into_split();
974 let mut reader = BufReader::new(r);
975 let mut response_line = String::new();
976 reader.read_line(&mut response_line).await.unwrap();
977
978 let response: PluginMessage = serde_json::from_str(&response_line).unwrap();
979 match response {
980 PluginMessage::ApiResponse { request_id, .. } => {
981 assert_eq!(request_id, "req-1");
982 }
983 _ => panic!("Expected ApiResponse, got {response:?}"),
984 }
985
986 drop(reader);
987 drop(_w);
988 service.shutdown().await;
989 }
990
991 #[tokio::test]
992 async fn test_connection_tagging_prevents_spoofing() {
993 let temp_dir = tempdir().unwrap();
994 let socket_path = temp_dir.path().join("shared2.sock");
995
996 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
997 let state = create_mock_app_state(None, None, None, None, None, None).await;
998
999 service
1000 .clone()
1001 .start(Arc::new(web::ThinData(state)))
1002 .await
1003 .unwrap();
1004
1005 let execution_id = "test-exec-456".to_string();
1006 let _guard = service.register_execution(execution_id.clone(), true).await;
1007
1008 tokio::time::sleep(Duration::from_millis(50)).await;
1009
1010 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1011 .await
1012 .unwrap();
1013
1014 let register_msg = PluginMessage::Register {
1016 execution_id: execution_id.clone(),
1017 };
1018 let msg_json = serde_json::to_string(®ister_msg).unwrap() + "\n";
1019 client.write_all(msg_json.as_bytes()).await.unwrap();
1020
1021 let spoofed_register = PluginMessage::Register {
1023 execution_id: "different-exec-id".to_string(),
1024 };
1025 let spoofed_json = serde_json::to_string(&spoofed_register).unwrap() + "\n";
1026 client.write_all(spoofed_json.as_bytes()).await.unwrap();
1027 client.flush().await.unwrap();
1028
1029 tokio::time::sleep(Duration::from_millis(100)).await;
1031
1032 let (r, _w) = client.into_split();
1034 let mut reader = BufReader::new(r);
1035 let mut line = String::new();
1036 let result = reader.read_line(&mut line).await;
1037
1038 assert!(result.is_err() || result.unwrap() == 0);
1040
1041 drop(reader);
1042 drop(_w);
1043 service.shutdown().await;
1044 }
1045
1046 #[tokio::test]
1047 async fn test_backward_compatibility_with_legacy_format() {
1048 let temp_dir = tempdir().unwrap();
1049 let socket_path = temp_dir.path().join("shared3.sock");
1050
1051 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1052 let state = create_mock_app_state(None, None, None, None, None, None).await;
1053
1054 service
1055 .clone()
1056 .start(Arc::new(web::ThinData(state)))
1057 .await
1058 .unwrap();
1059
1060 let execution_id = "test-exec-789".to_string();
1061 let _guard = service.register_execution(execution_id.clone(), true).await;
1062
1063 tokio::time::sleep(Duration::from_millis(50)).await;
1064
1065 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1066 .await
1067 .unwrap();
1068
1069 let legacy_request = crate::services::plugins::relayer_api::Request {
1071 request_id: "legacy-1".to_string(),
1072 relayer_id: "relayer-1".to_string(),
1073 method: crate::services::plugins::relayer_api::PluginMethod::GetRelayerStatus,
1074 payload: serde_json::json!({}),
1075 http_request_id: Some(execution_id.clone()),
1076 };
1077 let legacy_json = serde_json::to_string(&legacy_request).unwrap() + "\n";
1078 client.write_all(legacy_json.as_bytes()).await.unwrap();
1079 client.flush().await.unwrap();
1080
1081 let (r, _w) = client.into_split();
1083 let mut reader = BufReader::new(r);
1084 let mut response_line = String::new();
1085 reader.read_line(&mut response_line).await.unwrap();
1086
1087 let response: crate::services::plugins::relayer_api::Response =
1088 serde_json::from_str(&response_line).unwrap();
1089
1090 assert_eq!(response.request_id, "legacy-1");
1091 assert!(response.result.is_some() || response.error.is_some());
1094
1095 drop(reader);
1096 drop(_w);
1097 service.shutdown().await;
1098 }
1099
1100 #[tokio::test]
1101 async fn test_trace_collection() {
1102 let temp_dir = tempdir().unwrap();
1103 let socket_path = temp_dir.path().join("shared4.sock");
1104
1105 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1106 let state = create_mock_app_state(None, None, None, None, None, None).await;
1107
1108 service
1109 .clone()
1110 .start(Arc::new(web::ThinData(state)))
1111 .await
1112 .unwrap();
1113
1114 let execution_id = "test-exec-trace".to_string();
1115 let guard = service.register_execution(execution_id.clone(), true).await;
1116
1117 tokio::time::sleep(Duration::from_millis(50)).await;
1118
1119 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1120 .await
1121 .unwrap();
1122
1123 let register_msg = PluginMessage::Register {
1125 execution_id: execution_id.clone(),
1126 };
1127 client
1128 .write_all((serde_json::to_string(®ister_msg).unwrap() + "\n").as_bytes())
1129 .await
1130 .unwrap();
1131
1132 let trace1 = PluginMessage::Trace {
1134 trace: serde_json::json!({"event": "start", "timestamp": 1000}),
1135 };
1136 client
1137 .write_all((serde_json::to_string(&trace1).unwrap() + "\n").as_bytes())
1138 .await
1139 .unwrap();
1140
1141 let trace2 = PluginMessage::Trace {
1142 trace: serde_json::json!({"event": "processing", "timestamp": 2000}),
1143 };
1144 client
1145 .write_all((serde_json::to_string(&trace2).unwrap() + "\n").as_bytes())
1146 .await
1147 .unwrap();
1148
1149 let shutdown_msg = PluginMessage::Shutdown;
1151 client
1152 .write_all((serde_json::to_string(&shutdown_msg).unwrap() + "\n").as_bytes())
1153 .await
1154 .unwrap();
1155 client.flush().await.unwrap();
1156
1157 drop(client);
1158
1159 tokio::time::sleep(Duration::from_millis(100)).await;
1161
1162 let mut traces_rx = guard.into_receiver().expect("Traces should be enabled");
1164 let traces = traces_rx.recv().await.unwrap();
1165
1166 assert_eq!(traces.len(), 2);
1168 assert_eq!(traces[0]["event"], "start");
1169 assert_eq!(traces[1]["event"], "processing");
1170
1171 service.shutdown().await;
1172 }
1173
1174 #[tokio::test]
1175 async fn test_execution_guard_auto_unregister() {
1176 let temp_dir = tempdir().unwrap();
1177 let socket_path = temp_dir.path().join("shared_guard.sock");
1178
1179 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1180 let execution_id = "test-exec-guard".to_string();
1181
1182 {
1183 let _guard = service.register_execution(execution_id.clone(), true).await;
1184
1185 assert_eq!(service.registered_executions_count().await, 1);
1187 }
1188 assert_eq!(service.registered_executions_count().await, 0);
1192 }
1193
1194 #[tokio::test]
1195 async fn test_api_request_without_register_rejected() {
1196 let temp_dir = tempdir().unwrap();
1197 let socket_path = temp_dir.path().join("shared_no_register.sock");
1198
1199 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1200 let state = create_mock_app_state(None, None, None, None, None, None).await;
1201
1202 service
1203 .clone()
1204 .start(Arc::new(web::ThinData(state)))
1205 .await
1206 .unwrap();
1207
1208 tokio::time::sleep(Duration::from_millis(50)).await;
1209
1210 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1211 .await
1212 .unwrap();
1213
1214 let api_request = PluginMessage::ApiRequest {
1216 request_id: "req-1".to_string(),
1217 relayer_id: "relayer-1".to_string(),
1218 method: crate::services::plugins::relayer_api::PluginMethod::GetRelayerStatus,
1219 payload: serde_json::json!({}),
1220 };
1221 let req_json = serde_json::to_string(&api_request).unwrap() + "\n";
1222 client.write_all(req_json.as_bytes()).await.unwrap();
1223 client.flush().await.unwrap();
1224
1225 tokio::time::sleep(Duration::from_millis(100)).await;
1227
1228 let (r, _w) = client.into_split();
1229 let mut reader = BufReader::new(r);
1230 let mut line = String::new();
1231 let result = reader.read_line(&mut line).await;
1232
1233 assert!(result.is_err() || result.unwrap() == 0);
1235
1236 drop(reader);
1237 drop(_w);
1238 service.shutdown().await;
1239 }
1240
1241 #[tokio::test]
1242 async fn test_register_with_unknown_execution_id_rejected() {
1243 let temp_dir = tempdir().unwrap();
1244 let socket_path = temp_dir.path().join("shared_unknown_exec.sock");
1245
1246 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1247 let state = create_mock_app_state(None, None, None, None, None, None).await;
1248
1249 service
1250 .clone()
1251 .start(Arc::new(web::ThinData(state)))
1252 .await
1253 .unwrap();
1254
1255 tokio::time::sleep(Duration::from_millis(50)).await;
1256
1257 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1258 .await
1259 .unwrap();
1260
1261 let register_msg = PluginMessage::Register {
1263 execution_id: "unknown-exec-id".to_string(),
1264 };
1265 let msg_json = serde_json::to_string(®ister_msg).unwrap() + "\n";
1266 client.write_all(msg_json.as_bytes()).await.unwrap();
1267 client.flush().await.unwrap();
1268
1269 tokio::time::sleep(Duration::from_millis(100)).await;
1271
1272 let (r, _w) = client.into_split();
1273 let mut reader = BufReader::new(r);
1274 let mut line = String::new();
1275 let result = reader.read_line(&mut line).await;
1276
1277 assert!(result.is_err() || result.unwrap() == 0);
1278
1279 drop(reader);
1280 drop(_w);
1281 service.shutdown().await;
1282 }
1283
1284 #[tokio::test]
1285 async fn test_connection_limit_enforcement() {
1286 let temp_dir = tempdir().unwrap();
1287 let socket_path = temp_dir.path().join("shared_connection_limit.sock");
1288
1289 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1290 let state = create_mock_app_state(None, None, None, None, None, None).await;
1291
1292 service
1293 .clone()
1294 .start(Arc::new(web::ThinData(state)))
1295 .await
1296 .unwrap();
1297
1298 tokio::time::sleep(Duration::from_millis(50)).await;
1299
1300 let initial_permits = service.connection_semaphore.available_permits();
1302 let max_connections = get_config().socket_max_connections;
1303 assert_eq!(initial_permits, max_connections);
1304
1305 let _client = UnixStream::connect(socket_path.to_str().unwrap())
1307 .await
1308 .unwrap();
1309
1310 tokio::time::sleep(Duration::from_millis(50)).await;
1311
1312 let after_connect = service.connection_semaphore.available_permits();
1314 assert!(after_connect < initial_permits);
1315
1316 drop(_client);
1317 service.shutdown().await;
1318 }
1319
1320 #[tokio::test]
1321 async fn test_connection_stays_alive_within_safety_timeout() {
1322 let temp_dir = tempdir().unwrap();
1323 let socket_path = temp_dir.path().join("shared_safety.sock");
1324
1325 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1326 let state = create_mock_app_state(None, None, None, None, None, None).await;
1327
1328 service
1329 .clone()
1330 .start(Arc::new(web::ThinData(state)))
1331 .await
1332 .unwrap();
1333
1334 let execution_id = "test-exec-safety".to_string();
1335 let _guard = service.register_execution(execution_id.clone(), true).await;
1336
1337 tokio::time::sleep(Duration::from_millis(50)).await;
1338
1339 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1340 .await
1341 .unwrap();
1342
1343 let register_msg = PluginMessage::Register { execution_id };
1345 client
1346 .write_all((serde_json::to_string(®ister_msg).unwrap() + "\n").as_bytes())
1347 .await
1348 .unwrap();
1349 client.flush().await.unwrap();
1350
1351 tokio::time::sleep(Duration::from_millis(200)).await;
1354
1355 let shutdown_msg = PluginMessage::Shutdown;
1357 let write_result = client
1358 .write_all((serde_json::to_string(&shutdown_msg).unwrap() + "\n").as_bytes())
1359 .await;
1360
1361 assert!(
1362 write_result.is_ok(),
1363 "Connection should still be alive within safety timeout"
1364 );
1365
1366 drop(client);
1367 service.shutdown().await;
1368 }
1369
1370 #[tokio::test]
1371 async fn test_idle_connection_cleaned_up_by_safety_timeout() {
1372 let temp_dir = tempdir().unwrap();
1373 let socket_path = temp_dir.path().join("shared_safety_timeout.sock");
1374
1375 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1376 let state = create_mock_app_state(None, None, None, None, None, None).await;
1377
1378 service
1379 .clone()
1380 .start(Arc::new(web::ThinData(state)))
1381 .await
1382 .unwrap();
1383
1384 let execution_id = "test-exec-safety-timeout".to_string();
1385 let _guard = service.register_execution(execution_id.clone(), true).await;
1386
1387 tokio::time::sleep(Duration::from_millis(50)).await;
1388
1389 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1390 .await
1391 .unwrap();
1392
1393 let register_msg = PluginMessage::Register { execution_id };
1395 client
1396 .write_all((serde_json::to_string(®ister_msg).unwrap() + "\n").as_bytes())
1397 .await
1398 .unwrap();
1399 client.flush().await.unwrap();
1400
1401 tokio::time::sleep(Duration::from_millis(200)).await;
1406
1407 drop(client);
1409
1410 service.shutdown().await;
1411 }
1412
1413 #[tokio::test]
1414 async fn test_multiple_api_requests_same_connection() {
1415 let temp_dir = tempdir().unwrap();
1416 let socket_path = temp_dir.path().join("shared_multiple_requests.sock");
1417
1418 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1419 let state = create_mock_app_state(None, None, None, None, None, None).await;
1420
1421 service
1422 .clone()
1423 .start(Arc::new(web::ThinData(state)))
1424 .await
1425 .unwrap();
1426
1427 let execution_id = "test-exec-multi".to_string();
1428 let _guard = service.register_execution(execution_id.clone(), true).await;
1429
1430 tokio::time::sleep(Duration::from_millis(50)).await;
1431
1432 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1433 .await
1434 .unwrap();
1435
1436 let register_msg = PluginMessage::Register {
1438 execution_id: execution_id.clone(),
1439 };
1440 client
1441 .write_all((serde_json::to_string(®ister_msg).unwrap() + "\n").as_bytes())
1442 .await
1443 .unwrap();
1444
1445 let (r, mut w) = client.into_split();
1446 let mut reader = BufReader::new(r);
1447
1448 for i in 1..=3 {
1450 let api_request = PluginMessage::ApiRequest {
1451 request_id: format!("req-{i}"),
1452 relayer_id: "relayer-1".to_string(),
1453 method: crate::services::plugins::relayer_api::PluginMethod::GetRelayerStatus,
1454 payload: serde_json::json!({}),
1455 };
1456 w.write_all((serde_json::to_string(&api_request).unwrap() + "\n").as_bytes())
1457 .await
1458 .unwrap();
1459 w.flush().await.unwrap();
1460
1461 let mut response_line = String::new();
1463 reader.read_line(&mut response_line).await.unwrap();
1464
1465 let response: PluginMessage = serde_json::from_str(&response_line).unwrap();
1466 match response {
1467 PluginMessage::ApiResponse { request_id, .. } => {
1468 assert_eq!(request_id, format!("req-{i}"));
1469 }
1470 _ => panic!("Expected ApiResponse"),
1471 }
1472 }
1473
1474 drop(reader);
1475 drop(w);
1476 service.shutdown().await;
1477 }
1478
1479 #[tokio::test]
1480 async fn test_shutdown_signal() {
1481 let temp_dir = tempdir().unwrap();
1482 let socket_path = temp_dir.path().join("shared_shutdown_signal.sock");
1483
1484 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1485 let state = create_mock_app_state(None, None, None, None, None, None).await;
1486
1487 service
1488 .clone()
1489 .start(Arc::new(web::ThinData(state)))
1490 .await
1491 .unwrap();
1492
1493 tokio::time::sleep(Duration::from_millis(50)).await;
1494
1495 assert!(std::path::Path::new(socket_path.to_str().unwrap()).exists());
1497
1498 service.shutdown().await;
1500
1501 tokio::time::sleep(Duration::from_millis(100)).await;
1503
1504 assert!(!std::path::Path::new(socket_path.to_str().unwrap()).exists());
1506 }
1507
1508 #[tokio::test]
1509 async fn test_malformed_json_handling() {
1510 let temp_dir = tempdir().unwrap();
1511 let socket_path = temp_dir.path().join("shared_malformed.sock");
1512
1513 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1514 let state = create_mock_app_state(None, None, None, None, None, None).await;
1515
1516 service
1517 .clone()
1518 .start(Arc::new(web::ThinData(state)))
1519 .await
1520 .unwrap();
1521
1522 let execution_id = "test-exec-malformed".to_string();
1523 let _guard = service.register_execution(execution_id.clone(), true).await;
1524
1525 tokio::time::sleep(Duration::from_millis(50)).await;
1526
1527 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1528 .await
1529 .unwrap();
1530
1531 let register_msg = PluginMessage::Register {
1533 execution_id: execution_id.clone(),
1534 };
1535 client
1536 .write_all((serde_json::to_string(®ister_msg).unwrap() + "\n").as_bytes())
1537 .await
1538 .unwrap();
1539
1540 client
1542 .write_all(b"{ this is not valid json }\n")
1543 .await
1544 .unwrap();
1545 client.flush().await.unwrap();
1546
1547 tokio::time::sleep(Duration::from_millis(100)).await;
1549
1550 let shutdown_msg = PluginMessage::Shutdown;
1552 let write_result = client
1553 .write_all((serde_json::to_string(&shutdown_msg).unwrap() + "\n").as_bytes())
1554 .await;
1555
1556 assert!(
1557 write_result.is_ok(),
1558 "Connection should still be alive after malformed JSON"
1559 );
1560
1561 drop(client);
1562 service.shutdown().await;
1563 }
1564
1565 #[tokio::test]
1566 async fn test_invalid_message_direction() {
1567 let temp_dir = tempdir().unwrap();
1568 let socket_path = temp_dir.path().join("shared_invalid_direction.sock");
1569
1570 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1571 let state = create_mock_app_state(None, None, None, None, None, None).await;
1572
1573 service
1574 .clone()
1575 .start(Arc::new(web::ThinData(state)))
1576 .await
1577 .unwrap();
1578
1579 let execution_id = "test-exec-invalid-dir".to_string();
1580 let _guard = service.register_execution(execution_id.clone(), true).await;
1581
1582 tokio::time::sleep(Duration::from_millis(50)).await;
1583
1584 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1585 .await
1586 .unwrap();
1587
1588 let register_msg = PluginMessage::Register {
1590 execution_id: execution_id.clone(),
1591 };
1592 client
1593 .write_all((serde_json::to_string(®ister_msg).unwrap() + "\n").as_bytes())
1594 .await
1595 .unwrap();
1596
1597 let invalid_msg = PluginMessage::ApiResponse {
1599 request_id: "invalid".to_string(),
1600 result: Some(serde_json::json!({})),
1601 error: None,
1602 };
1603 client
1604 .write_all((serde_json::to_string(&invalid_msg).unwrap() + "\n").as_bytes())
1605 .await
1606 .unwrap();
1607 client.flush().await.unwrap();
1608
1609 tokio::time::sleep(Duration::from_millis(100)).await;
1611
1612 let shutdown_msg = PluginMessage::Shutdown;
1614 let write_result = client
1615 .write_all((serde_json::to_string(&shutdown_msg).unwrap() + "\n").as_bytes())
1616 .await;
1617
1618 assert!(write_result.is_ok());
1619
1620 drop(client);
1621 service.shutdown().await;
1622 }
1623
1624 #[tokio::test]
1625 async fn test_stale_execution_cleanup() {
1626 let temp_dir = tempdir().unwrap();
1627 let socket_path = temp_dir.path().join("shared_stale_cleanup.sock");
1628
1629 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1630
1631 let execution_id = "stale-exec".to_string();
1633 let (tx, _rx) = mpsc::channel(1);
1634 let _ = service.executions.insert(
1636 execution_id.clone(),
1637 ExecutionContext {
1638 traces_tx: Some(tx),
1639 created_at: Instant::now() - Duration::from_secs(400), bound_execution_id: execution_id.clone(),
1641 },
1642 );
1643
1644 assert!(service.executions.contains(&execution_id));
1646
1647 drop(service);
1653 }
1654
1655 #[tokio::test]
1656 async fn test_socket_path_getter() {
1657 let temp_dir = tempdir().unwrap();
1658 let socket_path = temp_dir.path().join("shared_path.sock");
1659
1660 let service = SharedSocketService::new(socket_path.to_str().unwrap()).unwrap();
1661
1662 assert_eq!(service.socket_path(), socket_path.to_str().unwrap());
1663 }
1664
1665 #[tokio::test]
1666 async fn test_trace_send_timeout() {
1667 let temp_dir = tempdir().unwrap();
1668 let socket_path = temp_dir.path().join("shared_trace_timeout.sock");
1669
1670 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1671 let state = create_mock_app_state(None, None, None, None, None, None).await;
1672
1673 service
1674 .clone()
1675 .start(Arc::new(web::ThinData(state)))
1676 .await
1677 .unwrap();
1678
1679 let execution_id = "test-exec-trace-timeout".to_string();
1680 let guard = service.register_execution(execution_id.clone(), true).await;
1681
1682 drop(guard);
1684
1685 tokio::time::sleep(Duration::from_millis(50)).await;
1686
1687 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1688 .await
1689 .unwrap();
1690
1691 let register_msg = PluginMessage::Register {
1693 execution_id: execution_id.clone(),
1694 };
1695 client
1696 .write_all((serde_json::to_string(®ister_msg).unwrap() + "\n").as_bytes())
1697 .await
1698 .unwrap();
1699
1700 let trace = PluginMessage::Trace {
1702 trace: serde_json::json!({"event": "test"}),
1703 };
1704 client
1705 .write_all((serde_json::to_string(&trace).unwrap() + "\n").as_bytes())
1706 .await
1707 .unwrap();
1708
1709 let shutdown_msg = PluginMessage::Shutdown;
1711 client
1712 .write_all((serde_json::to_string(&shutdown_msg).unwrap() + "\n").as_bytes())
1713 .await
1714 .unwrap();
1715 client.flush().await.unwrap();
1716
1717 drop(client);
1718
1719 tokio::time::sleep(Duration::from_millis(200)).await;
1721
1722 service.shutdown().await;
1723 }
1724
1725 #[tokio::test]
1726 async fn test_get_shared_socket_service() {
1727 let service1 = get_shared_socket_service();
1729 assert!(service1.is_ok());
1730
1731 let service2 = get_shared_socket_service();
1732 assert!(service2.is_ok());
1733
1734 let svc1 = service1.unwrap();
1736 let svc2 = service2.unwrap();
1737 let path1 = svc1.socket_path();
1738 let path2 = svc2.socket_path();
1739 assert_eq!(path1, path2);
1740 }
1741
1742 #[tokio::test]
1743 async fn test_available_connection_slots_and_active_count() {
1744 let temp_dir = tempdir().unwrap();
1745 let socket_path = temp_dir.path().join("shared_slots.sock");
1746
1747 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1748 let state = create_mock_app_state(None, None, None, None, None, None).await;
1749 let max_connections = get_config().socket_max_connections;
1750
1751 assert_eq!(service.available_connection_slots(), max_connections);
1753 assert_eq!(service.active_connection_count(), 0);
1754
1755 service
1756 .clone()
1757 .start(Arc::new(web::ThinData(state)))
1758 .await
1759 .unwrap();
1760
1761 tokio::time::sleep(Duration::from_millis(50)).await;
1762
1763 let _client = UnixStream::connect(socket_path.to_str().unwrap())
1765 .await
1766 .unwrap();
1767
1768 tokio::time::sleep(Duration::from_millis(50)).await;
1769
1770 assert!(service.available_connection_slots() < max_connections);
1771 assert!(service.active_connection_count() > 0);
1772
1773 drop(_client);
1774 service.shutdown().await;
1775 }
1776
1777 #[tokio::test]
1778 async fn test_start_called_twice_is_idempotent() {
1779 let temp_dir = tempdir().unwrap();
1780 let socket_path = temp_dir.path().join("shared_double_start.sock");
1781
1782 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1783 let state = create_mock_app_state(None, None, None, None, None, None).await;
1784 let thin = Arc::new(web::ThinData(state));
1785
1786 service.clone().start(thin.clone()).await.unwrap();
1788
1789 service.clone().start(thin).await.unwrap();
1791
1792 service.shutdown().await;
1793 }
1794
1795 #[tokio::test]
1796 async fn test_traces_discarded_when_emit_traces_false() {
1797 let temp_dir = tempdir().unwrap();
1798 let socket_path = temp_dir.path().join("shared_no_traces.sock");
1799
1800 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1801 let state = create_mock_app_state(None, None, None, None, None, None).await;
1802
1803 service
1804 .clone()
1805 .start(Arc::new(web::ThinData(state)))
1806 .await
1807 .unwrap();
1808
1809 let execution_id = "test-exec-no-trace".to_string();
1810 let guard = service
1812 .register_execution(execution_id.clone(), false)
1813 .await;
1814 assert_eq!(service.registered_executions_count().await, 1);
1815
1816 tokio::time::sleep(Duration::from_millis(50)).await;
1817
1818 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1819 .await
1820 .unwrap();
1821
1822 let register_msg = PluginMessage::Register {
1824 execution_id: execution_id.clone(),
1825 };
1826 client
1827 .write_all((serde_json::to_string(®ister_msg).unwrap() + "\n").as_bytes())
1828 .await
1829 .unwrap();
1830
1831 let trace = PluginMessage::Trace {
1833 trace: serde_json::json!({"event": "should_be_discarded"}),
1834 };
1835 client
1836 .write_all((serde_json::to_string(&trace).unwrap() + "\n").as_bytes())
1837 .await
1838 .unwrap();
1839
1840 let shutdown_msg = PluginMessage::Shutdown;
1842 client
1843 .write_all((serde_json::to_string(&shutdown_msg).unwrap() + "\n").as_bytes())
1844 .await
1845 .unwrap();
1846 client.flush().await.unwrap();
1847 drop(client);
1848
1849 tokio::time::timeout(Duration::from_secs(1), async {
1851 loop {
1852 if service.active_connection_count() == 0 {
1853 break;
1854 }
1855 tokio::time::sleep(Duration::from_millis(20)).await;
1856 }
1857 })
1858 .await
1859 .expect("Connection should close promptly when traces are disabled");
1860
1861 assert!(guard.into_receiver().is_none());
1863 assert_eq!(service.registered_executions_count().await, 0);
1864
1865 service.shutdown().await;
1866 }
1867
1868 #[tokio::test]
1869 async fn test_legacy_protocol_without_http_request_id() {
1870 let temp_dir = tempdir().unwrap();
1871 let socket_path = temp_dir.path().join("shared_legacy_no_http_id.sock");
1872
1873 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1874 let state = create_mock_app_state(None, None, None, None, None, None).await;
1875
1876 service
1877 .clone()
1878 .start(Arc::new(web::ThinData(state)))
1879 .await
1880 .unwrap();
1881
1882 let request_id = "legacy-fallback-id".to_string();
1884 let _guard = service.register_execution(request_id.clone(), true).await;
1885
1886 tokio::time::sleep(Duration::from_millis(50)).await;
1887
1888 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1889 .await
1890 .unwrap();
1891
1892 let legacy_request = crate::services::plugins::relayer_api::Request {
1895 request_id: request_id.clone(),
1896 relayer_id: "relayer-1".to_string(),
1897 method: crate::services::plugins::relayer_api::PluginMethod::GetRelayerStatus,
1898 payload: serde_json::json!({}),
1899 http_request_id: None,
1900 };
1901 let legacy_json = serde_json::to_string(&legacy_request).unwrap() + "\n";
1902 client.write_all(legacy_json.as_bytes()).await.unwrap();
1903 client.flush().await.unwrap();
1904
1905 let (r, _w) = client.into_split();
1907 let mut reader = BufReader::new(r);
1908 let mut response_line = String::new();
1909 reader.read_line(&mut response_line).await.unwrap();
1910
1911 let response: crate::services::plugins::relayer_api::Response =
1912 serde_json::from_str(&response_line).unwrap();
1913 assert_eq!(response.request_id, request_id);
1914 assert!(response.result.is_some() || response.error.is_some());
1915
1916 drop(reader);
1917 drop(_w);
1918 service.shutdown().await;
1919 }
1920
1921 #[tokio::test]
1922 async fn test_legacy_protocol_with_unknown_execution_id() {
1923 let temp_dir = tempdir().unwrap();
1924 let socket_path = temp_dir.path().join("shared_legacy_unknown.sock");
1925
1926 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1927 let state = create_mock_app_state(None, None, None, None, None, None).await;
1928
1929 service
1930 .clone()
1931 .start(Arc::new(web::ThinData(state)))
1932 .await
1933 .unwrap();
1934
1935 tokio::time::sleep(Duration::from_millis(50)).await;
1939
1940 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1941 .await
1942 .unwrap();
1943
1944 let legacy_request = crate::services::plugins::relayer_api::Request {
1946 request_id: "unknown-req".to_string(),
1947 relayer_id: "relayer-1".to_string(),
1948 method: crate::services::plugins::relayer_api::PluginMethod::GetRelayerStatus,
1949 payload: serde_json::json!({}),
1950 http_request_id: Some("nonexistent-exec-id".to_string()),
1951 };
1952 let legacy_json = serde_json::to_string(&legacy_request).unwrap() + "\n";
1953 client.write_all(legacy_json.as_bytes()).await.unwrap();
1954 client.flush().await.unwrap();
1955
1956 let (r, _w) = client.into_split();
1958 let mut reader = BufReader::new(r);
1959 let mut response_line = String::new();
1960 reader.read_line(&mut response_line).await.unwrap();
1961
1962 let response: crate::services::plugins::relayer_api::Response =
1963 serde_json::from_str(&response_line).unwrap();
1964 assert_eq!(response.request_id, "unknown-req");
1965
1966 drop(reader);
1967 drop(_w);
1968 service.shutdown().await;
1969 }
1970
1971 #[tokio::test]
1972 async fn test_legacy_unparsable_message() {
1973 let temp_dir = tempdir().unwrap();
1974 let socket_path = temp_dir.path().join("shared_legacy_unparse.sock");
1975
1976 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
1977 let state = create_mock_app_state(None, None, None, None, None, None).await;
1978
1979 service
1980 .clone()
1981 .start(Arc::new(web::ThinData(state)))
1982 .await
1983 .unwrap();
1984
1985 let execution_id = "test-exec-legacy-unparse".to_string();
1986 let _guard = service.register_execution(execution_id.clone(), true).await;
1987
1988 tokio::time::sleep(Duration::from_millis(50)).await;
1989
1990 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
1991 .await
1992 .unwrap();
1993
1994 let register_msg = PluginMessage::Register {
1996 execution_id: execution_id.clone(),
1997 };
1998 client
1999 .write_all((serde_json::to_string(®ister_msg).unwrap() + "\n").as_bytes())
2000 .await
2001 .unwrap();
2002
2003 client.write_all(b"{\"foo\": \"bar\"}\n").await.unwrap();
2006 client.flush().await.unwrap();
2007
2008 tokio::time::sleep(Duration::from_millis(100)).await;
2009
2010 let shutdown_msg = PluginMessage::Shutdown;
2012 let write_result = client
2013 .write_all((serde_json::to_string(&shutdown_msg).unwrap() + "\n").as_bytes())
2014 .await;
2015
2016 assert!(
2017 write_result.is_ok(),
2018 "Connection should still be alive after unparsable legacy message"
2019 );
2020
2021 drop(client);
2022 service.shutdown().await;
2023 }
2024
2025 #[tokio::test]
2026 async fn test_invalid_plugin_message_type() {
2027 let temp_dir = tempdir().unwrap();
2028 let socket_path = temp_dir.path().join("shared_invalid_type.sock");
2029
2030 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
2031 let state = create_mock_app_state(None, None, None, None, None, None).await;
2032
2033 service
2034 .clone()
2035 .start(Arc::new(web::ThinData(state)))
2036 .await
2037 .unwrap();
2038
2039 let execution_id = "test-exec-invalid-type".to_string();
2040 let _guard = service.register_execution(execution_id.clone(), true).await;
2041
2042 tokio::time::sleep(Duration::from_millis(50)).await;
2043
2044 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
2045 .await
2046 .unwrap();
2047
2048 let register_msg = PluginMessage::Register {
2050 execution_id: execution_id.clone(),
2051 };
2052 client
2053 .write_all((serde_json::to_string(®ister_msg).unwrap() + "\n").as_bytes())
2054 .await
2055 .unwrap();
2056
2057 client
2060 .write_all(b"{\"type\": \"nonexistent_type\", \"data\": \"foo\"}\n")
2061 .await
2062 .unwrap();
2063 client.flush().await.unwrap();
2064
2065 let api_request = PluginMessage::ApiRequest {
2068 request_id: "req-after-invalid".to_string(),
2069 relayer_id: "relayer-1".to_string(),
2070 method: crate::services::plugins::relayer_api::PluginMethod::GetRelayerStatus,
2071 payload: serde_json::json!({}),
2072 };
2073 client
2074 .write_all((serde_json::to_string(&api_request).unwrap() + "\n").as_bytes())
2075 .await
2076 .unwrap();
2077 client.flush().await.unwrap();
2078
2079 let (r, mut w) = client.into_split();
2080 let mut reader = BufReader::new(r);
2081 let mut response_line = String::new();
2082 tokio::time::timeout(Duration::from_secs(1), reader.read_line(&mut response_line))
2083 .await
2084 .expect("Timed out waiting for ApiResponse")
2085 .unwrap();
2086
2087 let response: PluginMessage = serde_json::from_str(&response_line).unwrap();
2088 match response {
2089 PluginMessage::ApiResponse { request_id, .. } => {
2090 assert_eq!(request_id, "req-after-invalid");
2091 }
2092 _ => panic!("Expected ApiResponse after invalid plugin message"),
2093 }
2094
2095 let shutdown_msg = PluginMessage::Shutdown;
2096 w.write_all((serde_json::to_string(&shutdown_msg).unwrap() + "\n").as_bytes())
2097 .await
2098 .unwrap();
2099 w.flush().await.unwrap();
2100
2101 drop(reader);
2102 drop(w);
2103 service.shutdown().await;
2104 }
2105
2106 #[tokio::test]
2107 async fn test_service_drop_sends_shutdown() {
2108 let temp_dir = tempdir().unwrap();
2109 let socket_path = temp_dir.path().join("shared_drop.sock");
2110
2111 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
2112 let state = create_mock_app_state(None, None, None, None, None, None).await;
2113
2114 service
2115 .clone()
2116 .start(Arc::new(web::ThinData(state)))
2117 .await
2118 .unwrap();
2119
2120 let mut shutdown_rx = service.shutdown_tx.subscribe();
2122
2123 drop(service);
2125
2126 tokio::time::timeout(Duration::from_secs(1), shutdown_rx.changed())
2127 .await
2128 .expect("Timed out waiting for shutdown signal from Drop")
2129 .unwrap();
2130 assert!(
2131 *shutdown_rx.borrow(),
2132 "Drop should broadcast shutdown=true to listeners"
2133 );
2134 }
2135
2136 #[tokio::test]
2137 async fn test_trace_channel_closed_before_send() {
2138 let temp_dir = tempdir().unwrap();
2139 let socket_path = temp_dir.path().join("shared_trace_closed.sock");
2140
2141 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
2142 let state = create_mock_app_state(None, None, None, None, None, None).await;
2143
2144 service
2145 .clone()
2146 .start(Arc::new(web::ThinData(state)))
2147 .await
2148 .unwrap();
2149
2150 let execution_id = "test-exec-trace-closed".to_string();
2151 let guard = service.register_execution(execution_id.clone(), true).await;
2152
2153 let rx = guard.into_receiver();
2155 assert!(rx.is_some());
2156 drop(rx);
2157
2158 tokio::time::sleep(Duration::from_millis(50)).await;
2159
2160 let mut client = UnixStream::connect(socket_path.to_str().unwrap())
2161 .await
2162 .unwrap();
2163
2164 let register_msg = PluginMessage::Register {
2166 execution_id: execution_id.clone(),
2167 };
2168 client
2169 .write_all((serde_json::to_string(®ister_msg).unwrap() + "\n").as_bytes())
2170 .await
2171 .unwrap();
2172
2173 let trace = PluginMessage::Trace {
2175 trace: serde_json::json!({"event": "will_be_lost"}),
2176 };
2177 client
2178 .write_all((serde_json::to_string(&trace).unwrap() + "\n").as_bytes())
2179 .await
2180 .unwrap();
2181
2182 let shutdown_msg = PluginMessage::Shutdown;
2184 client
2185 .write_all((serde_json::to_string(&shutdown_msg).unwrap() + "\n").as_bytes())
2186 .await
2187 .unwrap();
2188 client.flush().await.unwrap();
2189 drop(client);
2190
2191 tokio::time::timeout(Duration::from_secs(1), async {
2193 loop {
2194 if service.active_connection_count() == 0 {
2195 break;
2196 }
2197 tokio::time::sleep(Duration::from_millis(20)).await;
2198 }
2199 })
2200 .await
2201 .expect("Connection should close even when trace receiver is dropped");
2202
2203 service.shutdown().await;
2204 }
2205
2206 #[tokio::test]
2207 async fn test_duplicate_execution_id_does_not_corrupt_counter() {
2208 let temp_dir = tempdir().unwrap();
2209 let socket_path = temp_dir.path().join("shared_duplicate_exec.sock");
2210
2211 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
2212 let execution_id = "duplicate-exec-id".to_string();
2213
2214 assert_eq!(service.registered_executions_count().await, 0);
2216
2217 let guard1 = service.register_execution(execution_id.clone(), true).await;
2219 assert_eq!(service.registered_executions_count().await, 1);
2220
2221 let guard2 = service.register_execution(execution_id.clone(), true).await;
2224 assert_eq!(service.registered_executions_count().await, 1);
2226
2227 drop(guard2);
2230 assert_eq!(service.registered_executions_count().await, 1);
2231
2232 drop(guard1);
2234 assert_eq!(service.registered_executions_count().await, 0);
2235 }
2236
2237 #[tokio::test]
2238 async fn test_execution_guard_registered_field() {
2239 let temp_dir = tempdir().unwrap();
2240 let socket_path = temp_dir.path().join("shared_registered_field.sock");
2241
2242 let service = Arc::new(SharedSocketService::new(socket_path.to_str().unwrap()).unwrap());
2243
2244 let execution_id_1 = "unique-exec-1".to_string();
2246 let guard1 = service
2247 .register_execution(execution_id_1.clone(), true)
2248 .await;
2249 assert_eq!(service.registered_executions_count().await, 1);
2250
2251 let execution_id_2 = "unique-exec-2".to_string();
2253 let guard2 = service
2254 .register_execution(execution_id_2.clone(), false)
2255 .await;
2256 assert_eq!(service.registered_executions_count().await, 2);
2257
2258 let rx = guard1.into_receiver();
2260 assert!(rx.is_some()); let rx2 = guard2.into_receiver();
2264 assert!(rx2.is_none()); assert_eq!(service.registered_executions_count().await, 0);
2268 }
2269
2270 #[test]
2275 fn test_log_socket_write_error_broken_pipe_does_not_panic() {
2276 let err = std::io::Error::new(std::io::ErrorKind::BrokenPipe, "Broken pipe");
2278 log_socket_write_error("API response", &err);
2279 }
2280
2281 #[test]
2282 fn test_log_socket_write_error_connection_reset_does_not_panic() {
2283 let err = std::io::Error::new(std::io::ErrorKind::ConnectionReset, "Connection reset");
2285 log_socket_write_error("API response flush", &err);
2286 }
2287
2288 #[test]
2289 fn test_log_socket_write_error_other_errors_do_not_panic() {
2290 let err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Permission denied");
2292 log_socket_write_error("response", &err);
2293 }
2294
2295 #[test]
2296 fn test_log_socket_write_error_unexpected_eof() {
2297 let err = std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected eof");
2298 log_socket_write_error("response flush", &err);
2299 }
2300
2301 #[test]
2302 fn test_log_socket_write_error_context_strings() {
2303 let err = std::io::Error::new(std::io::ErrorKind::BrokenPipe, "os error 32");
2305 log_socket_write_error("API response", &err);
2306 log_socket_write_error("API response flush", &err);
2307 log_socket_write_error("response", &err);
2308 log_socket_write_error("response flush", &err);
2309 }
2310}