0

How do you access a crossbeam channel "send" from every actix-ws callback?

This is a version of this asked on a specific example with a beautiful MRE.

Specifically, I've made as few changes as possible to the actix-ws example server to keep it neat + simple.

The exact problem is to access "send" on the first commented line ('// use "send"...')


use actix_web::{middleware::Logger, web, App, Error, HttpRequest, HttpResponse, HttpServer};
use actix_ws::Message;
use futures_util::StreamExt;

async fn ws(req: HttpRequest, body: web::Payload, send: crossbeam::channel::Sender<u32>) -> Result<HttpResponse, Error> {
    // use "send", possible cloned

    let (response, mut session, mut msg_stream) = actix_ws::handle(&req, body)?;

    actix_rt::spawn(async move {
        while let Some(Ok(msg)) = msg_stream.next().await {
            match msg {
                Message::Ping(bytes) => {
                    if session.pong(&bytes).await.is_err() {
                        return;
                    }
                }
                Message::Text(s) => println!("Got text, {}", s),
                _ => break,
            }
        }

        let _ = session.close(None).await;
    });
    
    Ok(response)
}

#[actix_web::main]
async fn main() -> Result<(), anyhow::Error> {
    let (send, recv) = crossbeam::channel::unbounded();

    HttpServer::new(move || {
        App::new()
            .wrap(Logger::default())
            .route("/ws", web::get().to(|req: HttpRequest, body: web::Payload| {
                ws(req, body, send);
            }))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await?;

    Ok(())
}

Error:

error[E0277]: the trait bound `[closure@src/file:35:41: 35:79]: Handler<_>` is not satisfied
   --> src/file:35:41
    |
35  |               .route("/ws", web::get().to(|req: HttpRequest, body: web::Payload| {
    |  ______________________________________--_^
    | |                                      |
    | |                                      required by a bound introduced by this call
36  | |                 ws(req, body, send);
37  | |             }))
    | |_____________^ the trait `Handler<_>` is not implemented for closure `[closure@src/fuck-so.rs:35:41: 35:79]`
    |
note: required by a bound in `Route::to`
Herohtar
  • 5,347
  • 4
  • 31
  • 41
Test
  • 962
  • 9
  • 26

1 Answers1

2

A few issues are happening here:

  1. Your posted error is because your closure doesn't return anything and returning () isn't a valid response for a Handler. However, all you need to do is remove the ; after ws(...).

  2. The closure must not reference the local variable send because handlers must be 'static. You can fix that by using the move keyword so any captured variables are moved into your closure:

    web::get().to(move |req: HttpRequest, body:
               // ^^^^
    
  3. The closure for HttpServer::new() can be called multiple times since its constrained by Fn. In this case, we've already moved send in but we need to also move it out. You can do this by .clone()-ing it within the closure (fortunately crossbeam Senders are cheap to clone):

    HttpServer::new(move || {
        let send = send.clone(); // need to make a new copy to move into the route handler
        App::new(...
    
  4. Again, in your route closure, you can't move the variable by passing it to ws(...) since it needs to be called multiple times. In other situations you could just pass by reference, Sender doesn't require ownership to do anything, but because async functions return Futures that capture their arguments and functions are not allowed to return values that reference their captures, you'll need to .clone() it anyway:

    ws(req, body, send.clone())
                   // ^^^^^^^^
    

With those changes, your code compiles. Here's the full fix:

use actix_web::{middleware::Logger, web, App, Error, HttpRequest, HttpResponse, HttpServer};
use actix_ws::Message;
use futures_util::StreamExt;

async fn ws(
    req: HttpRequest,
    body: web::Payload,
    send: crossbeam::channel::Sender<u32>,
) -> Result<HttpResponse, Error> {
    // use "send", possible cloned

    let (response, mut session, mut msg_stream) = actix_ws::handle(&req, body)?;

    actix_rt::spawn(async move {
        while let Some(Ok(msg)) = msg_stream.next().await {
            match msg {
                Message::Ping(bytes) => {
                    if session.pong(&bytes).await.is_err() {
                        return;
                    }
                }
                Message::Text(s) => println!("Got text, {}", s),
                _ => break,
            }
        }

        let _ = session.close(None).await;
    });

    Ok(response)
}

#[actix_web::main]
async fn main() -> Result<(), anyhow::Error> {
    let (send, recv) = crossbeam::channel::unbounded();

    HttpServer::new(move || {
        let send = send.clone();
        App::new().wrap(Logger::default()).route(
            "/ws",
            web::get().to(move |req: HttpRequest, body: web::Payload| ws(req, body, send.clone())),
        )
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await?;

    Ok(())
}
kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • I thought that `move` worked by copying things that can be copied. `send` can't be copied. How does this work? Specifically, the send is getting accessed from many different contexts created by `move |req: Http...`. These might be running at the same time. How can there be multiple instances of `send`. Or does this work only because the closure is not async? – Test Sep 26 '22 at 06:05
  • A `move` works by *moving*. You might be confused because, if you were to try to use `send` after moving it elsewhere, the compiler may complain "`send` has type ___ which does not implement the `Copy` trait". However, that is because `Copy` types are a special case where they are always *copied* instead of *moved* (where the only distinction is you can use the variable after it was "moved"). – kmdreko Sep 26 '22 at 06:10
  • And its not the same `send` in many contexts since it is cloned many times. And even if multiple things do use the same instance at once, `Sender` implements `Sync` which makes that ok. – kmdreko Sep 26 '22 at 06:12
  • Where does send "go" when it is moved? The closure can be called many times, creating many closure environments, each with an instance of "send". Is "send" duplicated many times? How can it be duplicated without a call to `clone`? (The call to `clone` later is after the closure environment is already running with its own instance of "send"). In particular, multiple closure environments can be running at the same time; which ones gets the original "send"? Makes sense that "send" implements `Sync`, but can't you do other things with a closure that makes 2 things to try to use it simultaneously? – Test Sep 26 '22 at 06:14
  • Specifically, would this fail to compile if `Sync` weren't implemented? `Sync` means it ok's to share references, but where is a reference to "send" being created? I thought it was being "moved"? – Test Sep 26 '22 at 06:17
  • Also, would this fail if the closure were async? – Test Sep 26 '22 at 06:27
  • It is first *moved* into the closure passed to `HttpServer::new()`. That may be called many times, but that doesn't mean it needs a new `send` each time, the same closure with the same `send` is invoked each time. The closure passed to `to()` *is* created multiple times, once for each time the outer closure is called, but a cloned `send` is *moved* into each one. No duplicates are made without an explicit `.clone()`. – kmdreko Sep 26 '22 at 06:29
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/248342/discussion-between-kmdreko-and-test). – kmdreko Sep 26 '22 at 06:30
  • *Where does send "go" when it is moved?* - It goes into the closure you created. Calling the closure doesn't create a new environment, it uses the existing one, created at the time the closure itself was created. So "send" is not duplicated on every call, only when the closure is created. – user4815162342 Sep 26 '22 at 07:58