원클릭으로
rust-io-loop
io_loop, smoltcp/Tokio bridge, manual polling, tunnel sessions, io_uring buffers, and stalls.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
io_loop, smoltcp/Tokio bridge, manual polling, tunnel sessions, io_uring buffers, and stalls.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use this skill to generate well-branded interfaces and assets for RIPDPI (an Android-native, Compose-first VPN and DPI-bypass app), either for production or throwaway prototypes/mocks/etc. Contains essential design guidelines, colors, type, fonts, brand-ship icons, and an interactive UI kit of every public composable from the live ui/components tree.
Use when managing the Rust workspace, adding/removing crates, editing workspace dependencies, running cargo nextest/audit/deny, configuring Cargo profiles for Android cross-compilation, debugging Cargo.lock churn, migrating crate edition, or wiring Gradle to cargo via the ripdpi.android.rust-native plugin.
Use when modifying the diagnostics scan pipeline, ScanRequest/ScanReport types, ProbeTask families, ripdpi-monitor-engine / ripdpi-diagnostics-* crates, strategy-probe candidates, the diagnostics catalog (packs/profiles), wire-schema contracts between Rust and Kotlin, DIAGNOSTICS_ENGINE_SCHEMA_VERSION, golden contract tests, or adding a new probe type / profile. Triggers on diagnostics scans, strategy probes, automatic audit, dpi-detector profiles, or anything in core/diagnostics or native/rust/crates/ripdpi-monitor-*.
Android-specific Rust build, verification, and packaging — per-target 16 KiB page alignment, size-optimized release profile, ELF symbol allowlist, .so size budgets, NDK 29 specifics. Use when modifying .cargo/config.toml for Android targets, the workspace [profile.release] / [profile.android-jni] block, or when verifying a built .so before release.
Telemetry and observability discipline for the RIPDPI Android Rust stack — control-plane vs data-plane logging, bounded flume event queues, atomic counters, snapshot polling, readiness callbacks, and deterministic JSON contracts. Use when authoring or modifying telemetry emission code, the bounded event ring, the Kotlin-side telemetry consumer, or any per-packet logging.
Custom detekt rules, DI/privacy/suppression guardrails, detekt.yml configuration, and false-positive triage.
| name | rust-io-loop |
| description | io_loop, smoltcp/Tokio bridge, manual polling, tunnel sessions, io_uring buffers, and stalls. |
ripdpi-tunnel-core owns a userspace TCP stack (smoltcp) that must feed into tokio-driven session tasks. The two runtimes don't share a waker — smoltcp's scheduler is step-driven, tokio's is waker-driven. The bridge resolves this by constructing a NoopWaker, wrapping it in a Context, and manually calling poll_read / poll_write on a tokio::io::DuplexStream:
pub(super) struct NoopWaker;
impl std::task::Wake for NoopWaker {
fn wake(self: Arc<Self>) {}
fn wake_by_ref(self: &Arc<Self>) {}
}
pub(super) fn try_read_duplex(
stream: &mut tokio::io::DuplexStream,
buf: &mut [u8],
) -> Option<io::Result<usize>> {
let waker = Waker::from(Arc::new(NoopWaker));
let mut cx = Context::from_waker(&waker);
let mut rb = ReadBuf::new(buf);
match Pin::new(stream).poll_read(&mut cx, &mut rb) {
Poll::Ready(Ok(())) => Some(Ok(rb.filled().len())),
Poll::Ready(Err(e)) => Some(Err(e)),
Poll::Pending => None, // bridge will retry next tick
}
}
Canonical implementation: native/rust/crates/ripdpi-tunnel-core/src/io_loop/bridge/duplex.rs; locate try_read_duplex and try_write_duplex by symbol.
The whole try_*_duplex family is called from the io_loop's polling tick, not from a tokio task. If we gave the duplex stream a real waker, the runtime would record a wake-up registration pointing at a context that no longer exists by the time the wake fires. That's safe (the weak reference is just dropped) but wasteful.
The trade-off: Poll::Pending yields no progress information. The io_loop decides when to re-poll by its own timing (after smoltcp's iface.poll() processes packets, or on a per-tick interval), not by tokio wake signals.
try_*_duplex is NEVER called from inside an async task's await. If it were, Poll::Pending would propagate as "task not ready", but with a NoopWaker attached the task would never be woken — permanent stall. The bridge functions must be called from synchronous io_loop code.
The stream must outlive the Context. Pin::new(&mut stream) is safe because DuplexStream: Unpin; the bridge function's own stack frame owns the waker for the duration of the poll call.
Cancellation is cooperative, not waker-driven. The io_loop checks a CancellationToken between polling rounds. A smoltcp socket whose peer duplex stream has been dropped will surface Poll::Ready(Err(BrokenPipe)) on the next try_read_duplex — that's the cancellation signal.
If you add a new tokio::io::DuplexStream-like type to the bridge:
AsyncRead + AsyncWrite + Unpin.try_* wrapper must handle Poll::Ready(Ok(0)) as a write-zero or read-zero condition and translate it to WriteZero or UnexpectedEof; use flush_pending_to_session in bridge/duplex.rs as the canonical handling.async fn wrapper that awaits the stream — it will stall under the NoopWaker.New smoltcp sockets (TCP, UDP, raw) must plug into:
native/rust/crates/ripdpi-tunnel-core/src/session/tcp.rsnative/rust/crates/ripdpi-tunnel-core/src/session/udp.rsnative/rust/crates/ripdpi-tunnel-core/src/io_loop/udp_assoc.rsFollow the try_*_duplex convention and call from io_loop tick code, not from async tasks.
ripdpi-io-uring)The io_uring side of the tunnel uses registered buffer pools for zero-copy SendZc / RecvFixed / TunReadBatch. Key invariants:
unsafe block in ring.rs that constructs an SQE carries a // SAFETY: comment naming the buffer-index validity and the fd lifetime. New SQE constructors must match.SendZc / RecvFixed that the session drops must be cancelled via IORING_OP_ASYNC_CANCEL — don't just drop the SQE. A fire-and-forget drop leaks the registered buffer until the kernel completes the operation.Anchor: native/rust/crates/ripdpi-io-uring/src/ring.rs (12 unsafe blocks, 2 unsafe trait impls).
Tokio 1.51.1 fixed a file-descriptor leak when an io_uring open operation is cancelled before completion — directly RIPDPI-relevant because the io_loop's cancellation path relies on this. Do not bypass or downgrade the tokio pin in Cargo.toml.
Reference: tokio CHANGELOG, PR #7983.
try_write_duplex from an async task instead of the io_loop tick. Move the call to the synchronous bridge.iface.poll() tick after the write; if the io_loop skipped a tick under load, writes accumulate in the duplex stream until the next tick.IORING_OP_ASYNC_CANCEL. Add the cancel submission in the drop path.rust-async-internals — tokio runtime config, CancellationToken, select!/join! patterns on the session-task side (the async side of the bridge).rust-unsafe — SAFETY conventions for ring.rs SQE construction.rust-panic-safety — session tasks wrap their bodies in catch_unwind before driving the bridge; a panic in the bridge itself propagates to the io_loop task which catches it.