Skip to content

library: don't hide errors #434

@bryanlarsen

Description

@bryanlarsen

Describe the feature

When wstunnel is used as a library and a tunnel fails, the user should get a Response::Err rather than a Response::Ok.

Describe the reason for such feature

  • ergonomics
  • being able to retry with slightly different connection parameters on failure

Currently wstunnel calls tracing::error! on most errors and then swallows the error rather than returning them to the original caller.

Describe alternatives you've considered

diff --git a/src/lib.rs b/src/lib.rs
index e05fa16..3201a88 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -38,25 +38,19 @@ use tokio::task::JoinSet;
 use tracing::{error, info};
 use url::Url;
 
-pub async fn run_client(args: Client, executor: impl TokioExecutor) -> anyhow::Result<()> {
-    let tunnels = create_client_tunnels(args, executor.clone()).await?;
-
-    // Start all tunnels
-    let (tx, rx) = oneshot::channel();
-    executor.spawn(async move {
-        let _ = JoinSet::from_iter(tunnels).join_all().await;
-        let _ = tx.send(());
-    });
+pub async fn run_client(args: Client, executor: impl TokioExecutor) -> Vec<anyhow::Result<()>> {
+    let tunnels = match create_client_tunnels(args, executor.clone()).await {
+        Ok(tunnels) => tunnels,
+        Err(err) => return vec![Err(err)],  // FIXME: this is probably unacceptable
+    };
 
-    // wait for all tunnels to finish
-    rx.await?;
-    Ok(())
+    JoinSet::from_iter(tunnels).join_all().await
 }
 
 async fn create_client_tunnels(
     args: Client,
     executor: impl TokioExecutor,
-) -> anyhow::Result<Vec<BoxFuture<'static, ()>>> {
+) -> anyhow::Result<Vec<BoxFuture<'static, anyhow::Result<()>>>> {
     let (tls_certificate, tls_key) = if let (Some(cert), Some(key)) =
         (args.tls_certificate.as_ref(), args.tls_private_key.as_ref())
     {
@@ -159,7 +153,8 @@ async fn create_client_tunnels(
     info!("Starting wstunnel client v{}", env!("CARGO_PKG_VERSION"),);
 
     // Keep track of all spawned tunnels
-    let mut tunnels: Vec<BoxFuture<()>> = Vec::with_capacity(args.remote_to_local.len() + args.local_to_remote.len());
+    let mut tunnels: Vec<BoxFuture<anyhow::Result<()>>> =
+        Vec::with_capacity(args.remote_to_local.len() + args.local_to_remote.len());
     macro_rules! spawn_tunnel {
         ( $($s:stmt);* ) => {
             tunnels.push(Box::pin(async move {
@@ -188,9 +183,7 @@ async fn create_client_tunnels(
                         host,
                         port,
                     };
-                    if let Err(err) = client.run_reverse_tunnel(remote, tcp_connector).await {
-                        error!("{:?}", err);
-                    }
+                    client.run_reverse_tunnel(remote, tcp_connector).await
                 }
             }

and so on.

A similar "print an swallow the error pattern is at https://github.com/erebe/wstunnel/blob/main/src/tunnel/client/client.rs#L127C54-L127C56 but I haven't quite figured out how to fix it yet, because of the spawn. But unlike the leak (#433), I'm fairly confident I can figure it out.

This task would likely be easier if wstunnel exported a "create a single tunnel" API. We create multiple wstunnel's with a single tunnel in each instantiation. Would you like a MR that created such an API?

I'm again willing to do the work here. And I am more confident on doing so than I was with the socket leak! However, some guidance on how you'd like the API to look would be appreciated.

Thanks.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions