tonic icon indicating copy to clipboard operation
tonic copied to clipboard

Received Status.source = None when server sends Status.source = Some

Open BaxHugh opened this issue 1 year ago • 0 comments

Bug Report

Version

tonic v0.11.0 tonic-build v0.11.0

Platform

Linux 6.5.0-21-generic #21~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Feb 9 13:32:52 UTC 2 x86_64 x86_64 x86_64 GNU/Linux

Description

Received Status.source field is None even though it is Some when sent from the server.

Situation: GRPC server built with tonic-build encounters an error err and returns a tonic::Status with source: err, the client, (also built with tonic-build) receives the Status but the source is None.

I expected the source field of the received Status on the client's side to be populated with the same information that was sent from the client.

Below is a demo server and client cargo project as a zip file, the server.rs and client.rs files are also below for reference, along with their output. The server simply returns a Status where the source of the status has been set.

To run the demo to reproduce, extract the zip, from within the zip;

  • run cargo run --bin server in one terminal.
  • run cargo run --bin client in another terminal. See that the log message from the client states received error: Status {..., source: None } however the log in the server shows sending error: Status {..., source: Some(Custom ...)}

tonic_status_source_bug_demo.zip

server.rs

use std::sync::Arc;

use tonic::{transport::Server, Request, Response, Status};

pub mod error_server {
    tonic::include_proto!("error_server");
}

#[derive(Debug, Default)]
pub struct ErrorServerService {}

#[tonic::async_trait]
impl error_server::error_server_server::ErrorServer for ErrorServerService {
    /// Just return error
    async fn error_server(
        &self,
        _: Request<error_server::Request>,
    ) -> Result<Response<error_server::Response>, Status> {
        let source_error = std::io::Error::new(std::io::ErrorKind::Other, "oh no!");
        let mut status = tonic::Status::internal("error:");
        status.set_source(Arc::new(source_error));
        log::error!("sending error: {:?}", status);
        return Err(status);
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let env = env_logger::Env::default()
        .filter_or("LOG_LEVEL", "info")
        .write_style_or("LOG_STYLE", "never");
    env_logger::init_from_env(env);

    let port = std::env::var("PORT").unwrap_or_else(|_| "50051".to_string());
    let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port.parse()?));
    let error_server = ErrorServerService::default();
    log::info!("serving on {addr}");

    Server::builder()
        .add_service(error_server::error_server_server::ErrorServerServer::new(
            error_server,
        ))
        .serve(addr)
        .await?;

    Ok(())
}

client.rs

use tonic::transport::Channel;

pub mod error_server {
    tonic::include_proto!("error_server");
}

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
    let env = env_logger::Env::default()
        .filter_or("LOG_LEVEL", "info")
        .write_style_or("LOG_STYLE", "never");
    env_logger::init_from_env(env);

    let port = std::env::var("PORT").unwrap_or_else(|_| "50051".to_string());
    let endpoint = format!("https://localhost:{port}");
    // log::info!("sending requests to {host}:{port}");
    let channel = Channel::from_shared(endpoint)?.connect().await?;
    log::info!("channel connected");

    let mut client = error_server::error_server_client::ErrorServerClient::new(channel);

    let request = tonic::Request::new(error_server::Request {
        text: "Hello World".to_string(),
    });

    match client.error_server(request).await {
        Err(status) => {
            // debug print the error showing that source is None.
            log::error!("received error: {:?}", status);
            Err(status.into())
        }
        Ok(response) => {
            log::info!("received response: {:?}", response.into_inner());
            Ok(())
        }
    }
}

Server output:

[2024-03-12T16:03:28Z INFO  server] serving on 0.0.0.0:50052
[2024-03-12T16:04:20Z ERROR server] sending error: Status { code: Internal, message: "error:", source: Some(Custom { kind: Other, error: "oh no!" }) }

Client output:

[2024-03-12T16:04:20Z ERROR client] received error: Status { code: Internal, message: "error:", metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Tue, 12 Mar 2024 16:04:20 GMT", "content-length": "0"} }, source: None }
Error: status: Internal, message: "error:", details: [], metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Tue, 12 Mar 2024 16:04:20 GMT", "content-length": "0"} }

BaxHugh avatar Mar 12 '24 16:03 BaxHugh