How to cancel a tokio tcp connecting gracefully?

567 views Asked by At

When we connect to a remote host via tcp, it can be a time-consuming operation. And while waiting for a connection, the user may cancel the operation at any time.

When connecting using async tokio, TcpStream::connect() returns a Future<TcpStream, io::Error> object, assumed to be called tcps_ft.

There are two parts, one is the normal logic of the program, which should call .awati() on tcp_ft above, and the other part is the UI of the program, where the user wants to call drop(tcps_ft) if he clicks the cancel button. But this seems impossible to do, because both calls consume tcps_ft.

#[tokio::test]
async fn test_cancel_by_drop() {
    let addr = "192.168.1.100:8080";
    let tcps_ft = TcpStream::connect(addr);
    let mut tcps = tcps_ft.await.unwrap();

    // simulate user's operation.
    let cancel_jh = tokio::spawn(async move {
        tokio::time::sleep(Duration::from_millis(100)).await;
        drop(tcps_ft); // can not compile:: tcps_ft moved when await
    });

    // simulate user's program
    tcps.shutdown().await;
    cancel_jh.await;
}

So I considered using Task to do it, after all the Task::abort() function will not consume the atjh: Future<JoinHandle> object corresponding to this task. But I still can't call atjh.await before abort() returns synchronously, and in any case, await will consume the variable, making it impossible to call abort() asynchronously. (In other words, the call to abort() must be executed synchronously before await.)

#[tokio::test]
async fn test_cancel_by_abort() {
    let addr = "192.168.1.100:8080";
    let atjh = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });

    // simulate user's operation.
    let cancel_jh = tokio::spawn(async {
        tokio::time::sleep(Duration::from_millis(100)).await;
        &atjh.abort();
    });

    // simulate user's program
    let mut tcps = atjh.await.unwrap(); // can not compile:: atjh moved when await
    tcps.shutdown().await;
    cancel_jh.await; 
}

Of course, one less direct way is to use callback functions. In my asynchronous connection task, when connect().await returns, the user's callback function is called to notify the user to call atjh.await.

But here the callback function is introduced again, and I know await/async itself is designed to solve the callback hell problem.

Further, for user-supplied asynchronous callback functions, the compiler may impose very many requirements, such as implementing Send, avoiding cross-thread safety issues, etc. This is certainly not something that async would like to encounter.

How can I do it asynchronously and gracefully to cancel this asynchronous connection process? Is there a suggested model to handle it?

0

There are 0 answers