0

I tried the example of actix-multipart with actix-web v3.3.2 and actix-multipart v0.3.0.

For a minimal example,

use actix_multipart::Multipart;
use actix_web::{post, web, App, HttpResponse, HttpServer};
use futures::{StreamExt, TryStreamExt};

#[post("/")]
async fn save_file(mut payload: Multipart) -> HttpResponse {
    while let Ok(Some(mut field)) = payload.try_next().await {
        let content_type = field.content_disposition().unwrap();
        let filename = content_type.get_filename().unwrap();
        println!("filename = {}", filename);

        while let Some(chunk) = field.next().await {
            let data = chunk.unwrap();
            println!("Read a chunk.");
        }
        println!("Done");
    }
    HttpResponse::Ok().finish()
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(save_file))
        .bind("0.0.0.0:8080")?
        .run()
        .await
}

This works well, but I want to do with form data asynchronously. So I tried instead:

use actix_multipart::Multipart;
use actix_web::{post, web, App, HttpResponse, HttpServer};
use futures::{StreamExt, TryStreamExt};

#[post("/")]
async fn save_file(mut payload: Multipart) -> HttpResponse {
    actix_web::rt::spawn(async move {
        while let Ok(Some(mut field)) = payload.try_next().await {
            let content_type = field.content_disposition().unwrap();
            let filename = content_type.get_filename().unwrap();
            println!("filename = {}", filename);

            while let Some(chunk) = field.next().await {
                let data = chunk.unwrap();
                println!("Read a chunk.");
            }
            println!("Done");
        }
    });
    HttpResponse::Ok().finish()
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(save_file))
        .bind("0.0.0.0:8080")?
        .run()
        .await
}

(Added actix_web::rt::spawn to save_file.)

But this did not work -- the message "Done" never printed. The number of "Read a chunk" displayed in the second case was less than the first case, so I guess that field.next().await cannot terminate for some reason before completing reading all data.

I do not know so much about asynchronous programming, so I am not sure why field.next() did not work in actix_web::rt::spawn.

My question are: why is it, and how can I do with actix_web::rt::spawn?

naughie
  • 315
  • 2
  • 14
  • Your first code is already asynchronous. OTOH your second code sends the `OK` response before it has finished reading the incoming data, which may prompt the sender to stop sending it assuming they haven't finished yet. – Jmb Feb 12 '21 at 12:39
  • Sorry, I mean that "return a response, and then handle the form data." Ah, got it. So I should first get data, await, then return response, and finally handle data, right? – naughie Feb 12 '21 at 12:49

1 Answers1

2

When you make this call:

actix_web::rt::spawn(async move {
    // do things...
});

spawn returns a JoinHandle which is used to poll the task. When you drop that handle (by not binding it to anything), the task is "detached", i.e., it runs in the background.

The actix documentation is not particularly helpful here, but actix uses the tokio runtime under the hood. A key issue is that in tokio, spawned tasks are not guaranteed to complete. The executor needs to know, somehow, that it should perform work on that future. In your second example, the spawned task is never .awaited, nor does it communicate with any other task via channels.

Most likely, the spawned task is never polled and does not make any progress. In order to ensure that it completes, you can either .await the JoinHandle (which will drive the task to completion) or .await some other Future that depends on work in the spawned task (usually by using a channel).


As for your more general goal, the work is already being performed asynchronously! Most likely, actix is doing roughly what you tried to do in your second example: upon receiving a request, it spawns a task to handle the request and polls it repeatedly (as well as the other active requests) until it completes, then sends a response.

Mac O'Brien
  • 2,407
  • 18
  • 22