May 2026 Rust Blr meetup

Published on 2026-05-16

Async runtimes revisited - Sanchayan


Why async?

Regular:

  • Not scalable
  • 1 OS thread ~= 8MB ⇒ Can’t use many threads for concurrency otherwise too much RAM consumption
  • Kernel level threads used ⇒ Context switch overhead

Async:

  • Handful of OS threads can handle millions of concurrent tasks
  • Yield control to Executor on I/O wait instead of blocking thread in Async model

Async Rust

  • Lazy evaluation until future is polled (using .await)

    • Unlike other languages like JS in which an async fn is evaluated as soon as it is called.
    • Avoids overhead
    • Async runtimes provide Executors to handle polling of futures until it finishes execution
  • No callback hell thanks to async/await syntax

    • Zero cost state machines
  • epoll/kqueue (Readiness model):

[!NOTE] Slightly related: nice video on file descriptors

The Readiness model
  • io_uring (Completion model):
    • Don’t have to explicitly call read on ready fd
    • Just read final result directly from Completion queue when OS async finishes execution and notifies
The Completion model

Async strats

  • tokio
    • M:N tasks:threads
    • Multithreaded by default but can use single (current) thread
    • epoll mechanism (Readiness model)
    • Technically also thread-per-core
  • compio/glommio/monoio
    • What is generally known as “thread-per-core”
    • io_uring mechanism mainly (Completion model)
    • compio also supports IOCP (Windows’ completion model I/O interface)

Readiness model - Data not returned until read called

  • Process - Is x ready?
  • epoll - Yes
  • Return x to process and process calls read to get final product

[!NOTE]

  • 2 syscalls per I/O task lifecycle (epoll_wait and read) ⇒ Context switch overhead
  • epoll_ctl is not counted because it is an amortized, one-time setup cost
  • No batching

Completion model

  • Process - I want to do x
  • SQueue - Ok will do
  • CQueue - Return final product to process

[!NOTE]

  • Batch submit all tasks in 1 syscall to SQueue (io_uring_enter. Not required if SQPOLL setup)
  • But no syscall per I/O task
  • No need for syscall to read from CQueue

Work stealing in the Readiness model

Work Stealing
  • Good load balancing
  • Synchronization overhead between threads + Cold cache penalty in new thread
  • Future has to implement Send + 'static trait bounds + also Sync if concurrent sharing via references
    • Send bound can be relaxed by running only on current thread using tokio::task::LocalSet but not truly parallel

Readiness model good to use generally with mixed workloads and unknown load patterns

Thread-Per-Core and Share-nothing

  • All async runtimes are thread-per-core (TPC)

  • Difference between tokio and the other so-called “thread-per-core” runtimes is that they are also share-nothing ⇒ No work stealing

  • One thread pinned to each CPU core

  • No synchronization overhead, better cache locality, no contention and predictable tail latency but can cause load imbalance

  • Need for app level partitioning of connections

    • But not all workloads can be partitioned
    • Shared state managed through message passing
  • Nice reddit thread on implications of completion vs readiness

  • compio v nice TPC+SN runtime because actively maintained + supports IOCP

  • Completion good for

    • Data plane workloads
    • I/O bound workloads
    • Highly cache sensitive workloads
    • When you can partition cleanly
    • Does your tail latency matter?
  • Cross-core communication is expensive since message passing through channels instead of shared state

  • Tokio handles a lot of details in bg but has to be manually handled in compio

    • Basically async hard mode but offers way better performance for I/O heavy workloads in return
  • Buffer reference shared (borrowed) in Readiness model whereas buffer has to be owned by the kernel in the Completion model until I/O is complete

    • Problem for future cancellation in Completion, Has to be explicitly handled
      • When cancelled in Readiness, reference goes out of scope and ends
      • In Completion, kernel still has pointer to that memory ⇒ Use-After-Free if it has been deallocated when cancelled
    • Incurs “Ownership tax” + can’t do concurrent reads with the same buffer
      • Ownership tax = Can’t easily share buffer, Have to handle returned buffer to reuse, Buffer pooling has to be done to avoid constant allocations
  • Usually tokio is good enough. Consider Completion model runtimes if I/O is bottleneck or cache issue

All links: https://adihegde.com/til/rust-blr-16-may-2026

Cracking games and how to make them crack proof - Ishan


For local first, offline apps

  1. Bake a password into the binary
    • Easily cracked by listing strings in the binary
    • Can also patch the binary to bypass password checks
    • Only problem is takes a bit of time to search for the exact string or the exact location to patch
  2. Signed license. Pubkey baked into bin. Verify with privkey on app launch
    • Cracked by patching code to bypass check or replace pubkey with your own pubkey
  3. Hardware fingerprinting. App runs only on one specific system. Verify using system specific details like MAC addr
    • Bypass by spoofing fingerprints
  4. Harden during LTO. Distorts binary a bit making it difficult to understand decompiler output
    • Fat LTO
    • Abort on panic instead of unwind (No stack traces to read and get info)
    • Strip debug symbols

Checks usually done by dynamic libraries in games so patch in your own DLLs in windows instead of the original ones or use LD_PRELOAD to load your own libraries and bypass checks.