M0: compilable skeleton — Kigi 0.1.0 fork surgery
Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.
Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
ptyctl, ptyctl-cli, third_party/ unchanged; proto package
xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
(templates re-encrypted)
Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
module & dc_log, heap-profile uploader, auth-diagnostics uploader,
session-analytics halves of feedback; local zero-egress observability
preserved in new kigi-log crate (unified log, --debug firehose,
subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
shell util
Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted
Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean
Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
(new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
fast-worktree); RSS measurement tests serialized via serial_test
Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
notices sustained; kigi-tools ported-code notices extended; README,
CONTRIBUTING, SECURITY, AGENTS.md rewritten
Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
This commit is contained in:
commit
d6c20fc13f
2612 files changed
+1353757
No files matched your search
@@ -0,0 +1,512 @@
|
||||
//! Frame-aware fault-injection proxy for unix-domain-socket IPC.
|
||||
//!
|
||||
//! Sits between a client and a real listener (`proxy.sock` → `real.sock`),
|
||||
//! parsing the leader IPC framing (4-byte big-endian length prefix + body) so
|
||||
//! faults land on exact frame boundaries: drop exactly the Nth frame, sever
|
||||
//! after a half-written length prefix, delay or duplicate one frame. Everything
|
||||
//! is path-addressed, so no production changes are needed — point
|
||||
//! `LeaderClient::connect` / `KIGI_LEADER_SOCKET` at the proxy path.
|
||||
//!
|
||||
//! Frame numbering is 1-based and **per proxied connection, per direction**;
|
||||
//! reconnects restart the count. Unix-only (the leader transport on Windows is
|
||||
//! a named pipe, which cannot be interposed this way); gated in `lib.rs`.
|
||||
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf};
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
/// Which pump direction a [`FaultPlan`] applies to.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum FaultDirection {
|
||||
#[default]
|
||||
ClientToLeader,
|
||||
LeaderToClient,
|
||||
}
|
||||
|
||||
/// Frame-indexed fault schedule (1-based, per connection, per direction).
|
||||
///
|
||||
/// The default plan is a transparent pass-through.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FaultPlan {
|
||||
/// Direction the frame-indexed faults below apply to; the other direction
|
||||
/// always passes through untouched.
|
||||
pub direction: FaultDirection,
|
||||
/// Silently drop the Nth frame (never forwarded).
|
||||
pub drop_frame: Option<u64>,
|
||||
/// On the Nth frame, forward only 2 bytes of its 4-byte length prefix,
|
||||
/// then hard-close both sides of the connection.
|
||||
pub sever_mid_frame: Option<u64>,
|
||||
/// Hold the Nth frame for the given duration before forwarding it.
|
||||
pub delay: Option<(u64, Duration)>,
|
||||
/// Forward the Nth frame twice.
|
||||
pub duplicate_frame: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FaultState {
|
||||
/// Current sever scope: cancelled + swapped for a fresh token on every
|
||||
/// [`FaultHandle::sever_now`], so only connections active at sever time die.
|
||||
sever_now: std::sync::Mutex<CancellationToken>,
|
||||
/// Frames fully forwarded client→leader across all connections.
|
||||
forwarded_c2l: AtomicU64,
|
||||
/// Frames fully forwarded leader→client across all connections.
|
||||
forwarded_l2c: AtomicU64,
|
||||
}
|
||||
|
||||
/// Runtime control over a running [`UdsProxy`].
|
||||
#[derive(Clone, Default)]
|
||||
pub struct FaultHandle {
|
||||
state: Arc<FaultState>,
|
||||
}
|
||||
|
||||
impl FaultHandle {
|
||||
/// Hard-close every active proxied connection immediately (mid-stream
|
||||
/// sever, independent of the frame-indexed plan). Later connections
|
||||
/// through the same proxy are unaffected.
|
||||
pub fn sever_now(&self) {
|
||||
let mut guard = self.state.sever_now.lock().unwrap();
|
||||
guard.cancel();
|
||||
*guard = CancellationToken::new();
|
||||
}
|
||||
|
||||
fn connection_scope(&self) -> CancellationToken {
|
||||
self.state.sever_now.lock().unwrap().child_token()
|
||||
}
|
||||
|
||||
/// Frames fully forwarded so far in the given direction. Relaxed:
|
||||
/// independent counters, no cross-variable ordering to protect.
|
||||
pub fn forwarded(&self, direction: FaultDirection) -> u64 {
|
||||
match direction {
|
||||
FaultDirection::ClientToLeader => self.state.forwarded_c2l.load(Ordering::Relaxed),
|
||||
FaultDirection::LeaderToClient => self.state.forwarded_l2c.load(Ordering::Relaxed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A running proxy: listener on [`Self::proxy_path`], forwarding to the
|
||||
/// upstream path it was spawned with. Dropping the struct stops the listener
|
||||
/// and severs active connections.
|
||||
pub struct UdsProxy {
|
||||
pub proxy_path: PathBuf,
|
||||
handle: FaultHandle,
|
||||
cancel: CancellationToken,
|
||||
}
|
||||
|
||||
impl UdsProxy {
|
||||
/// Bind `proxy_path` and forward each accepted connection to
|
||||
/// `upstream_path`, applying `plan` per connection.
|
||||
pub async fn spawn(
|
||||
proxy_path: impl Into<PathBuf>,
|
||||
upstream_path: impl AsRef<Path>,
|
||||
plan: FaultPlan,
|
||||
) -> io::Result<Self> {
|
||||
let proxy_path = proxy_path.into();
|
||||
let upstream_path = upstream_path.as_ref().to_path_buf();
|
||||
let _ = std::fs::remove_file(&proxy_path);
|
||||
let listener = UnixListener::bind(&proxy_path)?;
|
||||
|
||||
let handle = FaultHandle::default();
|
||||
let cancel = CancellationToken::new();
|
||||
|
||||
let accept_handle = handle.clone();
|
||||
let accept_cancel = cancel.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = accept_cancel.cancelled() => break,
|
||||
accepted = listener.accept() => {
|
||||
let Ok((client, _)) = accepted else { break };
|
||||
let Ok(upstream) = UnixStream::connect(&upstream_path).await else {
|
||||
// Upstream gone: dropping `client` models a refused
|
||||
// connection; the caller's retry logic takes over.
|
||||
continue;
|
||||
};
|
||||
spawn_connection(client, upstream, plan.clone(), accept_handle.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
proxy_path,
|
||||
handle,
|
||||
cancel,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> FaultHandle {
|
||||
self.handle.clone()
|
||||
}
|
||||
|
||||
/// Stop accepting and sever active connections.
|
||||
pub fn shutdown(&self) {
|
||||
self.handle.sever_now();
|
||||
self.cancel.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for UdsProxy {
|
||||
fn drop(&mut self) {
|
||||
self.shutdown();
|
||||
let _ = std::fs::remove_file(&self.proxy_path);
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_connection(
|
||||
client: UnixStream,
|
||||
upstream: UnixStream,
|
||||
plan: FaultPlan,
|
||||
handle: FaultHandle,
|
||||
) {
|
||||
let (client_read, client_write) = tokio::io::split(client);
|
||||
let (upstream_read, upstream_write) = tokio::io::split(upstream);
|
||||
|
||||
// One sever scope per connection: a mid-frame sever (or `sever_now`)
|
||||
// cancels BOTH pumps so the two half-connections drop together.
|
||||
let conn_cancel = handle.connection_scope();
|
||||
|
||||
let c2l_plan = (plan.direction == FaultDirection::ClientToLeader).then(|| plan.clone());
|
||||
let l2c_plan = (plan.direction == FaultDirection::LeaderToClient).then_some(plan);
|
||||
|
||||
let c2l_counter = handle.clone();
|
||||
let c2l_cancel = conn_cancel.clone();
|
||||
tokio::spawn(async move {
|
||||
pump_frames(
|
||||
client_read,
|
||||
upstream_write,
|
||||
c2l_plan,
|
||||
FaultDirection::ClientToLeader,
|
||||
c2l_counter,
|
||||
c2l_cancel,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
let l2c_counter = handle;
|
||||
tokio::spawn(async move {
|
||||
pump_frames(
|
||||
upstream_read,
|
||||
client_write,
|
||||
l2c_plan,
|
||||
FaultDirection::LeaderToClient,
|
||||
l2c_counter,
|
||||
conn_cancel,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
|
||||
/// Pump length-prefixed frames from `reader` to `writer`, applying `plan`
|
||||
/// (when `Some`) to this direction. Ends on EOF, IO error, or sever.
|
||||
async fn pump_frames(
|
||||
mut reader: ReadHalf<UnixStream>,
|
||||
mut writer: WriteHalf<UnixStream>,
|
||||
plan: Option<FaultPlan>,
|
||||
direction: FaultDirection,
|
||||
handle: FaultHandle,
|
||||
cancel: CancellationToken,
|
||||
) {
|
||||
let mut frame_index: u64 = 0;
|
||||
loop {
|
||||
let frame = tokio::select! {
|
||||
_ = cancel.cancelled() => break,
|
||||
frame = read_frame(&mut reader) => frame,
|
||||
};
|
||||
let Ok((len_prefix, body)) = frame else {
|
||||
break;
|
||||
};
|
||||
frame_index += 1;
|
||||
|
||||
if let Some(ref plan) = plan {
|
||||
if plan.drop_frame == Some(frame_index) {
|
||||
continue;
|
||||
}
|
||||
if plan.sever_mid_frame == Some(frame_index) {
|
||||
// Half a length prefix, then a hard close of the whole
|
||||
// connection: the reader sees a short read, never a body.
|
||||
let _ = writer.write_all(&len_prefix[..2]).await;
|
||||
let _ = writer.flush().await;
|
||||
cancel.cancel();
|
||||
break;
|
||||
}
|
||||
if let Some((nth, duration)) = plan.delay
|
||||
&& nth == frame_index
|
||||
{
|
||||
tokio::select! {
|
||||
_ = cancel.cancelled() => break,
|
||||
_ = tokio::time::sleep(duration) => {}
|
||||
}
|
||||
}
|
||||
let copies = if plan.duplicate_frame == Some(frame_index) {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
};
|
||||
for _ in 0..copies {
|
||||
if write_frame(&mut writer, &len_prefix, &body).await.is_err() {
|
||||
return;
|
||||
}
|
||||
bump_forwarded(&handle, direction);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if write_frame(&mut writer, &len_prefix, &body).await.is_err() {
|
||||
return;
|
||||
}
|
||||
bump_forwarded(&handle, direction);
|
||||
}
|
||||
}
|
||||
|
||||
fn bump_forwarded(handle: &FaultHandle, direction: FaultDirection) {
|
||||
match direction {
|
||||
FaultDirection::ClientToLeader => {
|
||||
handle.state.forwarded_c2l.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
FaultDirection::LeaderToClient => {
|
||||
handle.state.forwarded_l2c.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Max frame body the proxy will buffer — mirrors the leader transport's own
|
||||
/// 64 MiB `MAX_MESSAGE_SIZE`, so a corrupt/mis-framed length surfaces as a
|
||||
/// readable pump error instead of a multi-GiB allocation.
|
||||
const MAX_FRAME_SIZE: usize = 64 * 1024 * 1024;
|
||||
|
||||
async fn read_frame(reader: &mut ReadHalf<UnixStream>) -> io::Result<([u8; 4], Vec<u8>)> {
|
||||
let mut len_prefix = [0u8; 4];
|
||||
reader.read_exact(&mut len_prefix).await?;
|
||||
let len = u32::from_be_bytes(len_prefix) as usize;
|
||||
if len > MAX_FRAME_SIZE {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("frame length {len} exceeds MAX_FRAME_SIZE ({MAX_FRAME_SIZE})"),
|
||||
));
|
||||
}
|
||||
let mut body = vec![0u8; len];
|
||||
reader.read_exact(&mut body).await?;
|
||||
Ok((len_prefix, body))
|
||||
}
|
||||
|
||||
async fn write_frame(
|
||||
writer: &mut WriteHalf<UnixStream>,
|
||||
len_prefix: &[u8; 4],
|
||||
body: &[u8],
|
||||
) -> io::Result<()> {
|
||||
writer.write_all(len_prefix).await?;
|
||||
writer.write_all(body).await?;
|
||||
writer.flush().await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::TempDir;
|
||||
|
||||
async fn client_write_frame(stream: &mut UnixStream, body: &[u8]) {
|
||||
stream
|
||||
.write_all(&(body.len() as u32).to_be_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
stream.write_all(body).await.unwrap();
|
||||
stream.flush().await.unwrap();
|
||||
}
|
||||
|
||||
async fn client_read_frame(stream: &mut UnixStream) -> io::Result<Vec<u8>> {
|
||||
let mut len = [0u8; 4];
|
||||
stream.read_exact(&mut len).await?;
|
||||
let mut body = vec![0u8; u32::from_be_bytes(len) as usize];
|
||||
stream.read_exact(&mut body).await?;
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
/// Upstream that echoes every frame back to the sender.
|
||||
fn spawn_echo_upstream(path: PathBuf) {
|
||||
let listener = UnixListener::bind(&path).unwrap();
|
||||
tokio::spawn(async move {
|
||||
while let Ok((mut stream, _)) = listener.accept().await {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let mut len = [0u8; 4];
|
||||
if stream.read_exact(&mut len).await.is_err() {
|
||||
break;
|
||||
}
|
||||
let mut body = vec![0u8; u32::from_be_bytes(len) as usize];
|
||||
if stream.read_exact(&mut body).await.is_err() {
|
||||
break;
|
||||
}
|
||||
if stream.write_all(&len).await.is_err()
|
||||
|| stream.write_all(&body).await.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
let _ = stream.flush().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn passes_frames_through_untouched() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let upstream_path = temp.path().join("real.sock");
|
||||
spawn_echo_upstream(upstream_path.clone());
|
||||
let proxy = UdsProxy::spawn(
|
||||
temp.path().join("proxy.sock"),
|
||||
&upstream_path,
|
||||
FaultPlan::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut client = UnixStream::connect(&proxy.proxy_path).await.unwrap();
|
||||
for payload in [b"one".as_slice(), b"two", b"three"] {
|
||||
client_write_frame(&mut client, payload).await;
|
||||
assert_eq!(client_read_frame(&mut client).await.unwrap(), payload);
|
||||
}
|
||||
assert_eq!(proxy.handle().forwarded(FaultDirection::ClientToLeader), 3);
|
||||
assert_eq!(proxy.handle().forwarded(FaultDirection::LeaderToClient), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn drops_exactly_the_nth_frame() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let upstream_path = temp.path().join("real.sock");
|
||||
spawn_echo_upstream(upstream_path.clone());
|
||||
let proxy = UdsProxy::spawn(
|
||||
temp.path().join("proxy.sock"),
|
||||
&upstream_path,
|
||||
FaultPlan {
|
||||
drop_frame: Some(2),
|
||||
..FaultPlan::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut client = UnixStream::connect(&proxy.proxy_path).await.unwrap();
|
||||
client_write_frame(&mut client, b"first").await;
|
||||
client_write_frame(&mut client, b"second").await;
|
||||
client_write_frame(&mut client, b"third").await;
|
||||
|
||||
// The echo of "second" never arrives; "third" comes straight after "first".
|
||||
assert_eq!(client_read_frame(&mut client).await.unwrap(), b"first");
|
||||
assert_eq!(client_read_frame(&mut client).await.unwrap(), b"third");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn duplicates_exactly_the_nth_frame() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let upstream_path = temp.path().join("real.sock");
|
||||
spawn_echo_upstream(upstream_path.clone());
|
||||
let proxy = UdsProxy::spawn(
|
||||
temp.path().join("proxy.sock"),
|
||||
&upstream_path,
|
||||
FaultPlan {
|
||||
duplicate_frame: Some(1),
|
||||
..FaultPlan::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut client = UnixStream::connect(&proxy.proxy_path).await.unwrap();
|
||||
client_write_frame(&mut client, b"once").await;
|
||||
assert_eq!(client_read_frame(&mut client).await.unwrap(), b"once");
|
||||
assert_eq!(client_read_frame(&mut client).await.unwrap(), b"once");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn severs_mid_frame_and_closes_both_sides() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let upstream_path = temp.path().join("real.sock");
|
||||
spawn_echo_upstream(upstream_path.clone());
|
||||
let proxy = UdsProxy::spawn(
|
||||
temp.path().join("proxy.sock"),
|
||||
&upstream_path,
|
||||
FaultPlan {
|
||||
sever_mid_frame: Some(1),
|
||||
..FaultPlan::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut client = UnixStream::connect(&proxy.proxy_path).await.unwrap();
|
||||
client_write_frame(&mut client, b"never-delivered").await;
|
||||
|
||||
// The upstream got 2 bytes of a length prefix and then a close, so it
|
||||
// echoes nothing; the client's next read observes the sever.
|
||||
let read = client_read_frame(&mut client).await;
|
||||
assert!(
|
||||
read.is_err(),
|
||||
"sever must close the client side, got {read:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delays_exactly_the_nth_frame() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let upstream_path = temp.path().join("real.sock");
|
||||
spawn_echo_upstream(upstream_path.clone());
|
||||
let delay = Duration::from_millis(300);
|
||||
let proxy = UdsProxy::spawn(
|
||||
temp.path().join("proxy.sock"),
|
||||
&upstream_path,
|
||||
FaultPlan {
|
||||
delay: Some((1, delay)),
|
||||
..FaultPlan::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut client = UnixStream::connect(&proxy.proxy_path).await.unwrap();
|
||||
let started = std::time::Instant::now();
|
||||
client_write_frame(&mut client, b"held").await;
|
||||
assert_eq!(client_read_frame(&mut client).await.unwrap(), b"held");
|
||||
assert!(
|
||||
started.elapsed() >= delay,
|
||||
"frame must be held for the configured delay"
|
||||
);
|
||||
|
||||
// Only the Nth frame is delayed; the next one is immediate.
|
||||
let started = std::time::Instant::now();
|
||||
client_write_frame(&mut client, b"quick").await;
|
||||
assert_eq!(client_read_frame(&mut client).await.unwrap(), b"quick");
|
||||
assert!(started.elapsed() < delay);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sever_now_drops_active_connections() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let upstream_path = temp.path().join("real.sock");
|
||||
spawn_echo_upstream(upstream_path.clone());
|
||||
let proxy = UdsProxy::spawn(
|
||||
temp.path().join("proxy.sock"),
|
||||
&upstream_path,
|
||||
FaultPlan::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut client = UnixStream::connect(&proxy.proxy_path).await.unwrap();
|
||||
client_write_frame(&mut client, b"alive").await;
|
||||
assert_eq!(client_read_frame(&mut client).await.unwrap(), b"alive");
|
||||
|
||||
proxy.handle().sever_now();
|
||||
let read = client_read_frame(&mut client).await;
|
||||
assert!(
|
||||
read.is_err(),
|
||||
"sever_now must close the proxied connection, got {read:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user