May 2026 Rust Blr meetup
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
[!NOTE] Slightly related: nice video on file descriptors
- 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
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_waitandread) ⇒ Context switch overheadepoll_ctlis 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 ifSQPOLLsetup)- But no syscall per I/O task
- No need for syscall to read from CQueue
Work stealing in the Readiness model
- Research paper - Scheduling multi-threaded computations by work stealing
- Blog - Making the Tokio scheduler 10x faster
- Basic idea: Idle threads steal work from busy threads’ queue
- Steal oldest waiting tasks (queue head) to minimize contention
- Current thread executes next task remaining after stealing
- Better for cache locality
- Emulates call stack
- Good load balancing
- Synchronization overhead between threads + Cold cache penalty in new thread
Futurehas to implementSend+'statictrait bounds + alsoSyncif concurrent sharing via referencesSendbound can be relaxed by running only on current thread usingtokio::task::LocalSetbut 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
- Problem for future cancellation in Completion, Has to be explicitly handled
-
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
- Bake a password into the binary
- Easily cracked by listing
stringsin 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
- Easily cracked by listing
- 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
- Hardware fingerprinting. App runs only on one specific system. Verify using system specific details like MAC addr
- Bypass by spoofing fingerprints
- 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.