Rust 100 Ex 🏃【36/37】 ブロッキング・非同期用の実装・キャンセル ～ラストスパート！～

前の記事

- [【0】 準備](https://qiita.com/namn1125/items/bd1b4cd028874189a3c1) ← 初回
- ...
- [【35】 非同期ランタイム・Futureトレイト ～非同期のお作法～](https://qiita.com/namn1125/items/c840d48f8f558613ec10) ← 前回
- 【36】 ブロッキング・非同期用の実装・キャンセル ～ラストスパート！～ ← 今回

<details><summary>全記事一覧</summary>

- [【0】 準備](https://qiita.com/namn1125/items/bd1b4cd028874189a3c1)
- [【1】 構文・整数・変数](https://qiita.com/namn1125/items/c776768baec89b8a0438)
- [【2】 if・パニック・演習](https://qiita.com/namn1125/items/13870965e8273bc87bce)
- [【3】 可変・ループ・オーバーフロー](https://qiita.com/namn1125/items/4f57f8aede2f5201016f)
- [【4】 キャスト・構造体 (たまにUFCS)](https://qiita.com/namn1125/items/227ed5bbedbb775c5d27)
- [【5】 バリデーション・モジュールの公開範囲 ～ → カプセル化！～](https://qiita.com/namn1125/items/46721cceccbfa9ec3c7a)
- [【6】 カプセル化の続きと所有権とセッター ～そして不変参照と可変参照！～](https://qiita.com/namn1125/items/442de7798d49f777384c)
- [【7】 スタック・ヒープと参照のサイズ ～メモリの話～](https://qiita.com/namn1125/items/780d80e4340f0e34dbf6)
- [【8】 デストラクタ(変数の終わり)・トレイト ～終わりと始まり～](https://qiita.com/namn1125/items/7247edde3b6bd0b85b25)
- [【9】 Orphan rule (孤児ルール)・演算子オーバーロード・derive ～Empowerment 💪 ～](https://qiita.com/namn1125/items/e565713b423301a80c76)
- [【10】 トレイト境界・文字列・Derefトレイト ～トレイトのアレコレ～](https://qiita.com/namn1125/items/9060faee2965f23e0e3d)
- [【11】 Sized トレイト・From トレイト・関連型 ～おもしろトレイトと関連型～](https://qiita.com/namn1125/items/b68990712d379a3d6a49)
- [【12】 Clone・Copy・Dropトレイト ～覚えるべき主要トレイトたち～](https://qiita.com/namn1125/items/c6f34945f331c7229746)
- [【13】 トレイトまとめ・列挙型・match式 ～最強のトレイトの次は、最強の列挙型～](https://qiita.com/namn1125/items/6b2cc31d6a47f9ffaff6)
- [【14】 フィールド付き列挙型とOption型 ～チョクワガタ～](https://qiita.com/namn1125/items/6fe69d1cab6c7936c698)
- [【15】 Result型 ～Rust流エラーハンドリング術～](https://qiita.com/namn1125/items/698aafaf22ec4bfd1bde)
- [【16】 Errorトレイトと外部クレート ～依存はCargo.tomlに全部お任せ！～](https://qiita.com/namn1125/items/9dae7b728fa0668e4102)
- [【17】 thiserror・TryFrom ～トレイトもResultも自由自在！～](https://qiita.com/namn1125/items/d67006a29b7ff5d6148c)
- [【18】 Errorのネスト・慣例的な書き方 ～Rustらしさの目醒め～](https://qiita.com/namn1125/items/705829027661a38e4a4f)
- [【19】 配列・動的配列 ～スタックが使われる配列と、ヒープに保存できる動的配列～](https://qiita.com/namn1125/items/9c49fd4c08f3e61896c7)
- [【20】 動的配列のリサイズ・イテレータ ～またまたトレイト登場！～](https://qiita.com/namn1125/items/6e4dede789c6de97f396)
- [【21】 イテレータ・ライフタイム ～ライフタイム注釈ようやく登場！～](https://qiita.com/namn1125/items/9a1224abf733f29a980a)
- [【22】 コンビネータ・RPIT ～ :dragon: 「 `Iterator` トレイトを実装してるやつ」～](https://qiita.com/namn1125/items/27fe20f2d60803c94357)
- [【23】 `impl Trait` ・スライス ～配列の欠片～](https://qiita.com/namn1125/items/7601d0268293ce7fcf28)
- [【24】 可変スライス・下書き構造体 ～構造体で状態表現～](https://qiita.com/namn1125/items/a9acc7dc2ca7c4c55cc3)
- [【25】 インデックス・可変インデックス ～インデックスもトレイト！～](https://qiita.com/namn1125/items/bd43375362af10ea8b42)
- [【26】 HashMap・順序・BTreeMap ～Rustの辞書型～](https://qiita.com/namn1125/items/a2806cded12fa719a1cb)
- [【27】 スレッド・'staticライフタイム ～並列処理に見るRustの恩恵～](https://qiita.com/namn1125/items/5a4a09172834063268b1)
- [【28】 リーク・スコープ付きスレッド ～ライフタイムに技あり！～](https://qiita.com/namn1125/items/34a0aacd4d40f9234aef)
- [【29】 チャネル・参照の内部可変性 ～Rustの虎の子、mpscと `Rc<RefCell<T>>`～](https://qiita.com/namn1125/items/861e72dbb4b837508948)
- [【30】 双方向通信・リファクタリング ～返信用封筒を入れよう！～](https://qiita.com/namn1125/items/46f03de967d0de46737e)
- [【31】 上限付きチャネル・PATCH機能 ～パンクしないように制御！～](https://qiita.com/namn1125/items/d7ae8a205ee969339401)
- [【32】 `Send`・排他的ロック(`Mutex`)・非対称排他的ロック(`RwLock`) ～真打 `Arc<Mutex<T>>` 登場～](https://qiita.com/namn1125/items/eee890dc8672e975e70c)
- [【33】 チャネルなしで実装・Syncの話 ～考察回です～](https://qiita.com/namn1125/items/cc8bee3871b727e564e3)
- [【34】 `async fn`・非同期タスク生成 ～Rustの非同期入門～](https://qiita.com/namn1125/items/9c32b730a283ad76c891)
- [【35】 非同期ランタイム・Futureトレイト ～非同期のお作法～](https://qiita.com/namn1125/items/c840d48f8f558613ec10)
- [【36】 ブロッキング・非同期用の実装・キャンセル ～ラストスパート！～](https://qiita.com/namn1125/items/39c139da3f1f26264353)
- [【37】 Axumでクラサバ！ ～最終回～](https://qiita.com/namn1125/items/bae492f823fbe60e9974)
- [【おまけ1】 Rustで勘違いしていたこと3選 🏄🌴 【100 Exercises To Learn Rust 🦀 完走記事 🏃】](https://qiita.com/namn1125/items/2a59f486cce30fd2b889)
- [【おまけ2】 【🙇 懺悔 🙇】Qiitanグッズ欲しさに1日に33記事投稿した話またはQiita CLIとcargo scriptを布教する的な何か](https://qiita.com/namn1125/items/8e552694b389c5b13b5e)
</details>

[100 Exercise To Learn Rust](https://rust-exercises.com/) 演習第36回になります、ついにラス前で、解説に対応した演習は今回が最後です！

今回は非同期特有の考え方や操作が中心となっているみたいですね、サクッと行きたいと思います！(事情につき3問あります)

:::note warn
8章の非同期について、全体的に筆者の理解度が低いです...他の本とかも読んでる最中...マサカリ大歓迎です！

記事は執筆時点での理解という感じです
:::

今回の関連ページ

- [8.5. Blocking the runtime](https://rust-exercises.com/08_futures/05_blocking)
- [8.6. Async-aware primitives](https://rust-exercises.com/08_futures/06_async_aware_primitives)
- [8.7. Cancellation](https://rust-exercises.com/08_futures/07_cancellation)

# [[08_futures/05_blocking](https://github.com/mainmatter/100-exercises-to-learn-rust/blob/main/exercises/08_futures/05_blocking/src/lib.rs)] ブロッキングを回避すべし

- [8.5. Blocking the runtime](https://rust-exercises.com/08_futures/05_blocking)

問題はこちらです。

```rust:lib.rs
// TODO: the `echo` server uses non-async primitives.
//  When running the tests, you should observe that it hangs, due to a
//  deadlock between the caller and the server.
//  Use `spawn_blocking` inside `echo` to resolve the issue.
use std::io::{Read, Write};
use tokio::net::TcpListener;

pub async fn echo(listener: TcpListener) -> Result<(), anyhow::Error> {
    loop {
        let (socket, _) = listener.accept().await?;
        let mut socket = socket.into_std()?;
        socket.set_nonblocking(false)?;
        let mut buffer = Vec::new();
        socket.read_to_end(&mut buffer)?;
        socket.write_all(&buffer)?;
    }
}
```

<details><summary>テストを含めた全体</summary>

```rust:lib.rs
// TODO: the `echo` server uses non-async primitives.
//  When running the tests, you should observe that it hangs, due to a
//  deadlock between the caller and the server.
//  Use `spawn_blocking` inside `echo` to resolve the issue.
use std::io::{Read, Write};
use tokio::net::TcpListener;

pub async fn echo(listener: TcpListener) -> Result<(), anyhow::Error> {
    loop {
        let (socket, _) = listener.accept().await?;
        let mut socket = socket.into_std()?;
        socket.set_nonblocking(false)?;
        let mut buffer = Vec::new();
        socket.read_to_end(&mut buffer)?;
        socket.write_all(&buffer)?;
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::net::SocketAddr;
    use std::panic;
    use tokio::io::{AsyncReadExt, AsyncWriteExt};
    use tokio::task::JoinSet;

    async fn bind_random() -> (TcpListener, SocketAddr) {
        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
        let addr = listener.local_addr().unwrap();
        (listener, addr)
    }

    #[tokio::test]
    async fn test_echo() {
        let (listener, addr) = bind_random().await;
        tokio::spawn(echo(listener));

        let requests = vec![
            "hello here we go with a long message",
            "world",
            "foo",
            "bar",
        ];
        let mut join_set = JoinSet::new();

        for request in requests {
            join_set.spawn(async move {
                let mut socket = tokio::net::TcpStream::connect(addr).await.unwrap();
                let (mut reader, mut writer) = socket.split();

                // Send the request
                writer.write_all(request.as_bytes()).await.unwrap();
                // Close the write side of the socket
                writer.shutdown().await.unwrap();

                // Read the response
                let mut buf = Vec::with_capacity(request.len());
                reader.read_to_end(&mut buf).await.unwrap();
                assert_eq!(&buf, request.as_bytes());
            });
        }

        while let Some(outcome) = join_set.join_next().await {
            if let Err(e) = outcome {
                if let Ok(reason) = e.try_into_panic() {
                    panic::resume_unwind(reason);
                }
            }
        }
    }
}
```
</details>

「ブロッキングI/Oを使用するとデッドロックすることを確かめて、 `spawn_blocking` を使って解消してください」という問題です。

## 解説

というわけで、 `spawn_blocking` を使うように書き換えましょう。

```diff_rust:lib.rs
pub async fn echo(listener: TcpListener) -> Result<(), anyhow::Error> {
    loop {
        let (socket, _) = listener.accept().await?;

+        tokio::task::spawn_blocking(|| -> anyhow::Result<()> {
            let mut socket = socket.into_std()?;
            socket.set_nonblocking(false)?;
            let mut buffer = Vec::new();
            socket.read_to_end(&mut buffer)?;
            socket.write_all(&buffer)?;

+            Ok(())
+        });
    }
}
```

どう変わったのでしょうか...？ブロッキングとノンブロッキングバージョンでどう違うのでしょう...？解説を試みてみます。

非同期処理でブロックしないとは、「 `.await` でこまめに主導権をランタイムに返す」ということです。もし長時間ブロックするスレッドがあり、ずっと `.await` してくれない場合、tokioランタイムにはそのタスクをどうにかする術はありません！

この問題うまいなぁと思うのは、 `#[tokio::test]` マクロ以下なのでシングルスレッド処理になっているんですよね...よって問題では次のようにしてデッドロックが起こっています。

- tokioランタイムはシングルスレッドしか扱えない状況
  - `.await` が入るたびにタスクを <ruby>お手玉<rt>・・・</rt></ruby> している
- ノンブロッキングI/Oの場合は、 `read_to_end` 呼び出し時に `.await` できるから、そこで `write_all` 側のタスクに切り替わるので、デッドロックしない
- しかし、ブロッキングI/Oの場合は、 `read_to_end` でtokioランタイムにスレッド主導権が返らないから、ずっと待ちになってしまう、デッドロック！

※ 以下の図は概略です！

**全部非同期の場合**

<details><summary>PoCソースコード</summary>

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=de4d03d4204a1474c50cb855fed05a4c

```rust:Rust
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

pub async fn echo(listener: TcpListener) -> Result<(), anyhow::Error> {
    loop {
        let (mut socket, _) = listener.accept().await?;
        let mut buffer = Vec::new();
        socket.read_to_end(&mut buffer).await?;
        socket.write_all(&buffer).await?;
    }
}
```

<details><summary>省略なし全体</summary>

```rust:Rust
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

pub async fn echo(listener: TcpListener) -> Result<(), anyhow::Error> {
    loop {
        let (mut socket, _) = listener.accept().await?;
        let mut buffer = Vec::new();
        socket.read_to_end(&mut buffer).await?;
        socket.write_all(&buffer).await?;
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::net::SocketAddr;
    use std::panic;
    use tokio::io::{AsyncReadExt, AsyncWriteExt};
    use tokio::task::JoinSet;

    async fn bind_random() -> (TcpListener, SocketAddr) {
        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
        let addr = listener.local_addr().unwrap();
        (listener, addr)
    }

    #[tokio::test]
    async fn test_echo() {
        let (listener, addr) = bind_random().await;
        tokio::spawn(echo(listener));

        let requests = vec![
            "hello here we go with a long message",
            "world",
            "foo",
            "bar",
        ];
        let mut join_set = JoinSet::new();

        for request in requests {
            join_set.spawn(async move {
                let mut socket = tokio::net::TcpStream::connect(addr).await.unwrap();
                let (mut reader, mut writer) = socket.split();

                // Send the request
                writer.write_all(request.as_bytes()).await.unwrap();
                // Close the write side of the socket
                writer.shutdown().await.unwrap();

                // Read the response
                let mut buf = Vec::with_capacity(request.len());
                reader.read_to_end(&mut buf).await.unwrap();
                assert_eq!(&buf, request.as_bytes());
            });
        }

        while let Some(outcome) = join_set.join_next().await {
            if let Err(e) = outcome {
                if let Ok(reason) = e.try_into_panic() {
                    panic::resume_unwind(reason);
                }
            }
        }
    }
}
```
</details>

</details>

```mermaid
sequenceDiagram
    participant W as requestタスク
    actor T as tokio所有スレッド
    participant R as echoタスク
    R->>T: 実行
    activate T
    T->>R: listener.accept().await?
    deactivate T
    W->>T: 実行
    activate T
    T->>W: TcpStream::connect(addr).await?
    deactivate T
    R->>T: 実行
    activate T
    T->>R: socket.read_to_end(&mut buffer).await?
    deactivate T
    W->>T: 実行
    activate T
    T->>W: writer.write_all(...).await?
    deactivate T
```

**今回のブロッキングI/O混入バージョン**

```mermaid
sequenceDiagram
    participant W as requestタスク
    actor T as tokio所有スレッド
    participant R as echoタスク
    R->>T: 実行
    activate T
    T->>R: listener.accept().await?
    deactivate T
    W->>T: 実行
    activate T
    T->>W: TcpStream::connect(addr).await?
    deactivate T
    R->>T: 実行
    activate T
    Note over T: socket.read_to_end(&mut buffer)?
    Note over T: write_all実行されず！デッドロック！
    deactivate T
```

では `spawn_blocking` でどう解決するのか...？このメソッドを使うことで、 **重たいブロッキング処理専用のスレッドを建ててそちらで実行** されます！

**演習回答バージョン**

```mermaid
sequenceDiagram
    participant W as requestタスク
    actor T as tokio所有スレッド
    participant R as echoタスク
    actor S as spawn_blocking
    R->>T: 実行
    activate T
    T->>R: listener.accept().await?
    deactivate T
    W->>T: 実行
    activate T
    T->>W: TcpStream::connect(addr).await?
    deactivate T
    R->>S: 実行
    activate S
    Note over S: socket.read_to_end(&mut buffer)?
    W->>T: 実行
    activate T
    T->>S: ある意味、スレッド間通信
    T->>W: writer.write_all(...).await?
    deactivate T
    deactivate S
```

暗号計算等重い処理やブロッキングAPIしか提供されておらずそれを使わなければならない場合などに対しては、この `spawn_blocking` 専用スレッドを用いると良さそうです。

# [[08_futures/06_async_aware_primitives](https://github.com/mainmatter/100-exercises-to-learn-rust/blob/main/exercises/08_futures/06_async_aware_primitives/src/lib.rs)] 非同期プリミティブ (非同期用実装)

- [8.6. Async-aware primitives](https://rust-exercises.com/08_futures/06_async_aware_primitives)

問題はこちらです。

```rust:lib.rs
/// TODO: the code below will deadlock because it's using std's channels,
///  which are not async-aware.
///  Rewrite it to use `tokio`'s channels primitive (you'll have to touch
///  the testing code too, yes).
///
/// Can you understand the sequence of events that can lead to a deadlock?
use std::sync::mpsc;

pub struct Message {
    payload: String,
    response_channel: mpsc::Sender<Message>,
}

/// Replies with `pong` to any message it receives, setting up a new
/// channel to continue communicating with the caller.
pub async fn pong(mut receiver: mpsc::Receiver<Message>) {
    loop {
        if let Ok(msg) = receiver.recv() {
            println!("Pong received: {}", msg.payload);
            let (sender, new_receiver) = mpsc::channel();
            msg.response_channel
                .send(Message {
                    payload: "pong".into(),
                    response_channel: sender,
                })
                .unwrap();
            receiver = new_receiver;
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{pong, Message};
    use std::sync::mpsc;

    #[tokio::test]
    async fn ping() {
        let (sender, receiver) = mpsc::channel();
        let (response_sender, response_receiver) = mpsc::channel();
        sender
            .send(Message {
                payload: "pong".into(),
                response_channel: response_sender,
            })
            .unwrap();

        tokio::spawn(pong(receiver));

        let answer = response_receiver.recv().unwrap().payload;
        assert_eq!(answer, "pong");
    }
}
```

ブロッキングして到着を待つ `std::sync::mpsc;` だとデッドロックが起きるので、非同期用の `tokio::sync::mpsc` を使うように書き直そうという問題です。

## 解説

回答としては、 `.await` が使用可能な `tokio::sync::mpsc` に置き換えるだけです！

```diff_rust:lib.rs
// use std::sync::mpsc;
+use tokio::sync::mpsc;

pub struct Message {
    payload: String,
    response_channel: mpsc::Sender<Message>,
}

/// Replies with `pong` to any message it receives, setting up a new
/// channel to continue communicating with the caller.
pub async fn pong(mut receiver: mpsc::Receiver<Message>) {
    loop {
+        if let Some(msg) = receiver.recv().await {
            println!("Pong received: {}", msg.payload);
            let (sender, new_receiver) = mpsc::channel(1);
            msg.response_channel
                .send(Message {
                    payload: "pong".into(),
                    response_channel: sender,
                })
+                .await
                .unwrap();
            receiver = new_receiver;
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{pong, Message};
    // use std::sync::mpsc;
+    use tokio::sync::mpsc;

    #[tokio::test]
    async fn ping() {
        let (sender, receiver) = mpsc::channel(1);
        let (response_sender, mut response_receiver) = mpsc::channel(1);
        sender
            .send(Message {
                payload: "pong".into(),
                response_channel: response_sender,
            })
+            .await
            .unwrap();

        tokio::spawn(pong(receiver));

+        let answer = response_receiver.recv().await.unwrap().payload;
        assert_eq!(answer, "pong");
    }
}
```

「なぜデッドロックするかわかる？」と問われているので、先ほどと同様にシーケンス図に描き起こしてみようと思います。

**`std::sync::mpsc` バージョン**

```mermaid
sequenceDiagram
    participant Ping as pingタスク
    actor T as tokio所有スレッド
    Ping->>T: 実行
    activate T
    Note over T: sender.send(...) (ブロックせず)
    create participant Pong as pongタスク
    T->>Pong: タスク生成
    Note over T: response_receiver.recv()
    Note over T: 誰も何も送ってこない！デッドロック発生！
    deactivate T
```

即効でデッドロックしましたね... `recv()` は `.await` したいです！

tokioに置き換えましょう。

**`tokio::sync::mpsc` バージョン**

```mermaid
sequenceDiagram
    participant Ping as pingタスク
    actor T as tokio所有スレッド
    Ping->>T: 実行
    activate T
    T->>Ping: sender.sent(...).await
    deactivate T
    Ping->>T: 実行
    activate T
    create participant Pong as pongタスク
    T->>Pong: タスク生成
    T->>Ping: response_receiver.recv().await
    deactivate T
    Pong->>T: 実行
    activate T
    T->>Pong: receiver.recv().await
    deactivate T
    Note right of Pong: すでにメッセージは来ている
    Pong->>T: 実行
    activate T
    T->>Pong: msg.response_channel.send(...).await
    deactivate T
    Ping->>T: 実行
    activate T
    T->>Ping: ...
    deactivate T
```

無事にピンポン :ping_pong: できています。

ちなみに本エクササイズの [Book](https://rust-exercises.com/100-exercises/08_futures/06_async_aware_primitives) の方の解説では `std::sync::Mutex` と `tokio::sync::Mutex` が取り上げられており、さらにstdの方を用いた場合のデッドロックの仕組みまで解説されています[^mutex_guard]。

[^mutex_guard]: 解説で登場するソースコード、 `MutexGuard` は `!Send` なため一見するとコンパイルが通らなそうに見えるのですが、 `tokio::spawn` とかで包まない場合[コンパイル通ります](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9f27affc25ba4c63f8f61ea54d92dbae)ね(包む場合は[コンパイルエラー](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f17679b58250e94e30774cc7b1369d8b))...[`block_on`](https://docs.rs/futures/latest/futures/executor/fn.block_on.html) は `Send` を要求しないからでしょうか...？

解説側は `Mutex` 、 エクササイズ側は `mpsc` を題材にしたみたいですね。この2つにとどまらず、前の問題に出てきた非同期I/O ([`tokio::io`](https://docs.rs/tokio/latest/tokio/io/index.html)) やTCPソケット等 ([`tokio::net`](https://docs.rs/tokio/latest/tokio/net/index.html)) 等、tokioクレートには非同期用プリミティブがたくさん用意されており、また大体が標準ライブラリが持つAPIに似ていてそこに `.await` を付けられるようになっています。tokioクレート以外でも、例えば[reqwest](https://docs.rs/reqwest/latest/reqwest/)クレートはfeatureフラグで非同期・[ブロッキング](https://docs.rs/reqwest/latest/reqwest/blocking/index.html)を切り替えられたりできて興味深いですね。

[コストの面を考えると、何でもかんでも非同期プリミティブを使うべきではない](https://zenn.dev/magurotuna/books/tokio-tutorial-ja/viewer/shared_state#tokio-%E3%81%AE%E9%9D%9E%E5%90%8C%E6%9C%9F-mutex-%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B) (ブロッキングプリミティブを使えという意味ではありません)という言説[^async_mutex]もあり、考えることが多い非同期ですが、クレートの対応具合や対応の仕方を見てみると面白そうです(Bookに忖度した感想)。

[^async_mutex]: まぁこれも [`Mutex`](https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html) に限った話の可能性はありますが。

# [[08_futures/07_cancellation](https://github.com/mainmatter/100-exercises-to-learn-rust/blob/main/exercises/08_futures/07_cancellation/src/lib.rs)] タスクのキャンセル

- [8.7. Cancellation](https://rust-exercises.com/08_futures/07_cancellation)

問題はこちらです。主要な行をハイライトしておきます。

```diff_rust:lib.rs
// TODO: fix the `assert_eq` at the end of the tests.
//  Do you understand why that's the resulting output?
use std::time::Duration;
use tokio::io::AsyncReadExt;
use tokio::net::TcpListener;

pub async fn run(listener: TcpListener, n_messages: usize, timeout: Duration) -> Vec<u8> {
    let mut buffer = Vec::new();
    for _ in 0..n_messages {
        let (mut stream, _) = listener.accept().await.unwrap();
        let _ = tokio::time::timeout(timeout, async {
            stream.read_to_end(&mut buffer).await.unwrap();
        })
        .await;
    }
    buffer
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokio::io::AsyncWriteExt;

    #[tokio::test]
    async fn ping() {
        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
        let addr = listener.local_addr().unwrap();
        let messages = vec!["hello", "from", "this", "task"];
        let timeout = Duration::from_millis(20);
        let handle = tokio::spawn(run(listener, messages.len(), timeout.clone()));

        for message in messages {
            let mut socket = tokio::net::TcpStream::connect(addr).await.unwrap();
            let (_, mut writer) = socket.split();

            let (beginning, end) = message.split_at(message.len() / 2);

            // Send first half
            writer.write_all(beginning.as_bytes()).await.unwrap();
            tokio::time::sleep(timeout * 2).await;
            writer.write_all(end.as_bytes()).await.unwrap();

            // Close the write side of the socket
            let _ = writer.shutdown().await;
        }

        let buffered = handle.await.unwrap();
        let buffered = std::str::from_utf8(&buffered).unwrap();
+        assert_eq!(buffered, "");
    }
}
```

最終回手前にして久々の「実行結果どうなる...？」問題ですね...！

## 解説

[Book](https://rust-exercises.com/100-exercises/08_futures/07_cancellation) で解説されている通り、 `.await` がある場所でのみ [`tokio::time::timeout`](https://docs.rs/tokio/latest/tokio/time/fn.timeout.html) 等を利用して外から停止させることができます。裏を返すと前の問題のような **`.await` せずブロッキングしてしまうようなタスクは停止できない** ということですね。こまめな `.await` が大切です。

`.await` は入れ子構造になっています。タイムアウト時にランタイムにコントロールを返した `.await` の時点で停止されるため、それを考慮して回答します！

```mermaid
sequenceDiagram
    participant T1 as タスク (T1)
    create participant T2 as 内側の処理 (T2)
    T1->>T2: .await で呼び出し
    create participant T3 as さらに内側の処理 (T3)
    T2->>T3: .await で呼び出し
    Note right of T3: .await
    Note over T1,T3: ここでタイムアウト。T3の残りの処理は実行されず
```

```diff_rust:lib.rs
use std::time::Duration;
use tokio::io::AsyncReadExt;
use tokio::net::TcpListener;

pub async fn run(listener: TcpListener, n_messages: usize, timeout: Duration) -> Vec<u8> {
    let mut buffer = Vec::new();
    for _ in 0..n_messages {
        let (mut stream, _) = listener.accept().await.unwrap();
        let _ = tokio::time::timeout(timeout, async {
            // 最初を受け取ったところでread_to_end実装内部のawaitがyieldする
            // そしてタイムアウトするので、前半だけ残る
            stream.read_to_end(&mut buffer).await.unwrap();
        })
        .await;
    }
    buffer
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokio::io::AsyncWriteExt;

    #[tokio::test]
    async fn ping() {
        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
        let addr = listener.local_addr().unwrap();
        let messages = vec!["hello", "from", "this", "task"];
        let timeout = Duration::from_millis(20);
        let handle = tokio::spawn(run(listener, messages.len(), timeout.clone()));

+        let mut answer = String::new();

        for message in messages {
            let mut socket = tokio::net::TcpStream::connect(addr).await.unwrap();
            let (_, mut writer) = socket.split();

            let (beginning, end) = message.split_at(message.len() / 2);

+            answer = format!("{}{}", answer, beginning);

            // Send first half
            writer.write_all(beginning.as_bytes()).await.unwrap();
            tokio::time::sleep(timeout * 2).await;
            writer.write_all(end.as_bytes()).await.unwrap();

            // Close the write side of the socket
            let _ = writer.shutdown().await;
        }

        let buffered = handle.await.unwrap();
        let buffered = std::str::from_utf8(&buffered).unwrap();
-        assert_eq!(buffered, "");
+        assert_eq!(buffered, &answer);
        // 答えは、各文字列の前半分を足し合わせた
        // hefrthta
    }
}

```

`read_to_end` メソッドでの `.await` 中にタイムアウトが来るように設定されています。 `read_to_end` の内部でも `.await` が呼ばれてバッファに書き込みを行っていると予想でき、前半部分だけが書き込まれているだろうと踏まえて回答すればそれが正解です。ハードコードせずその意図を組み入れた回答をしてみました！

:::note 
**AsyncDropがほしい...！(ない)**

[Book](https://rust-exercises.com/100-exercises/08_futures/07_cancellation#clean-up) にてキャンセルの話のついでにクリーンアップの話題が出ています。そして「不幸なことに、Rustは**非同期**クリーンアップ操作に関して明確な手段を提供していません」みたいなことが書かれています。

クリーンアップと言えば、そう、我々には [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) トレイトがあります(参考: [第12回](https://qiita.com/namn1125/items/c6f34945f331c7229746))。しかしそのシグネチャには、 **当然非同期的な要素は含まれません** 。

```rust:Rust
pub trait Drop {
    // Required method
    fn drop(&mut self);
}
```

例えば [`tokio::runtime::Handle::current`](https://docs.rs/tokio/latest/tokio/runtime/struct.Handle.html) を使うことで無理やり非同期処理を実行しようとすることは可能ですが、クリーンアップの確実性の担保が難しいです。 

<details><summary>PoCソースコード</summary>

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=cab4561a2ae8af4ce62ef8585a7ed9e6

```rust:Rust
use tokio::time::{sleep, Duration};
use tokio::runtime::Handle;

#[derive(Clone)]
struct Hoge {
    // 所有するリソースには、所有権を奪える等スレッドを跨げるような細工をしておく
    resource: Option<String>,
}

impl Hoge {
    fn new(resource: String) -> Self {
        Self {
            resource: Some(resource),
        }
    }

    async fn clean_up(resource: String) {
        println!("Wait...");
        sleep(Duration::from_millis(100)).await;
        println!("Done: {}", resource);
    }
}

impl Drop for Hoge {
    fn drop(&mut self) {
        let Some(resource) = self.resource.take() else {
            return;
        };
        let handle = Handle::current();
        handle.spawn(async move {
            Hoge::clean_up(resource).await;
        });
    }
}

#[tokio::main]
async fn main() {
    let h = Hoge::new("Beep".to_string());
    
    drop(h);
    
    sleep(Duration::from_millis(1000)).await;
}
```
</details>

Bookでは `Drop` について、ケースに合わせて例えば次のように実装すると良いと書かれています。

- ランタイムで新しいタスクを作成する (挙げたPoCはこの方法)
- `mpsc` 等を使ってメッセージを送り、別タスクにクリーンアップを任せる (良さそう...？)
- 裏で別スレッドを立ち上げる

Bookの例にも挙げられているSQLトランザクションのabort等、確実に実行してほしくて、かつ非同期ではない、いつものRustなら `Drop` トレイトにまかせておけばおｋな処理でも、上記に挙げたような手法を取るしかないのが現状です。

このような要望に答えてくれそうな [`AsynDrop` を考案しているWG](https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_drop.html) もあるらしいですが、一方で「[ `AsyncDrop` みたいなものが要らないような実装を心がけろ](https://users.rust-lang.org/t/what-is-the-idiomatic-way-of-cleaning-up-async-resources-in-rust/48878/2) (おそらく上述したような対処をしようの意)」という言説もあります...umm

直感的に、 `drop` メソッドは軽い処理であってほしいような気がするので、"重め"なことをするであろう `AsyncDrop` というのはその思想に反していそうではあります[^mpsc]。一方、Pythonの `with` や Goの `defer` のような機能が `Drop` に求められているのも事実です。

[^mpsc]: そのため逆に言えば `mpsc` で外にクリーンアップを任せるのが筋の良い方法に見えます。ただ結局非同期 `mpsc` を使う都合上、考えることは多そうです。 [`spawn_blocking`](https://docs.rs/tokio/latest/tokio/runtime/struct.Runtime.html#method.spawn_blocking) で別スレッドを建てて [`blocking_send`](https://docs.rs/tokio/latest/tokio/sync/mpsc/struct.Sender.html#method.blocking_send) とか...？

そのため筆者的には `with` や `defer` により近い、 `Drop` とは異なるが、 `Drop` と同様スコープが外れる時にRAIIライクなクリーンアップをしてくれるトレイトがほしいなぁとか思ったりします。 `drop` 実行前に実行されるイメージです。もしそんな感じの `CleanUp` トレイト(筆者命名)があれば、 `AsyncCleanUp` トレイトみたいなのはありなんじゃないでしょうか？(妄想)
:::

さて、無事にここまでこれました。次回、いよいよ最終回です...！

次の記事: [【37】 Axumでクラサバ！ ～最終回～](https://qiita.com/namn1125/items/bae492f823fbe60e9974)

<details><summary>登場したPlayground</summary>

(実際に無効化したことはないですが、)Rust Playground上のデータが喪失する可能性を鑑みて、一応記事にもソースコードを掲載することとしました。

URL: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9f27affc25ba4c63f8f61ea54d92dbae

```rust:Rust
use tokio;
use std::sync::{Arc, Mutex};

async fn run(m: Arc<Mutex<Vec<u64>>>) {
    let guard = m.lock().unwrap();
    http_call(&guard).await;
    println!("Sent {:?} to the server", &guard);
    // `guard` is dropped here
}

async fn http_call(_v: &[u64]) {
  // [...]
}

#[tokio::main]
async fn main() {
    let amvu = Arc::new(Mutex::new(vec![0, 1, 2]));
    
    run(amvu).await;
}
```

URL: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f17679b58250e94e30774cc7b1369d8b

```rust:Rust
use std::sync::{Arc, Mutex};

async fn run(m: Arc<Mutex<Vec<u64>>>) {
    let guard = m.lock().unwrap();
    http_call(&guard).await;
    println!("Sent {:?} to the server", &guard);
    // `guard` is dropped here
}

/// Use `v` as the body of an HTTP call.
async fn http_call(v: &[u64]) {
  // [...]
}

#[tokio::main]
async fn main() {
    let m = Arc::new(Mutex::new(vec![0, 1, 2]));
    tokio::spawn(run(m));
}
```
</details>