use dtmrs_core::BranchResult;
use prost::bytes::{Buf, BufMut};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tonic::codec::{Codec, DecodeBuf, Decoder, EncodeBuf, Encoder};
use tonic::codegen::http::uri::PathAndQuery;
use tonic::transport::{Channel, Endpoint};
use tonic::Status;
use tracing::{info, warn};
use super::{MD_BRANCH_ID, MD_GID, MD_OP, MD_TRANS_TYPE};
use crate::registry::GrpcTarget;
#[derive(Debug, Default, Clone, Copy)]
pub struct BytesCodec;
impl Codec for BytesCodec {
type Encode = Vec<u8>;
type Decode = Vec<u8>;
type Encoder = BytesCodec;
type Decoder = BytesCodec;
fn encoder(&mut self) -> Self::Encoder {
*self
}
fn decoder(&mut self) -> Self::Decoder {
*self
}
}
impl Encoder for BytesCodec {
type Item = Vec<u8>;
type Error = Status;
fn encode(&mut self, item: Self::Item, dst: &mut EncodeBuf<'_>) -> Result<(), Self::Error> {
dst.put_slice(&item);
Ok(())
}
}
impl Decoder for BytesCodec {
type Item = Vec<u8>;
type Error = Status;
fn decode(&mut self, src: &mut DecodeBuf<'_>) -> Result<Option<Self::Item>, Self::Error> {
let mut out = vec![0u8; src.remaining()];
src.copy_to_slice(&mut out);
Ok(Some(out))
}
}
#[derive(Clone)]
pub struct GrpcCaller {
channels: Arc<Mutex<HashMap<String, Channel>>>,
timeout: Duration,
}
impl GrpcCaller {
pub fn new(timeout: Duration) -> Self {
Self {
channels: Arc::new(Mutex::new(HashMap::new())),
timeout,
}
}
fn channel(&self, endpoint: &str) -> Result<Channel, String> {
if let Some(c) = self.channels.lock().unwrap().get(endpoint) {
return Ok(c.clone());
}
let ch = Endpoint::from_shared(endpoint.to_string())
.map_err(|e| format!("gRPC 地址不合法: {e}"))?
.timeout(self.timeout)
.connect_timeout(self.timeout)
.connect_lazy();
self.channels
.lock()
.unwrap()
.insert(endpoint.to_string(), ch.clone());
Ok(ch)
}
pub async fn call(
&self,
target: &GrpcTarget,
gid: &str,
trans_type: &str,
branch_id: &str,
op: &str,
) -> BranchResult {
let channel = match self.channel(&target.endpoint) {
Ok(c) => c,
Err(e) => {
warn!(gid, branch = branch_id, endpoint = %target.endpoint, error = %e,
"gRPC 分支地址不合法,按结果未知处理(会重试,不回滚)");
return BranchResult::Unknown;
}
};
let path = match PathAndQuery::try_from(target.path.clone()) {
Ok(p) => p,
Err(e) => {
warn!(gid, branch = branch_id, path = %target.path, error = %e,
"gRPC 方法路径不合法,按结果未知处理");
return BranchResult::Unknown;
}
};
let mut grpc = tonic::client::Grpc::new(channel);
if let Err(e) = grpc.ready().await {
warn!(gid, branch = branch_id, error = %e, "gRPC 分支不可达,结果未知");
return BranchResult::Unknown;
}
let mut req = tonic::Request::new(Vec::<u8>::new());
for (k, v) in [
(MD_GID, gid),
(MD_TRANS_TYPE, trans_type),
(MD_BRANCH_ID, branch_id),
(MD_OP, op),
] {
match v.parse() {
Ok(val) => {
req.metadata_mut().insert(k, val);
}
Err(_) => {
warn!(
gid,
branch = branch_id,
key = k,
"metadata 值不合法(非 ASCII?),无法调用 gRPC 分支"
);
return BranchResult::Unknown;
}
}
}
match grpc
.unary::<Vec<u8>, Vec<u8>, BytesCodec>(req, path, BytesCodec)
.await
{
Ok(_) => {
info!(gid, branch = branch_id, op, "gRPC 分支返回 OK");
BranchResult::Success
}
Err(status) => {
let r = BranchResult::from_grpc(status.code() as i32);
info!(gid, branch = branch_id, op, code = ?status.code(), result = ?r,
"gRPC 分支返回");
r
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn 地址不合法时是未知而不是失败() {
let c = GrpcCaller::new(Duration::from_secs(1));
assert!(c.channel("这不是个地址").is_err());
}
#[tokio::test]
async fn 连不上的分支不能触发回滚() {
let c = GrpcCaller::new(Duration::from_millis(300));
let t = GrpcTarget {
endpoint: "http://127.0.0.1:1".into(),
path: "/a.B/C".into(),
};
let r = c.call(&t, "g1", "saga", "01", "action").await;
assert_eq!(r, BranchResult::Unknown, "连不上必须是未知,不能是失败");
}
#[tokio::test]
async fn channel会被缓存复用() {
let c = GrpcCaller::new(Duration::from_secs(1));
let a = c.channel("http://127.0.0.1:9").unwrap();
let b = c.channel("http://127.0.0.1:9").unwrap();
assert_eq!(c.channels.lock().unwrap().len(), 1);
drop((a, b));
}
}