use std::collections::BTreeSet;
use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::kinds::TransportConfig;
use crate::schema::{Registry, RegistryError, SchemaId};
use crate::transport::{
Batch, Changed, ConsumerName, DocumentName, Fingerprint, Position, QueueName, Stored,
TornRecord, Transport, TransportError,
};
pub const PROTOCOL: &str = "onemessagebus-transport";
pub const PROTOCOL_VERSION: u32 = 1;
const REMOTE_WAIT: Duration = Duration::from_millis(50);
pub const PLUGIN_PREFIX: &str = "onemessagebus-transport-";
pub const HELLO_SCHEMA: SchemaId = SchemaId::literal("onemessagebus", "transport-hello", 1);
pub const REQUEST_SCHEMA: SchemaId = SchemaId::literal("onemessagebus", "transport-request", 1);
pub const REPLY_SCHEMA: SchemaId = SchemaId::literal("onemessagebus", "transport-reply", 1);
pub fn register_protocol(registry: &mut Registry) -> Result<(), RegistryError> {
registry.register_schema(HELLO_SCHEMA, schemars::schema_for!(PluginHello).to_value())?;
registry.register_schema(
REQUEST_SCHEMA,
schemars::schema_for!(PluginRequest).to_value(),
)?;
registry.register_schema(REPLY_SCHEMA, schemars::schema_for!(PluginReply).to_value())
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct PluginHello {
pub protocol: String,
pub version: u32,
pub config: TransportConfig,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "op", rename_all = "snake_case", deny_unknown_fields)]
pub enum PluginRequest {
Append {
queue: QueueName,
record: String,
},
Read {
queue: QueueName,
#[serde(default, skip_serializing_if = "Option::is_none")]
from: Option<Position>,
limit: u64,
},
Cursor {
queue: QueueName,
consumer: ConsumerName,
},
Commit {
queue: QueueName,
consumer: ConsumerName,
at: Position,
},
BeginExclusive {
queue: QueueName,
},
EndExclusive {
queue: QueueName,
failed: bool,
},
Fingerprint {
queue: QueueName,
},
WaitForChange {
queue: QueueName,
since: Fingerprint,
timeout_ms: u64,
},
Document {
queue: QueueName,
name: DocumentName,
},
ReplaceDocument {
queue: QueueName,
name: DocumentName,
bytes: String,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum PluginReply {
Ok(PluginAnswer),
Error(PluginError),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum PluginAnswer {
Hello {
protocol: String,
version: u32,
},
Position(Position),
Batch {
records: Vec<PluginStored>,
#[serde(default, skip_serializing_if = "Option::is_none")]
torn: Option<PluginTorn>,
},
Cursor(Option<Position>),
Fingerprint(Fingerprint),
Moved(Fingerprint),
Unchanged(Fingerprint),
Document(Option<String>),
Done,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct PluginStored {
pub record: String,
pub after: Position,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct PluginTorn {
pub at: Position,
pub bytes: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum PluginError {
PastEnd {
message: String,
queue: QueueName,
position: Position,
end: Position,
},
NotABoundary {
message: String,
queue: QueueName,
position: Position,
},
Refused {
message: String,
},
Protocol {
message: String,
},
}
impl PluginError {
fn of(failure: &TransportError) -> Self {
let message = failure.to_string();
match failure {
TransportError::PastEnd {
queue,
position,
end,
} => Self::PastEnd {
message,
queue: queue.clone(),
position: *position,
end: *end,
},
TransportError::NotABoundary { queue, position } => Self::NotABoundary {
message,
queue: queue.clone(),
position: *position,
},
_ => Self::Refused { message },
}
}
fn protocol(message: impl Into<String>) -> Self {
Self::Protocol {
message: message.into(),
}
}
fn into_transport_error(self, kind: &str) -> TransportError {
match self {
Self::PastEnd {
queue,
position,
end,
..
} => TransportError::PastEnd {
queue,
position,
end,
},
Self::NotABoundary {
queue, position, ..
} => TransportError::NotABoundary { queue, position },
Self::Refused { message } | Self::Protocol { message } => TransportError::Backend {
transport: kind.to_owned(),
detail: message,
},
}
}
}
pub fn serve(
open: impl FnOnce(&TransportConfig) -> Result<Arc<dyn Transport>, TransportError>,
input: impl BufRead,
mut output: impl Write,
) -> Result<(), TransportError> {
let mut lines = input.lines();
let backend = |detail: String| TransportError::Backend {
transport: "plugin".to_owned(),
detail,
};
let Some(first) = lines.next() else {
return Ok(());
};
let first = first.map_err(|failure| backend(format!("cannot read the hello: {failure}")))?;
let hello: PluginHello = match serde_json::from_str(&first) {
Ok(hello) => hello,
Err(failure) => {
let why = format!("the first line is not a {PROTOCOL} hello: {failure}");
write_reply(
&mut output,
&PluginReply::Error(PluginError::protocol(why.clone())),
)?;
return Err(backend(why));
}
};
if hello.protocol != PROTOCOL || hello.version != PROTOCOL_VERSION {
let why = format!(
"the client speaks {} version {}, and this plugin speaks {PROTOCOL} version {PROTOCOL_VERSION}",
hello.protocol, hello.version
);
write_reply(
&mut output,
&PluginReply::Error(PluginError::protocol(why.clone())),
)?;
return Err(backend(why));
}
let transport = match open(&hello.config) {
Ok(transport) => transport,
Err(failure) => {
write_reply(&mut output, &PluginReply::Error(PluginError::of(&failure)))?;
return Err(failure);
}
};
write_reply(
&mut output,
&PluginReply::Ok(PluginAnswer::Hello {
protocol: PROTOCOL.to_owned(),
version: PROTOCOL_VERSION,
}),
)?;
let mut next_line = || lines.next();
serve_until(transport.as_ref(), &mut next_line, &mut output, None)
}
type NextLine<'a> = dyn FnMut() -> Option<std::io::Result<String>> + 'a;
fn serve_until(
transport: &dyn Transport,
next_line: &mut NextLine<'_>,
output: &mut dyn Write,
section: Option<&QueueName>,
) -> Result<(), TransportError> {
let backend = |detail: String| TransportError::Backend {
transport: "plugin".to_owned(),
detail,
};
while let Some(line) = next_line() {
let line = line.map_err(|failure| backend(format!("cannot read a request: {failure}")))?;
if line.trim().is_empty() {
continue;
}
let request: PluginRequest = match serde_json::from_str(&line) {
Ok(request) => request,
Err(failure) => {
write_reply(
output,
&PluginReply::Error(PluginError::protocol(format!(
"not a {PROTOCOL} request: {failure}"
))),
)?;
continue;
}
};
let answer = match request {
PluginRequest::EndExclusive { queue, failed } => {
if section == Some(&queue) {
return if failed {
Err(backend(format!("the client's section over {queue} failed")))
} else {
Ok(())
};
}
Err(PluginError::protocol(format!(
"end_exclusive for {queue}, which no open section holds"
)))
}
PluginRequest::BeginExclusive { queue } => {
write_reply(output, &PluginReply::Ok(PluginAnswer::Done))?;
let ended = transport.exclusive(&queue, &mut |inner| {
serve_until(inner, next_line, output, Some(&queue))
});
match ended {
Ok(()) => Ok(PluginAnswer::Done),
Err(failure) => Err(PluginError::of(&failure)),
}
}
other => answer(transport, other).map_err(|failure| PluginError::of(&failure)),
};
let reply = match answer {
Ok(answer) => PluginReply::Ok(answer),
Err(error) => PluginReply::Error(error),
};
write_reply(output, &reply)?;
}
match section {
None => Ok(()),
Some(queue) => Err(backend(format!(
"the client went away inside its section over {queue}"
))),
}
}
fn answer(
transport: &dyn Transport,
request: PluginRequest,
) -> Result<PluginAnswer, TransportError> {
Ok(match request {
PluginRequest::Append { queue, record } => {
PluginAnswer::Position(transport.append(&queue, record.as_bytes())?)
}
PluginRequest::Read { queue, from, limit } => {
let batch = transport.read(
&queue,
from.as_ref(),
usize::try_from(limit).unwrap_or(usize::MAX),
)?;
PluginAnswer::Batch {
records: batch
.records
.into_iter()
.map(|stored| {
let record = String::from_utf8(stored.bytes).map_err(|_| {
TransportError::NotARecord {
queue: queue.clone(),
why: "is not UTF-8, which the plugin protocol carries records as",
}
})?;
Ok(PluginStored {
record,
after: stored.after,
})
})
.collect::<Result<_, TransportError>>()?,
torn: batch.torn.map(|torn| PluginTorn {
at: torn.at,
bytes: torn.bytes,
}),
}
}
PluginRequest::Cursor { queue, consumer } => {
PluginAnswer::Cursor(transport.cursor(&queue, &consumer)?)
}
PluginRequest::Commit {
queue,
consumer,
at,
} => {
transport.commit(&queue, &consumer, &at)?;
PluginAnswer::Done
}
PluginRequest::Fingerprint { queue } => {
PluginAnswer::Fingerprint(transport.fingerprint(&queue)?)
}
PluginRequest::WaitForChange {
queue,
since,
timeout_ms,
} => match transport.wait_for_change(&queue, &since, Duration::from_millis(timeout_ms))? {
Changed::Moved(fingerprint) => PluginAnswer::Moved(fingerprint),
Changed::Unchanged(fingerprint) => PluginAnswer::Unchanged(fingerprint),
},
PluginRequest::Document { queue, name } => PluginAnswer::Document(
transport
.document(&queue, &name)?
.map(|bytes| {
String::from_utf8(bytes).map_err(|_| TransportError::Backend {
transport: "plugin".to_owned(),
detail: format!(
"document {name} is not UTF-8, which the plugin protocol carries documents as"
),
})
})
.transpose()?,
),
PluginRequest::ReplaceDocument { queue, name, bytes } => {
transport.replace_document(&queue, &name, bytes.as_bytes())?;
PluginAnswer::Done
}
PluginRequest::BeginExclusive { .. } | PluginRequest::EndExclusive { .. } => {
return Err(TransportError::Backend {
transport: "plugin".to_owned(),
detail: "a section request is not a single exchange".to_owned(),
})
}
})
}
fn write_reply(output: &mut dyn Write, reply: &PluginReply) -> Result<(), TransportError> {
let mut line = serde_json::to_string(reply).map_err(|failure| TransportError::Backend {
transport: "plugin".to_owned(),
detail: format!("cannot render a reply: {failure}"),
})?;
line.push('\n');
output
.write_all(line.as_bytes())
.and_then(|()| output.flush())
.map_err(|failure| TransportError::Backend {
transport: "plugin".to_owned(),
detail: format!("cannot write a reply: {failure}"),
})
}
struct PluginProcess {
kind: String,
path: PathBuf,
pipe: Mutex<Pipe>,
released: Condvar,
holders: AtomicU64,
}
impl std::fmt::Debug for PluginProcess {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PluginProcess")
.field("kind", &self.kind)
.field("path", &self.path)
.finish_non_exhaustive()
}
}
struct Pipe {
child: Child,
stdin: Option<ChildStdin>,
stdout: BufReader<ChildStdout>,
held_by: Option<u64>,
}
impl PluginProcess {
fn spawn(path: &Path, config: &TransportConfig) -> Result<Self, TransportError> {
let kind = config.kind.to_string();
let backend = |detail: String| TransportError::Backend {
transport: kind.clone(),
detail,
};
let mut child = Command::new(path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.map_err(|failure| {
backend(format!(
"cannot start the plugin {}: {failure}",
path.display()
))
})?;
let (Some(stdin), Some(stdout)) = (child.stdin.take(), child.stdout.take()) else {
return Err(backend("the plugin was started without pipes".to_owned()));
};
let transport = Self {
kind: kind.clone(),
path: path.to_path_buf(),
pipe: Mutex::new(Pipe {
child,
stdin: Some(stdin),
stdout: BufReader::new(stdout),
held_by: None,
}),
released: Condvar::new(),
holders: AtomicU64::new(0),
};
let hello = serde_json::to_string(&PluginHello {
protocol: PROTOCOL.to_owned(),
version: PROTOCOL_VERSION,
config: config.clone(),
})
.map_err(|failure| backend(format!("cannot render the hello: {failure}")))?;
match transport.exchange_line(None, &hello)? {
PluginAnswer::Hello { protocol, version }
if protocol == PROTOCOL && version == PROTOCOL_VERSION =>
{
Ok(transport)
}
PluginAnswer::Hello { protocol, version } => Err(backend(format!(
"the plugin {} speaks {protocol} version {version}, and this build speaks {PROTOCOL} version {PROTOCOL_VERSION}",
path.display()
))),
other => Err(backend(format!(
"the plugin {} answered the hello with {other:?}",
path.display()
))),
}
}
fn backend(&self, detail: String) -> TransportError {
TransportError::Backend {
transport: self.kind.clone(),
detail,
}
}
fn exchange_line(
&self,
holder: Option<u64>,
line: &str,
) -> Result<PluginAnswer, TransportError> {
let mut pipe = self
.pipe
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
while let Some(owner) = pipe.held_by {
if Some(owner) == holder {
break;
}
pipe = self
.released
.wait(pipe)
.unwrap_or_else(std::sync::PoisonError::into_inner);
}
self.exchange_on(&mut pipe, line)
}
fn exchange_on(&self, pipe: &mut Pipe, line: &str) -> Result<PluginAnswer, TransportError> {
let Some(stdin) = pipe.stdin.as_mut() else {
return Err(self.backend("the plugin's input is closed".to_owned()));
};
stdin
.write_all(format!("{line}\n").as_bytes())
.and_then(|()| stdin.flush())
.map_err(|failure| self.backend(format!("cannot write to the plugin: {failure}")))?;
let mut reply = String::new();
let read = pipe
.stdout
.read_line(&mut reply)
.map_err(|failure| self.backend(format!("cannot read from the plugin: {failure}")))?;
if read == 0 {
return Err(self.backend(format!(
"the plugin {} exited without answering",
self.path.display()
)));
}
match serde_json::from_str::<PluginReply>(&reply) {
Ok(PluginReply::Ok(answer)) => Ok(answer),
Ok(PluginReply::Error(error)) => Err(error.into_transport_error(&self.kind)),
Err(failure) => Err(self.backend(format!(
"the plugin answered with a line that is not a {PROTOCOL} reply: {failure}: {}",
reply.trim_end()
))),
}
}
fn request(
&self,
holder: Option<u64>,
request: &PluginRequest,
) -> Result<PluginAnswer, TransportError> {
let line = serde_json::to_string(request)
.map_err(|failure| self.backend(format!("cannot render a request: {failure}")))?;
self.exchange_line(holder, &line)
}
fn unexpected(&self, answer: &PluginAnswer, asked: &str) -> TransportError {
self.backend(format!("the plugin answered {asked} with {answer:?}"))
}
fn call_append(
&self,
holder: Option<u64>,
queue: &QueueName,
record: &[u8],
) -> Result<Position, TransportError> {
let Ok(record) = std::str::from_utf8(record) else {
return Err(TransportError::NotARecord {
queue: queue.clone(),
why: "is not UTF-8, which the plugin protocol carries records as",
});
};
match self.request(
holder,
&PluginRequest::Append {
queue: queue.clone(),
record: record.to_owned(),
},
)? {
PluginAnswer::Position(position) => Ok(position),
other => Err(self.unexpected(&other, "append")),
}
}
fn call_read(
&self,
holder: Option<u64>,
queue: &QueueName,
from: Option<&Position>,
limit: usize,
) -> Result<Batch, TransportError> {
match self.request(
holder,
&PluginRequest::Read {
queue: queue.clone(),
from: from.copied(),
limit: u64::try_from(limit).unwrap_or(u64::MAX),
},
)? {
PluginAnswer::Batch { records, torn } => Ok(Batch {
records: records
.into_iter()
.map(|stored| Stored {
bytes: stored.record.into_bytes(),
after: stored.after,
})
.collect(),
torn: torn.map(|torn| TornRecord {
at: torn.at,
bytes: torn.bytes,
}),
}),
other => Err(self.unexpected(&other, "read")),
}
}
fn call_cursor(
&self,
holder: Option<u64>,
queue: &QueueName,
consumer: &ConsumerName,
) -> Result<Option<Position>, TransportError> {
match self.request(
holder,
&PluginRequest::Cursor {
queue: queue.clone(),
consumer: consumer.clone(),
},
)? {
PluginAnswer::Cursor(position) => Ok(position),
other => Err(self.unexpected(&other, "cursor")),
}
}
fn call_commit(
&self,
holder: Option<u64>,
queue: &QueueName,
consumer: &ConsumerName,
at: &Position,
) -> Result<(), TransportError> {
match self.request(
holder,
&PluginRequest::Commit {
queue: queue.clone(),
consumer: consumer.clone(),
at: *at,
},
)? {
PluginAnswer::Done => Ok(()),
other => Err(self.unexpected(&other, "commit")),
}
}
fn call_exclusive(
self: &Arc<Self>,
holding: &BTreeSet<QueueName>,
holder: u64,
queue: &QueueName,
body: &mut dyn FnMut(&dyn Transport) -> Result<(), TransportError>,
) -> Result<(), TransportError> {
if holding.contains(queue) {
let held = ProcessHeld {
process: Arc::clone(self),
holder,
holding: holding.clone(),
};
return body(&held);
}
let outermost = holding.is_empty();
{
let mut pipe = self
.pipe
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
while pipe.held_by.is_some_and(|owner| owner != holder) {
pipe = self
.released
.wait(pipe)
.unwrap_or_else(std::sync::PoisonError::into_inner);
}
let line = serde_json::to_string(&PluginRequest::BeginExclusive {
queue: queue.clone(),
})
.map_err(|failure| self.backend(format!("cannot render a request: {failure}")))?;
match self.exchange_on(&mut pipe, &line)? {
PluginAnswer::Done => pipe.held_by = Some(holder),
other => return Err(self.unexpected(&other, "begin_exclusive")),
}
}
let mut nested = holding.clone();
nested.insert(queue.clone());
let held = ProcessHeld {
process: Arc::clone(self),
holder,
holding: nested,
};
let result = body(&held);
let ended = self.request(
Some(holder),
&PluginRequest::EndExclusive {
queue: queue.clone(),
failed: result.is_err(),
},
);
if outermost {
let mut pipe = self
.pipe
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
pipe.held_by = None;
self.released.notify_all();
}
match (result, ended) {
(Err(failure), _) => Err(failure),
(Ok(()), Ok(PluginAnswer::Done)) => Ok(()),
(Ok(()), Ok(other)) => Err(self.unexpected(&other, "end_exclusive")),
(Ok(()), Err(failure)) => Err(failure),
}
}
fn call_fingerprint(
&self,
holder: Option<u64>,
queue: &QueueName,
) -> Result<Fingerprint, TransportError> {
match self.request(
holder,
&PluginRequest::Fingerprint {
queue: queue.clone(),
},
)? {
PluginAnswer::Fingerprint(fingerprint) => Ok(fingerprint),
other => Err(self.unexpected(&other, "fingerprint")),
}
}
fn call_wait_once(
&self,
holder: Option<u64>,
queue: &QueueName,
since: &Fingerprint,
timeout: Duration,
) -> Result<Changed, TransportError> {
match self.request(
holder,
&PluginRequest::WaitForChange {
queue: queue.clone(),
since: since.clone(),
timeout_ms: u64::try_from(timeout.min(REMOTE_WAIT).as_millis()).unwrap_or(u64::MAX),
},
)? {
PluginAnswer::Moved(fingerprint) => Ok(Changed::Moved(fingerprint)),
PluginAnswer::Unchanged(fingerprint) => Ok(Changed::Unchanged(fingerprint)),
other => Err(self.unexpected(&other, "wait_for_change")),
}
}
fn call_wait(
&self,
holder: Option<u64>,
queue: &QueueName,
since: &Fingerprint,
timeout: Duration,
) -> Result<Changed, TransportError> {
let deadline = std::time::Instant::now().checked_add(timeout);
loop {
let left = deadline.map_or(Duration::MAX, |deadline| {
deadline.saturating_duration_since(std::time::Instant::now())
});
match self.call_wait_once(holder, queue, since, left)? {
Changed::Unchanged(now) if !left.is_zero() && left > REMOTE_WAIT => {
let _ = now;
}
answered => return Ok(answered),
}
}
}
fn call_document(
&self,
holder: Option<u64>,
queue: &QueueName,
name: &DocumentName,
) -> Result<Option<Vec<u8>>, TransportError> {
match self.request(
holder,
&PluginRequest::Document {
queue: queue.clone(),
name: name.clone(),
},
)? {
PluginAnswer::Document(text) => Ok(text.map(String::into_bytes)),
other => Err(self.unexpected(&other, "document")),
}
}
fn call_replace(
&self,
holder: Option<u64>,
queue: &QueueName,
name: &DocumentName,
bytes: &[u8],
) -> Result<(), TransportError> {
let Ok(text) = std::str::from_utf8(bytes) else {
return Err(self.backend(format!(
"document {name} is not UTF-8, which the plugin protocol carries documents as"
)));
};
match self.request(
holder,
&PluginRequest::ReplaceDocument {
queue: queue.clone(),
name: name.clone(),
bytes: text.to_owned(),
},
)? {
PluginAnswer::Done => Ok(()),
other => Err(self.unexpected(&other, "replace_document")),
}
}
}
impl Drop for PluginProcess {
fn drop(&mut self) {
let pipe = self
.pipe
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner);
drop(pipe.stdin.take());
let _ = pipe.child.wait();
}
}
#[derive(Debug, Clone)]
pub struct ProcessTransport(Arc<PluginProcess>);
impl ProcessTransport {
pub fn spawn(path: &Path, config: &TransportConfig) -> Result<Self, TransportError> {
PluginProcess::spawn(path, config).map(|process| Self(Arc::new(process)))
}
#[must_use]
pub fn kind(&self) -> &str {
&self.0.kind
}
}
impl Transport for ProcessTransport {
fn append(&self, queue: &QueueName, record: &[u8]) -> Result<Position, TransportError> {
self.0.call_append(None, queue, record)
}
fn read(
&self,
queue: &QueueName,
from: Option<&Position>,
limit: usize,
) -> Result<Batch, TransportError> {
self.0.call_read(None, queue, from, limit)
}
fn cursor(
&self,
queue: &QueueName,
consumer: &ConsumerName,
) -> Result<Option<Position>, TransportError> {
self.0.call_cursor(None, queue, consumer)
}
fn commit(
&self,
queue: &QueueName,
consumer: &ConsumerName,
at: &Position,
) -> Result<(), TransportError> {
self.0.call_commit(None, queue, consumer, at)
}
fn exclusive(
&self,
queue: &QueueName,
body: &mut dyn FnMut(&dyn Transport) -> Result<(), TransportError>,
) -> Result<(), TransportError> {
let holder = self.0.holders.fetch_add(1, Ordering::Relaxed) + 1;
self.0.call_exclusive(&BTreeSet::new(), holder, queue, body)
}
fn fingerprint(&self, queue: &QueueName) -> Result<Fingerprint, TransportError> {
self.0.call_fingerprint(None, queue)
}
fn wait_for_change(
&self,
queue: &QueueName,
since: &Fingerprint,
timeout: Duration,
) -> Result<Changed, TransportError> {
self.0.call_wait(None, queue, since, timeout)
}
fn document(
&self,
queue: &QueueName,
name: &DocumentName,
) -> Result<Option<Vec<u8>>, TransportError> {
self.0.call_document(None, queue, name)
}
fn replace_document(
&self,
queue: &QueueName,
name: &DocumentName,
bytes: &[u8],
) -> Result<(), TransportError> {
self.0.call_replace(None, queue, name, bytes)
}
}
struct ProcessHeld {
process: Arc<PluginProcess>,
holder: u64,
holding: BTreeSet<QueueName>,
}
impl Transport for ProcessHeld {
fn append(&self, queue: &QueueName, record: &[u8]) -> Result<Position, TransportError> {
self.process.call_append(Some(self.holder), queue, record)
}
fn read(
&self,
queue: &QueueName,
from: Option<&Position>,
limit: usize,
) -> Result<Batch, TransportError> {
self.process
.call_read(Some(self.holder), queue, from, limit)
}
fn cursor(
&self,
queue: &QueueName,
consumer: &ConsumerName,
) -> Result<Option<Position>, TransportError> {
self.process.call_cursor(Some(self.holder), queue, consumer)
}
fn commit(
&self,
queue: &QueueName,
consumer: &ConsumerName,
at: &Position,
) -> Result<(), TransportError> {
self.process
.call_commit(Some(self.holder), queue, consumer, at)
}
fn exclusive(
&self,
queue: &QueueName,
body: &mut dyn FnMut(&dyn Transport) -> Result<(), TransportError>,
) -> Result<(), TransportError> {
self.process
.call_exclusive(&self.holding, self.holder, queue, body)
}
fn fingerprint(&self, queue: &QueueName) -> Result<Fingerprint, TransportError> {
self.process.call_fingerprint(Some(self.holder), queue)
}
fn wait_for_change(
&self,
queue: &QueueName,
since: &Fingerprint,
timeout: Duration,
) -> Result<Changed, TransportError> {
self.process
.call_wait(Some(self.holder), queue, since, timeout)
}
fn document(
&self,
queue: &QueueName,
name: &DocumentName,
) -> Result<Option<Vec<u8>>, TransportError> {
self.process.call_document(Some(self.holder), queue, name)
}
fn replace_document(
&self,
queue: &QueueName,
name: &DocumentName,
bytes: &[u8],
) -> Result<(), TransportError> {
self.process
.call_replace(Some(self.holder), queue, name, bytes)
}
}