1use std::collections::HashMap;
16use std::fmt;
17use std::sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError};
18use std::time::Duration;
19
20use bytes::Bytes;
21use ruststream::testing::{Coordinator, InProcess, TestableBroker};
22use ruststream::{OutgoingMessage, RawMessage};
23use tokio::sync::mpsc;
24use zeromq::ZmqError as WireError;
25use zeromq::ZmqMessage as WireMessage;
26
27use crate::common::Lifecycle;
28use crate::endpoint::{Bind, Connect, EndpointRole, Side};
29use crate::error::ZmqError;
30use crate::fanout::{ConnectedZmqFanout, ZmqFanout};
31use crate::message::ZmqMessage;
32use crate::queue::{ConnectedZmqQueue, ZmqQueue};
33use crate::rpc::{ConnectedZmqRpc, REPLY_PREFIX, ZmqRpc, peer_identity};
34use crate::wire;
35
36pub(crate) type Deliveries = mpsc::UnboundedReceiver<Result<ZmqMessage, ZmqError>>;
38
39pub(crate) type Read = fn(WireMessage) -> Option<Result<ZmqMessage, ZmqError>>;
41
42#[derive(Debug, Clone, Copy)]
44pub(crate) enum Pick<'a> {
45 All,
48 OneInTurn,
51 Prefix(&'a str),
53 Nobody,
55}
56
57#[derive(Debug)]
59enum Peer {
60 Waiting(mpsc::UnboundedSender<WireMessage>),
62 Foreign,
65}
66
67struct Subscription {
68 id: u64,
69 name: String,
70 deliveries: mpsc::UnboundedSender<Result<ZmqMessage, ZmqError>>,
71 read: Read,
72}
73
74#[derive(Default)]
75struct State {
76 next_id: u64,
77 subscriptions: Vec<Subscription>,
79 turn: usize,
80 log: HashMap<String, Vec<RawMessage>>,
81 peers: HashMap<Bytes, Peer>,
82}
83
84#[derive(Default)]
87pub(crate) struct Bus {
88 state: Mutex<State>,
89 coordinator: OnceLock<Coordinator>,
90}
91
92impl fmt::Debug for Bus {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 f.debug_struct("Bus").finish_non_exhaustive()
95 }
96}
97
98pub(crate) struct Registration {
101 bus: Arc<Bus>,
102 id: u64,
103}
104
105impl Registration {
106 pub(crate) fn give_up(&self) {
108 self.bus
109 .state()
110 .subscriptions
111 .retain(|sub| sub.id != self.id);
112 }
113}
114
115#[derive(Debug, Default)]
120pub(crate) struct Release(Option<Coordinator>);
121
122impl Drop for Release {
123 fn drop(&mut self) {
124 if let Some(coordinator) = &self.0 {
125 coordinator.consumed();
126 }
127 }
128}
129
130impl Bus {
131 fn state(&self) -> MutexGuard<'_, State> {
132 self.state.lock().unwrap_or_else(PoisonError::into_inner)
135 }
136
137 fn install(&self, coordinator: Coordinator) {
138 let _ = self.coordinator.set(coordinator);
139 }
140
141 pub(crate) fn subscribe(
143 self: &Arc<Self>,
144 name: &str,
145 read: Read,
146 ) -> (Deliveries, Registration) {
147 let (tx, rx) = mpsc::unbounded_channel();
148 let mut state = self.state();
149 let id = state.next_id;
150 state.next_id += 1;
151 state.subscriptions.push(Subscription {
152 id,
153 name: name.to_owned(),
154 deliveries: tx,
155 read,
156 });
157 drop(state);
158 (
159 rx,
160 Registration {
161 bus: Arc::clone(self),
162 id,
163 },
164 )
165 }
166
167 pub(crate) fn send(
170 &self,
171 destination: &str,
172 frames: WireMessage,
173 identity: Option<&Bytes>,
174 pick: Pick<'_>,
175 ) -> usize {
176 let mut state = self.state();
177 record(&mut state, destination, &frames);
178 let chosen: Vec<_> = match pick {
179 Pick::Nobody => Vec::new(),
180 Pick::All => state.subscriptions.iter().collect(),
181 Pick::Prefix(name) => state
182 .subscriptions
183 .iter()
184 .filter(|sub| name.starts_with(sub.name.as_str()))
185 .collect(),
186 Pick::OneInTurn if state.subscriptions.is_empty() => Vec::new(),
187 Pick::OneInTurn => {
188 let at = state.turn % state.subscriptions.len();
189 state.turn = state.turn.wrapping_add(1);
190 vec![&state.subscriptions[at]]
191 }
192 };
193 let chosen: Vec<_> = chosen
194 .into_iter()
195 .map(|sub| (sub.deliveries.clone(), sub.read))
196 .collect();
197 drop(state);
199
200 let mut received = frames;
201 if let Some(identity) = identity {
202 received.push_front(identity.clone());
203 }
204 let reached = chosen.len();
205 for (deliveries, read) in chosen {
206 let Some(delivery) = read(received.clone()) else {
207 continue;
208 };
209 let delivery = match (delivery, self.coordinator.get()) {
210 (Ok(message), Some(coordinator)) => {
211 coordinator.enqueued();
214 Ok(message.counted(Release(Some(coordinator.clone()))))
215 }
216 (delivery, _) => delivery,
217 };
218 let _ = deliveries.send(delivery);
221 }
222 reached
223 }
224
225 pub(crate) fn reply(&self, destination: &str, frames: WireMessage) -> Result<(), ZmqError> {
233 let mut frames = frames.into_vecdeque();
234 let identity = frames.pop_front().unwrap_or_default();
235 let frames = WireMessage::try_from(frames)
236 .map_err(|_| ZmqError::Wire("a reply needs name and payload frames".into()))?;
237 let mut state = self.state();
238 let waiting = match state.peers.get(&identity) {
239 Some(Peer::Waiting(inbox)) => Some(inbox.clone()),
240 Some(Peer::Foreign) => {
241 state.peers.remove(&identity);
243 None
244 }
245 None => {
246 return Err(ZmqError::Send {
247 name: destination.to_owned(),
248 reason: WireError::Other("Destination client not found by identity")
249 .to_string(),
250 });
251 }
252 };
253 record(&mut state, destination, &frames);
254 drop(state);
255 if let Some(inbox) = waiting {
256 let _ = inbox.send(frames);
257 }
258 Ok(())
259 }
260
261 pub(crate) fn open_inbox(&self, identity: Bytes) -> mpsc::UnboundedReceiver<WireMessage> {
263 let (tx, rx) = mpsc::unbounded_channel();
264 self.state().peers.insert(identity, Peer::Waiting(tx));
265 rx
266 }
267
268 pub(crate) fn close_inbox(&self, identity: &Bytes) {
270 self.state().peers.remove(identity);
271 }
272
273 fn foreign_peer(&self) -> Bytes {
275 let identity = peer_identity();
276 self.state().peers.insert(identity.clone(), Peer::Foreign);
277 identity
278 }
279
280 fn published(&self, name: &str) -> Vec<RawMessage> {
281 self.state().log.get(name).cloned().unwrap_or_default()
282 }
283}
284
285fn record(state: &mut State, destination: &str, frames: &WireMessage) {
288 let entry = match wire::decode(frames.clone()) {
289 Ok((_, headers, payload)) => RawMessage::new(destination, payload).with_headers(headers),
290 Err(_) => RawMessage::new(destination, frames.get(2).cloned().unwrap_or_default()),
291 };
292 state
293 .log
294 .entry(destination.to_owned())
295 .or_default()
296 .push(entry);
297}
298
299fn foreign(message: &OutgoingMessage<'_>) -> WireMessage {
301 wire::encode_foreign(
302 message.name(),
303 message.headers(),
304 Bytes::copy_from_slice(message.payload()),
305 )
306}
307
308fn positions(subscriptions: &[&str], pick: Pick<'_>) -> Vec<usize> {
310 match pick {
311 Pick::Nobody | Pick::OneInTurn => Vec::new(),
312 Pick::All => (0..subscriptions.len()).collect(),
313 Pick::Prefix(name) => subscriptions
314 .iter()
315 .enumerate()
316 .filter(|(_, subscription)| name.starts_with(**subscription))
317 .map(|(position, _)| position)
318 .collect(),
319 }
320}
321
322impl<Role: EndpointRole> InProcess for ZmqQueue<Role> {
323 async fn connect_in_process(self) -> Result<Self::Connected, Self::Error> {
324 self.connect_with(Lifecycle::in_process).await
325 }
326}
327
328impl<Role: EndpointRole> InProcess for ZmqFanout<Role> {
329 async fn connect_in_process(self) -> Result<Self::Connected, Self::Error> {
330 self.connect_with(Lifecycle::in_process).await
331 }
332}
333
334impl InProcess for ZmqRpc {
335 async fn connect_in_process(self) -> Result<Self::Connected, Self::Error> {
336 self.connect_with(Lifecycle::in_process).await
337 }
338}
339
340impl<Role: EndpointRole> TestableBroker for ConnectedZmqQueue<Role> {
345 fn install_coordinator(&self, coordinator: Coordinator) {
346 if let Some(bus) = self.lifecycle().in_process_bus() {
347 bus.install(coordinator);
348 }
349 }
350
351 fn inject(&self, message: OutgoingMessage<'_>) {
352 if let Some(bus) = self.lifecycle().in_process_bus() {
353 let pick = match self.lifecycle().endpoint.side() {
354 Side::Bind => Pick::All,
355 Side::Connect => Pick::OneInTurn,
356 };
357 bus.send(message.name(), foreign(&message), None, pick);
358 }
359 }
360
361 fn published(&self, name: &str) -> Vec<RawMessage> {
362 self.lifecycle()
363 .in_process_bus()
364 .map(|bus| bus.published(name))
365 .unwrap_or_default()
366 }
367
368 fn routes(&self, _destination: &str, subscriptions: &[&str]) -> Vec<usize> {
369 let pick = match self.lifecycle().endpoint.side() {
370 Side::Bind => Pick::All,
371 Side::Connect => Pick::Nobody,
374 };
375 positions(subscriptions, pick)
376 }
377}
378
379impl<Role: EndpointRole> TestableBroker for ConnectedZmqFanout<Role> {
382 fn install_coordinator(&self, coordinator: Coordinator) {
383 if let Some(bus) = self.lifecycle().in_process_bus() {
384 bus.install(coordinator);
385 }
386 }
387
388 fn inject(&self, message: OutgoingMessage<'_>) {
389 if let Some(bus) = self.lifecycle().in_process_bus() {
390 bus.send(
391 message.name(),
392 foreign(&message),
393 None,
394 Pick::Prefix(message.name()),
395 );
396 }
397 }
398
399 fn published(&self, name: &str) -> Vec<RawMessage> {
400 self.lifecycle()
401 .in_process_bus()
402 .map(|bus| bus.published(name))
403 .unwrap_or_default()
404 }
405
406 fn routes(&self, destination: &str, subscriptions: &[&str]) -> Vec<usize> {
407 let pick = match self.lifecycle().endpoint.side() {
408 Side::Bind => Pick::Prefix(destination),
409 Side::Connect => Pick::Nobody,
410 };
411 positions(subscriptions, pick)
412 }
413}
414
415impl TestableBroker for ConnectedZmqRpc {
419 fn install_coordinator(&self, coordinator: Coordinator) {
420 if let Some(bus) = self.lifecycle().in_process_bus() {
421 bus.install(coordinator);
422 }
423 }
424
425 fn inject(&self, message: OutgoingMessage<'_>) {
426 if let Some(bus) = self.lifecycle().in_process_bus() {
427 let pick = match self.lifecycle().endpoint.side() {
428 Side::Bind => Pick::All,
429 Side::Connect => Pick::OneInTurn,
430 };
431 let identity = bus.foreign_peer();
432 if bus.send(message.name(), foreign(&message), Some(&identity), pick) == 0 {
433 bus.close_inbox(&identity);
435 }
436 }
437 }
438
439 fn published(&self, name: &str) -> Vec<RawMessage> {
440 self.lifecycle()
441 .in_process_bus()
442 .map(|bus| bus.published(name))
443 .unwrap_or_default()
444 }
445
446 fn routes(&self, destination: &str, subscriptions: &[&str]) -> Vec<usize> {
447 let pick = match self.lifecycle().endpoint.side() {
448 Side::Bind if !destination.starts_with(REPLY_PREFIX) => Pick::All,
449 Side::Bind | Side::Connect => Pick::Nobody,
450 };
451 positions(subscriptions, pick)
452 }
453}
454
455ruststream::register_testable_broker!(ZmqQueue<Bind>);
456ruststream::register_testable_broker!(ZmqQueue<Connect>);
457ruststream::register_testable_broker!(ZmqFanout<Bind>);
458ruststream::register_testable_broker!(ZmqFanout<Connect>);
459ruststream::register_testable_broker!(ZmqRpc);
460
461pub(crate) async fn request(
465 bus: &Bus,
466 local: bool,
467 name: &str,
468 frames: WireMessage,
469 correlation: &str,
470 timeout: Duration,
471) -> Result<ZmqMessage, ZmqError> {
472 let identity = peer_identity();
473 let mut inbox = bus.open_inbox(identity.clone());
474 let _connected = Connected {
476 bus,
477 identity: &identity,
478 };
479 let pick = if local { Pick::All } else { Pick::Nobody };
480 let _ = bus.send(name, frames, Some(&identity), pick);
481 let exchange = async {
482 while let Some(reply) = inbox.recv().await {
483 let (name, headers, payload) = wire::decode(reply)?;
484 if headers.correlation_id() == Some(correlation) {
485 return Ok(ZmqMessage::new(name, headers, payload));
486 }
487 }
488 Err(ZmqError::RequestTimeout)
489 };
490 tokio::time::timeout(timeout, exchange)
491 .await
492 .unwrap_or(Err(ZmqError::RequestTimeout))
493}
494
495struct Connected<'a> {
497 bus: &'a Bus,
498 identity: &'a Bytes,
499}
500
501impl Drop for Connected<'_> {
502 fn drop(&mut self) {
503 self.bus.close_inbox(self.identity);
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use futures::FutureExt;
510 use ruststream::HeaderMap;
511
512 use super::*;
513 use crate::endpoint::ZmqEndpoint;
514
515 impl Bus {
516 fn peers(&self) -> usize {
517 self.state().peers.len()
518 }
519 }
520
521 #[tokio::test]
522 async fn an_injected_request_no_responder_takes_leaves_no_peer() {
523 let rpc = ZmqRpc::new(ZmqEndpoint::bind("tcp://127.0.0.1:0"))
524 .connect_in_process()
525 .await
526 .expect("connects in process");
527 TestableBroker::inject(&rpc, OutgoingMessage::new("orders", b"x".as_slice()));
528 let bus = rpc.lifecycle().in_process_bus().expect("in process");
529 assert_eq!(bus.peers(), 0);
530 }
531
532 #[tokio::test]
533 async fn a_request_dropped_while_it_waits_leaves_no_peer() {
534 let bus = Bus::default();
535 let frames = wire::encode_to(
536 "orders",
537 "orders",
538 &HeaderMap::new(),
539 Bytes::from_static(b"x"),
540 )
541 .expect("frames");
542 let pending =
543 request(&bus, false, "orders", frames, "c-1", Duration::from_mins(1)).now_or_never();
544 assert!(pending.is_none(), "the request was still waiting");
545 assert_eq!(bus.peers(), 0);
546 }
547}