1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//! The [`Producer`] handle (WP-5.4): send messages on a sender link.
use std::sync::Arc;
use bytes::Bytes;
use tokio::sync::{Semaphore, mpsc, oneshot};
use crate::api::lifecycle::detach_on_drop;
use crate::codec::to_bytes;
use crate::error::{ErrorKind, LinkError, SendError};
use crate::ids::{ChannelId, Handle};
use crate::proto::{DriverCommand, LinkEvent};
use crate::types::messaging::{DeliveryState, Message};
/// A handle for sending on an attached sender link.
#[derive(Debug)]
pub struct Producer {
commands: mpsc::Sender<DriverCommand>,
channel: ChannelId,
handle: Handle,
#[allow(dead_code)]
events: mpsc::Receiver<LinkEvent>,
/// Bounds buffered fire-and-forget sends: one permit per unwritten
/// [`send_settled`](Self::send_settled) message, released when the driver
/// writes it. `None` = unbounded (`max_outbox == 0`).
outbox: Option<Arc<Semaphore>>,
}
impl Producer {
pub(crate) fn new(
commands: mpsc::Sender<DriverCommand>,
channel: ChannelId,
handle: Handle,
events: mpsc::Receiver<LinkEvent>,
max_outbox: usize,
) -> Self {
Producer {
commands,
channel,
handle,
events,
outbox: (max_outbox > 0).then(|| Arc::new(Semaphore::new(max_outbox))),
}
}
/// The link handle.
pub fn handle(&self) -> Handle {
self.handle
}
/// Send a message and await its terminal delivery state (the outcome the
/// peer settled with — `Accepted`, `Rejected`, …).
///
/// # Examples
/// ```no_run
/// # async fn ex(producer: &ramqp::Producer) -> Result<(), Box<dyn std::error::Error>> {
/// use ramqp::{Message, types::messaging::DeliveryState};
///
/// match producer.send(Message::text("order-42")).await? {
/// DeliveryState::Accepted(_) => println!("durably enqueued"),
/// other => eprintln!("broker did not accept: {other:?}"),
/// }
/// # Ok(()) }
/// ```
pub async fn send(&self, message: Message) -> Result<DeliveryState, SendError> {
self.send_bytes(to_bytes(&message).freeze(), false).await
}
/// Send a pre-encoded body and await its outcome.
pub async fn send_bytes(&self, body: Bytes, settled: bool) -> Result<DeliveryState, SendError> {
let (reply_tx, reply_rx) = oneshot::channel();
self.commands
.send(DriverCommand::SendTransfer {
channel: self.channel,
handle: self.handle,
body,
settled,
message_format: 0,
reply: Some(reply_tx),
})
.await
.map_err(|_| SendError::msg(ErrorKind::NotConnected, "connection closed"))?;
reply_rx
.await
.map_err(|_| SendError::msg(ErrorKind::Cancelled, "driver dropped"))?
}
/// Send a message pre-settled (fire-and-forget; no disposition awaited).
///
/// This trades the disposition round-trip for throughput, but is bounded:
/// at most `max_outbox` (see [`LinkConfig`](crate::config::LinkConfig))
/// unwritten messages may be buffered while awaiting broker credit, after
/// which the call back-pressures (awaits an outbox slot) rather than growing
/// memory unbounded. Set `max_outbox = 0` to opt back into unbounded
/// buffering. Use [`send`](Producer::send) when you need the full
/// credit→disposition loop and the broker's terminal outcome.
///
/// # Examples
/// ```no_run
/// # async fn ex(producer: &ramqp::Producer) -> Result<(), Box<dyn std::error::Error>> {
/// use ramqp::Message;
/// // Fire a burst; the bounded outbox back-pressures instead of buffering forever.
/// for i in 0..1000 {
/// producer.send_settled(Message::text(format!("event-{i}"))).await?;
/// }
/// # Ok(()) }
/// ```
pub async fn send_settled(&self, message: Message) -> Result<(), SendError> {
let body = to_bytes(&message).freeze();
match &self.outbox {
// Bounded: hold a permit until the driver confirms the write.
Some(sem) => {
let permit = sem
.clone()
.acquire_owned()
.await
.map_err(|_| SendError::msg(ErrorKind::Cancelled, "producer closed"))?;
let (reply_tx, reply_rx) = oneshot::channel();
self.commands
.send(DriverCommand::SendTransfer {
channel: self.channel,
handle: self.handle,
body,
settled: true,
message_format: 0,
reply: Some(reply_tx),
})
.await
.map_err(|_| SendError::msg(ErrorKind::NotConnected, "connection closed"))?;
// Free the slot once the message is written (or the link drops).
tokio::spawn(async move {
let _ = reply_rx.await;
drop(permit);
});
Ok(())
}
// Unbounded (opt-in): pure fire-and-forget, no completion tracked.
None => self
.commands
.send(DriverCommand::SendTransfer {
channel: self.channel,
handle: self.handle,
body,
settled: true,
message_format: 0,
reply: None,
})
.await
.map_err(|_| SendError::msg(ErrorKind::NotConnected, "connection closed")),
}
}
/// Detach the link and await completion.
pub async fn detach(mut self) -> Result<(), LinkError> {
let (tx, rx) = oneshot::channel();
let commands = self.commands.clone();
// Prevent the Drop impl from also detaching.
let handle = std::mem::replace(&mut self.handle, Handle(u32::MAX));
commands
.send(DriverCommand::DetachLink {
channel: self.channel,
handle,
closed: true,
error: None,
reply: tx,
})
.await
.map_err(|_| LinkError::msg(ErrorKind::NotConnected, "connection closed"))?;
rx.await
.map_err(|_| LinkError::msg(ErrorKind::Cancelled, "driver dropped"))?
}
}
impl Drop for Producer {
fn drop(&mut self) {
if self.handle != Handle(u32::MAX) {
detach_on_drop(&self.commands, self.channel, self.handle);
}
}
}