use std::fmt;
use futures::Stream;
use tokio::sync::oneshot;
use super::{
AFTER_SHUTDOWN_REQUEST_TIMEOUT, DEFAULT_TIMEOUT, MISS_TIMEOUT, ack_or_unsupported, promptly,
within,
};
use crate::conformance::harness::{expect_next, on_foreign_runtime};
use crate::conformance::helpers::unique_subject;
use crate::conformance::lifecycle::shutdown_within;
use crate::{
Broker, Connected, HeaderMap, IncomingMessage, OutgoingMessage, Publisher, RequestReply,
Subscriber, SubscriptionSource,
};
pub async fn request_reply<B, MkBroker, Src, MkSrc, Req, MkReq, Pub, MkPub>(
make_broker: MkBroker,
make_source: MkSrc,
make_requester: MkReq,
make_publisher: MkPub,
) where
B: Broker,
MkBroker: Fn() -> B,
Src: SubscriptionSource<Connected<B>> + Send,
Src::Subscriber: Send,
MkSrc: Fn(&str) -> Src,
Req: RequestReply + 'static,
MkReq: Fn(&Connected<B>) -> Req,
Pub: Publisher,
MkPub: Fn(&Connected<B>) -> Pub,
{
let subject = unique_subject("conformance.request_reply");
let unanswered_subject = format!("{subject}.void");
let connected = within(make_broker().connect(), "request_reply: connect")
.await
.expect("broker must connect");
let mut responder = within(
make_source(&subject).subscribe(&connected),
"request_reply: subscribe",
)
.await
.expect("responder subscription must open after connect");
let publisher = make_publisher(&connected);
let requester = make_requester(&connected);
let mut inbox = std::pin::pin!(responder.stream());
let requester = requests_across_runtimes(requester, &publisher, &mut inbox, &subject).await;
late_reply_resolves_no_later_request(&requester, &publisher, &mut inbox, &subject).await;
concurrent_requests_get_their_own_replies(&requester, &publisher, &mut inbox, &subject).await;
let unanswered = within(
requester.request(
OutgoingMessage::new(&unanswered_subject, b"ping".as_slice()),
MISS_TIMEOUT,
),
"request_reply: a request nobody answers",
)
.await;
assert!(
unanswered.is_err(),
"a request nobody answers must fail once its timeout elapses",
);
shutdown_within(connected, "request_reply").await;
let after_shutdown = promptly(
requester.request(
OutgoingMessage::new(&subject, b"ping".as_slice()),
AFTER_SHUTDOWN_REQUEST_TIMEOUT,
),
"request_reply: a request after shutdown",
)
.await;
assert!(
after_shutdown.is_err(),
"request_reply: a request through a requester that outlived the shutdown resolved; it \
must return an error, never succeed against the dead connection",
);
}
async fn requests_across_runtimes<Req, Pub, S, M, E>(
requester: Req,
publisher: &Pub,
inbox: &mut S,
subject: &str,
) -> Req
where
Req: RequestReply + 'static,
Pub: Publisher,
S: Stream<Item = Result<M, E>> + Unpin,
M: IncomingMessage,
E: fmt::Debug,
{
let respond = async {
for label in [
"request_reply responder, the request from another runtime",
"request_reply responder, the request after that runtime stopped",
] {
let msg = expect_next(&mut *inbox, label).await;
assert_eq!(
msg.payload(),
b"ping",
"responder must receive the request payload"
);
answer(publisher, &msg, b"pong".as_slice())
.await
.expect("reply publish failed");
ack_or_unsupported(msg, label).await;
}
};
let requests = async {
let first = subject.to_owned();
let requester = on_foreign_runtime(async move || {
let reply = within(
requester.request(
OutgoingMessage::new(&first, b"ping".as_slice()),
DEFAULT_TIMEOUT,
),
"request_reply: the request from another runtime",
)
.await
.expect("request must resolve once the responder replies");
assert_eq!(
reply.payload(),
b"pong",
"the correlated reply must carry the responder payload"
);
settle_reply(reply).await;
requester
})
.await;
let reply = within(
requester.request(
OutgoingMessage::new(subject, b"ping".as_slice()),
DEFAULT_TIMEOUT,
),
"request_reply: the request after that runtime stopped",
)
.await
.expect(
"a request must resolve after an earlier one was made from a runtime that has \
stopped; the broker must run its internal tasks on the runtime it connected on",
);
assert_eq!(
reply.payload(),
b"pong",
"the correlated reply must carry the responder payload"
);
settle_reply(reply).await;
requester
};
let (requester, ()) = futures::join!(requests, respond);
requester
}
async fn late_reply_resolves_no_later_request<Req, Pub, S, M, E>(
requester: &Req,
publisher: &Pub,
inbox: &mut S,
subject: &str,
) where
Req: RequestReply,
Pub: Publisher,
S: Stream<Item = Result<M, E>> + Unpin,
M: IncomingMessage,
E: fmt::Debug,
{
let (timed_out, held_back) = oneshot::channel::<()>();
let (late_sent, late_out) = oneshot::channel::<()>();
let requests = async move {
let late = within(
requester.request(
OutgoingMessage::new(subject, b"late".as_slice()),
MISS_TIMEOUT,
),
"request_reply: the request that times out",
)
.await;
assert!(
late.is_err(),
"request_reply: the responder held its reply back past the request's timeout, so the \
request must fail once {MISS_TIMEOUT:?} elapse",
);
let _ = timed_out.send(());
late_out
.await
.expect("request_reply: the responder sends the late reply before the next request");
let fresh = within(
requester.request(
OutgoingMessage::new(subject, b"fresh".as_slice()),
DEFAULT_TIMEOUT,
),
"request_reply: the request after the timeout",
)
.await
.expect("request_reply: a request after a timed-out one must resolve once answered");
assert_eq!(
fresh.payload(),
b"re:fresh",
"request_reply: a reply that arrived after its request timed out resolved the next \
request; a late reply must be discarded, never handed to another request",
);
settle_reply(fresh).await;
};
let respond = async move {
let late = expect_next(
&mut *inbox,
"request_reply responder, the request that times out",
)
.await;
held_back
.await
.expect("request_reply: the requester reports the timeout");
let _ = answer(publisher, &late, b"re:late".as_slice()).await;
ack_or_unsupported(late, "request_reply responder, the request that timed out").await;
let _ = late_sent.send(());
let fresh = expect_next(
&mut *inbox,
"request_reply responder, the request after the timeout",
)
.await;
answer(publisher, &fresh, b"re:fresh".as_slice())
.await
.expect("reply publish failed");
ack_or_unsupported(
fresh,
"request_reply responder, the request after the timeout",
)
.await;
};
futures::join!(requests, respond);
}
async fn concurrent_requests_get_their_own_replies<Req, Pub, S, M, E>(
requester: &Req,
publisher: &Pub,
inbox: &mut S,
subject: &str,
) where
Req: RequestReply,
Pub: Publisher,
S: Stream<Item = Result<M, E>> + Unpin,
M: IncomingMessage,
E: fmt::Debug,
{
let requests = async {
let (alpha, beta) = within(
async {
futures::join!(
requester.request(
OutgoingMessage::new(subject, b"alpha".as_slice()),
DEFAULT_TIMEOUT,
),
requester.request(
OutgoingMessage::new(subject, b"beta".as_slice()),
DEFAULT_TIMEOUT,
),
)
},
"request_reply: two concurrent requests",
)
.await;
for (reply, expected) in [(alpha, b"re:alpha".as_slice()), (beta, b"re:beta")] {
let reply = reply.expect(
"request_reply: each of two concurrent requests must resolve once answered",
);
assert_eq!(
reply.payload(),
expected,
"request_reply: two requests in flight at once must each resolve with the reply \
to itself",
);
settle_reply(reply).await;
}
};
let respond = async {
let first = expect_next(
&mut *inbox,
"request_reply responder, the first concurrent request",
)
.await;
let second = expect_next(
&mut *inbox,
"request_reply responder, the second concurrent request",
)
.await;
for request in [&second, &first] {
let echoed = [b"re:".as_slice(), request.payload()].concat();
answer(publisher, request, &echoed)
.await
.expect("reply publish failed");
}
ack_or_unsupported(
first,
"request_reply responder, the first concurrent request",
)
.await;
ack_or_unsupported(
second,
"request_reply responder, the second concurrent request",
)
.await;
};
futures::join!(requests, respond);
}
async fn answer<Pub, M>(publisher: &Pub, request: &M, payload: &[u8]) -> Result<(), Pub::Error>
where
Pub: Publisher,
M: IncomingMessage,
{
let reply_to = request
.headers()
.reply_to()
.expect("a request must carry a usable reply-to header")
.to_owned();
let mut headers = HeaderMap::new();
if let Some(correlation_id) = request.headers().correlation_id() {
headers.insert("correlation-id", correlation_id.to_owned());
}
within(
publisher.publish(
OutgoingMessage::new(&reply_to, payload).with_headers(headers),
None,
),
"request_reply: the responder's reply",
)
.await
}
async fn settle_reply<M: IncomingMessage>(reply: M) {
let _settled = within(reply.ack(), "request_reply: settling a reply").await;
}