use std::time::Duration;
use crate::dispatch::DeferredTerminalReason;
use crate::dispatch::IngressRequestView;
use crate::dispatch::RemotingRequest;
use crate::dispatch::RemotingResponse;
use crate::dispatch::RequestId;
use crate::dispatch::ResponseAction;
use crate::dispatch::ResponseBodyKind;
use crate::dispatch::ResponseCompletionOutcome;
use crate::dispatch::ResponseOperationalFailure;
use crate::dispatch::ResponseReceipt;
use crate::dispatch::WriteProgress;
use crate::request_ordering::RequestOrdering;
#[must_use]
#[derive(Debug, Default)]
#[allow(
clippy::large_enum_variant,
reason = "the public contract requires direct affine RemotingResponse ownership without indirection"
)]
pub enum RejectRequestDecision {
#[default]
Proceed,
Reject(RemotingResponse),
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ResponseWritePath {
Inline,
Deferred,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ResponseObservationMode {
Inline,
Deferred,
NoResponse,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ResponseObservationOutcome {
Written(ResponseReceipt),
Oneway,
ProtocolNoResponse,
Cancelled(DeferredTerminalReason),
Failed {
completion: Option<ResponseCompletionOutcome>,
progress: Option<WriteProgress>,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ResponseMetadata {
request_id: RequestId,
original_code: i32,
response_code: Option<i32>,
body_kind: Option<ResponseBodyKind>,
mode: ResponseObservationMode,
outcome: ResponseObservationOutcome,
}
impl ResponseMetadata {
pub(crate) const fn new(
request_id: RequestId,
original_code: i32,
response_code: Option<i32>,
body_kind: Option<ResponseBodyKind>,
mode: ResponseObservationMode,
outcome: ResponseObservationOutcome,
) -> Self {
Self {
request_id,
original_code,
response_code,
body_kind,
mode,
outcome,
}
}
#[must_use]
pub const fn request_id(self) -> RequestId {
self.request_id
}
#[must_use]
pub const fn original_code(self) -> i32 {
self.original_code
}
#[must_use]
pub const fn response_code(self) -> Option<i32> {
self.response_code
}
#[must_use]
pub const fn body_kind(self) -> Option<ResponseBodyKind> {
self.body_kind
}
#[must_use]
pub const fn mode(self) -> ResponseObservationMode {
self.mode
}
#[must_use]
pub const fn outcome(self) -> ResponseObservationOutcome {
self.outcome
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ResponseObservation {
metadata: ResponseMetadata,
write_elapsed: Option<Duration>,
end_to_end_elapsed: Duration,
}
impl ResponseObservation {
pub(crate) const fn new(
metadata: ResponseMetadata,
write_elapsed: Option<Duration>,
end_to_end_elapsed: Duration,
) -> Self {
Self {
metadata,
write_elapsed,
end_to_end_elapsed,
}
}
#[must_use]
pub const fn metadata(self) -> ResponseMetadata {
self.metadata
}
#[must_use]
pub const fn write_elapsed(self) -> Option<Duration> {
self.write_elapsed
}
#[must_use]
pub const fn end_to_end_elapsed(self) -> Duration {
self.end_to_end_elapsed
}
#[must_use]
pub fn write_projection(self) -> Option<ResponseWriteObservation> {
let response_code = self.metadata.response_code?;
let body_kind = self.metadata.body_kind?;
let path = match self.metadata.mode {
ResponseObservationMode::Inline => ResponseWritePath::Inline,
ResponseObservationMode::Deferred => ResponseWritePath::Deferred,
ResponseObservationMode::NoResponse => return None,
};
let outcome = match self.metadata.outcome {
ResponseObservationOutcome::Written(receipt) => ResponseWriteOutcome::Written(receipt),
ResponseObservationOutcome::Failed { completion, progress } => {
ResponseWriteOutcome::Failed { completion, progress }
}
ResponseObservationOutcome::Oneway
| ResponseObservationOutcome::ProtocolNoResponse
| ResponseObservationOutcome::Cancelled(_) => return None,
};
Some(ResponseWriteObservation {
request_id: self.metadata.request_id,
original_code: self.metadata.original_code,
response_code,
body_kind,
path,
write_elapsed: self.write_elapsed.unwrap_or(Duration::ZERO),
end_to_end_elapsed: self.end_to_end_elapsed,
outcome,
})
}
pub(crate) fn from_write_result(
request_id: RequestId,
original_code: i32,
response_code: i32,
body_kind: ResponseBodyKind,
path: ResponseWritePath,
write_elapsed: Duration,
end_to_end_elapsed: Duration,
result: &Result<ResponseCompletionOutcome, ResponseOperationalFailure>,
) -> Self {
let mode = match path {
ResponseWritePath::Inline => ResponseObservationMode::Inline,
ResponseWritePath::Deferred => ResponseObservationMode::Deferred,
};
let outcome = match result {
Ok(ResponseCompletionOutcome::Completed(receipt)) => ResponseObservationOutcome::Written(*receipt),
Ok(outcome) => ResponseObservationOutcome::Failed {
completion: Some(*outcome),
progress: response_completion_progress(*outcome),
},
Err(error) => ResponseObservationOutcome::Failed {
completion: None,
progress: Some(error.write_progress()),
},
};
Self::new(
ResponseMetadata::new(
request_id,
original_code,
Some(response_code),
Some(body_kind),
mode,
outcome,
),
Some(write_elapsed),
end_to_end_elapsed,
)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ResponseWriteOutcome {
Written(ResponseReceipt),
Failed {
completion: Option<ResponseCompletionOutcome>,
progress: Option<WriteProgress>,
},
}
impl ResponseWriteOutcome {
#[cfg(test)]
fn from_result(result: Result<ResponseCompletionOutcome, ResponseOperationalFailure>) -> Self {
match result {
Ok(ResponseCompletionOutcome::Completed(receipt)) => Self::Written(receipt),
Ok(outcome) => Self::Failed {
completion: Some(outcome),
progress: response_completion_progress(outcome),
},
Err(error) => Self::Failed {
completion: None,
progress: Some(error.write_progress()),
},
}
}
}
const fn response_completion_progress(outcome: ResponseCompletionOutcome) -> Option<WriteProgress> {
match outcome {
ResponseCompletionOutcome::Completed(_) | ResponseCompletionOutcome::AlreadyCompleted(_) => None,
ResponseCompletionOutcome::DeadlineExpired
| ResponseCompletionOutcome::Cancelled
| ResponseCompletionOutcome::SessionClosed
| ResponseCompletionOutcome::QueueSaturated => Some(WriteProgress::NotStarted),
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ResponseWriteObservation {
request_id: RequestId,
original_code: i32,
response_code: i32,
body_kind: ResponseBodyKind,
path: ResponseWritePath,
write_elapsed: Duration,
end_to_end_elapsed: Duration,
outcome: ResponseWriteOutcome,
}
impl ResponseWriteObservation {
#[cfg(test)]
pub(crate) fn from_result(
request_id: RequestId,
original_code: i32,
response_code: i32,
body_kind: ResponseBodyKind,
path: ResponseWritePath,
write_elapsed: Duration,
end_to_end_elapsed: Duration,
result: Result<ResponseCompletionOutcome, ResponseOperationalFailure>,
) -> Self {
Self {
request_id,
original_code,
response_code,
body_kind,
path,
write_elapsed,
end_to_end_elapsed,
outcome: ResponseWriteOutcome::from_result(result),
}
}
#[must_use]
pub const fn request_id(&self) -> RequestId {
self.request_id
}
#[must_use]
pub const fn original_code(&self) -> i32 {
self.original_code
}
#[must_use]
pub const fn response_code(&self) -> i32 {
self.response_code
}
#[must_use]
pub const fn body_kind(&self) -> ResponseBodyKind {
self.body_kind
}
#[must_use]
pub const fn path(&self) -> ResponseWritePath {
self.path
}
#[must_use]
pub const fn write_elapsed(&self) -> Duration {
self.write_elapsed
}
#[must_use]
pub const fn end_to_end_elapsed(&self) -> Duration {
self.end_to_end_elapsed
}
#[must_use]
pub const fn outcome(&self) -> ResponseWriteOutcome {
self.outcome
}
}
const fn default_request_ordering() -> RequestOrdering {
RequestOrdering::Concurrent
}
#[trait_variant::make(RequestProcessor: Send)]
pub trait LocalRequestProcessor {
async fn process(&mut self, request: &mut RemotingRequest) -> Result<ResponseAction, rocketmq_error::SharedError>;
fn reject_request(&self, _code: i32) -> RejectRequestDecision {
RejectRequestDecision::Proceed
}
fn request_ordering(&self, _ingress: IngressRequestView<'_>) -> RequestOrdering {
default_request_ordering()
}
fn observe_response(&self, _observation: ResponseObservation) {}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use super::*;
use crate::dispatch::ResponseBody;
use crate::dispatch::ResponseDisposition;
use crate::dispatch::ResponseTerminalState;
struct LocalFutureProcessor;
impl LocalRequestProcessor for LocalFutureProcessor {
async fn process(
&mut self,
_request: &mut RemotingRequest,
) -> Result<ResponseAction, rocketmq_error::SharedError> {
let local = Rc::new(());
std::future::ready(()).await;
drop(local);
Err(crate::error_helpers::argument_invalid())
}
}
fn assert_local_processor<T: LocalRequestProcessor>() {}
#[test]
fn local_processor_accepts_a_non_send_future_and_defaults_are_low_cost() {
assert_local_processor::<LocalFutureProcessor>();
let processor = LocalFutureProcessor;
assert!(matches!(processor.reject_request(39), RejectRequestDecision::Proceed));
assert_eq!(default_request_ordering(), RequestOrdering::Concurrent);
let request_id = RequestId::real(7, 9).expect("real request identity");
let observation = ResponseObservation::from_write_result(
request_id,
39,
0,
ResponseBodyKind::Empty,
ResponseWritePath::Inline,
Duration::from_millis(2),
Duration::from_millis(5),
&Ok(ResponseCompletionOutcome::Completed(ResponseReceipt::new(
request_id,
ResponseDisposition::TransportWritten,
))),
);
processor.observe_response(observation);
}
#[test]
fn rejection_moves_the_owned_remoting_response() {
let response = RemotingResponse::bytes(
rocketmq_protocol::protocol::remoting_command::RemotingCommand::create_response_command_with_code(7),
bytes::Bytes::from_static(b"owned response"),
)
.expect("remoting response");
let ResponseBody::Bytes(body) = response.test_body() else {
panic!("byte response should retain contiguous storage");
};
let pointer = body.as_ptr();
let decision = RejectRequestDecision::Reject(response);
let RejectRequestDecision::Reject(response) = decision else {
panic!("rejection should retain its response");
};
let ResponseBody::Bytes(body) = response.test_body() else {
panic!("rejection should retain contiguous storage");
};
assert_eq!(body.as_ptr(), pointer);
assert_eq!(response.body_kind(), ResponseBodyKind::Bytes);
assert_eq!(response.body_len(), 14);
}
#[test]
fn observation_maps_written_receipts_without_retaining_response_payloads() {
let request_id = RequestId::real(11, 13).expect("real request identity");
let receipt = ResponseReceipt::new(request_id, ResponseDisposition::InProcessAccepted);
let observation = ResponseWriteObservation::from_result(
request_id,
220,
17,
ResponseBodyKind::Segments,
ResponseWritePath::Deferred,
Duration::from_millis(3),
Duration::from_millis(8),
Ok(ResponseCompletionOutcome::Completed(receipt)),
);
assert_eq!(observation.request_id(), request_id);
assert_eq!(observation.original_code(), 220);
assert_eq!(observation.response_code(), 17);
assert_eq!(observation.body_kind(), ResponseBodyKind::Segments);
assert_eq!(observation.path(), ResponseWritePath::Deferred);
assert_eq!(observation.write_elapsed(), Duration::from_millis(3));
assert_eq!(observation.end_to_end_elapsed(), Duration::from_millis(8));
assert_eq!(observation.outcome(), ResponseWriteOutcome::Written(receipt));
}
#[test]
fn observation_maps_failure_kind_and_progress_without_the_source_error() {
let request_id = RequestId::real(17, 19).expect("real request identity");
let observation = ResponseWriteObservation::from_result(
request_id,
40,
1,
ResponseBodyKind::FileRegions,
ResponseWritePath::Inline,
Duration::from_micros(7),
Duration::from_micros(11),
Err(ResponseOperationalFailure::Transport {
progress: WriteProgress::PossiblyPartial,
source: crate::error_helpers::connection_failed(
crate::error_helpers::TransportStage::Write,
std::io::Error::other("transport source must not be retained"),
),
}),
);
assert_eq!(
observation.outcome(),
ResponseWriteOutcome::Failed {
completion: None,
progress: Some(WriteProgress::PossiblyPartial),
}
);
assert!(!format!("{observation:?}").contains("transport source must not be retained"));
let already_completed = ResponseWriteObservation::from_result(
request_id,
40,
1,
ResponseBodyKind::Empty,
ResponseWritePath::Inline,
Duration::ZERO,
Duration::ZERO,
Ok(ResponseCompletionOutcome::AlreadyCompleted(
ResponseTerminalState::Completed,
)),
);
assert_eq!(
already_completed.outcome(),
ResponseWriteOutcome::Failed {
completion: Some(ResponseCompletionOutcome::AlreadyCompleted(
ResponseTerminalState::Completed,
)),
progress: None,
}
);
}
}