Pin

Async blocks and functions return types implementing the Future trait. The type returned is the result of a compiler transformation which turns local variables into data stored inside the future.

Some of those variables can hold pointers to other local variables. Because of that, the future should never be moved to a different memory location, as it would invalidate those pointers.

To prevent moving the future type in memory, it can only be polled through a pinned pointer. Pin is a wrapper around a reference that disallows all operations that would move the instance it points to into a different memory location.

use tokio::sync::{mpsc, oneshot};
use tokio::task::spawn;
use tokio::time::{sleep, Duration};

// μž‘μ—… ν•­λͺ©. 이 경우 μ§€μ •λœ μ‹œκ°„ λ™μ•ˆ μ ˆμ „ λͺ¨λ“œμ΄κ³ 
// `respond_on` μ±„λ„μ˜ λ©”μ‹œμ§€λ‘œ μ‘λ‹΅ν•©λ‹ˆλ‹€.
#[derive(Debug)]
struct Work {
    input: u32,
    respond_on: oneshot::Sender<u32>,
}

// νμ—μ„œ μž‘μ—…μ„ μˆ˜μ‹  λŒ€κΈ°ν•˜κ³  μ‹€ν–‰ν•˜λŠ” workerμž…λ‹ˆλ‹€.
async fn worker(mut work_queue: mpsc::Receiver<Work>) {
    let mut iterations = 0;
    loop {
        tokio::select! {
            Some(work) = work_queue.recv() => {
                sleep(Duration::from_millis(10)).await; // μž‘μ—…ν•˜λŠ” μ²™ν•©λ‹ˆλ‹€.
                work.respond_on
                    .send(work.input * 1000)
                    .expect("응닡을 보내지 λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.");
                iterations += 1;
            }
            // TODO: 100λ°€λ¦¬μ΄ˆλ§ˆλ‹€ 반볡 횟수λ₯Ό λ³΄κ³ ν•©λ‹ˆλ‹€.
        }
    }
}

// μž‘μ—…μ„ μš”μ²­ν•˜κ³  μž‘μ—…μ΄ μ™„λ£Œλ˜κΈ°λ₯Ό κΈ°λ‹€λ¦¬λŠ” μš”μ²­μžμž…λ‹ˆλ‹€.
async fn do_work(work_queue: &mpsc::Sender<Work>, input: u32) -> u32 {
    let (tx, rx) = oneshot::channel();
    work_queue
        .send(Work { input, respond_on: tx })
        .await
        .expect("μž‘μ—… νμ—μ„œ μ „μ†‘ν•˜μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.");
    rx.await.expect("응닡 λŒ€κΈ° μ‹€νŒ¨")
}

#[tokio::main]
async fn main() {
    let (tx, rx) = mpsc::channel(10);
    spawn(worker(rx));
    for i in 0..100 {
        let resp = do_work(&tx, i).await;
        println!("반볡 μž‘μ—… κ²°κ³Ό {i}: {resp}");
    }
}
  • μœ„μ—μ„œ μ†Œκ°œν•œ 것은 μ•‘ν„°(actor) νŒ¨ν„΄μ˜ ν•œ 예라고 봐도 λ¬΄λ°©ν•©λ‹ˆλ‹€. μ•‘ν„°λŠ” 일반적으둜 루프 μ•ˆμ—μ„œ select!λ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€.

  • 이전 κ°•μ˜ λͺ‡ 개의 λ‚΄μš©μ„ μš”μ•½ν•œ 것이기 λ•Œλ¬Έμ— 천천히 μ‚΄νŽ΄λ³΄μ„Έμš”.

    • _ = sleep(Duration::from_millis(100)) => { println!(..) }을 select!에 μΆ”κ°€ν•΄ λ³΄μ„Έμš”. 이 μž‘μ—…μ€ μ‹€ν–‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μ™œ κ·ΈλŸ΄κΉŒμš”?

    • λŒ€μ‹ , ν•΄λ‹Ή futureκ°€ ν¬ν•¨λœ timeout_futλ₯Ό loop 외뢀에 μΆ”κ°€ν•΄ λ³΄μ„Έμš”.

      #![allow(unused)]
      fn main() {
      let mut timeout_fut = sleep(Duration::from_millis(100));
      loop {
          select! {
              ..,
              _ = timeout_fut => { println!(..); },
          }
      }
      }
    • μ—¬μ „νžˆ μž‘λ™ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 컴파일러 였λ₯˜λ₯Ό 따라 select!의 timeout_fut에 &mutλ₯Ό μΆ”κ°€ν•˜μ—¬ Move μ‹œλ©˜ν‹± κ΄€λ ¨ν•œ 문제λ₯Ό ν•΄κ²°ν•˜κ³  Box::pin을 μ‚¬μš©ν•˜μ„Έμš”.

      #![allow(unused)]
      fn main() {
      let mut timeout_fut = Box::pin(sleep(Duration::from_millis(100)));
      loop {
          select! {
              ..,
              _ = &mut timeout_fut => { println!(..); },
          }
      }
      }
    • μ΄λŠ” μ»΄νŒŒμΌμ€ λ˜μ§€λ§Œ νƒ€μž„ 아웃이 되면 맀번 λ°˜λ³΅ν•  λ•Œ λ§ˆλ‹€ Poll::Readyκ°€ λ©λ‹ˆλ‹€(μœ΅ν•©λœ futureκ°€ 도움이 될 수 있음). νƒ€μž„ 아웃 될 λ•Œλ§ˆλ‹€ timeout_futλ₯Ό λ¦¬μ…‹ν•˜λ„λ‘ μˆ˜μ •ν•˜μ„Έμš”.

  • BoxλŠ” νž™μ— ν• λ‹Ήν•©λ‹ˆλ‹€. κ²½μš°μ— 따라 std::pin::pin!(μ΅œκ·Όμ—μ•Ό μ•ˆμ •ν™”λ˜μ—ˆμœΌλ©° 이전 μ½”λ“œλŠ” tokio::pin!을 μ‚¬μš©ν•˜λŠ” κ²½μš°κ°€ 많음)도 μ‚¬μš©ν•  수 μžˆμ§€λ§Œ μ΄λŠ” μž¬ν• λ‹Ήλœ future에 μ‚¬μš©ν•˜κΈ°κ°€ μ–΄λ ΅μŠ΅λ‹ˆλ‹€.

  • 또 λ‹€λ₯Έ 방법은 pin을 μ•„μ˜ˆ μ‚¬μš©ν•˜μ§€ μ•Šκ³  100msλ§ˆλ‹€ oneshot 채널에 전솑할 λ‹€λ₯Έ μž‘μ—…μ„ μƒμ„±ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

  • Data that contains pointers to itself is called self-referential. Normally, the Rust borrow checker would prevent self-referential data from being moved, as the references cannot outlive the data they point to. However, the code transformation for async blocks and functions is not verified by the borrow checker.

  • Pin is a wrapper around a reference. An object cannot be moved from its place using a pinned pointer. However, it can still be moved through an unpinned pointer.

  • The poll method of the Future trait uses Pin<&mut Self> instead of &mut Self to refer to the instance. That’s why it can only be called on a pinned pointer.