Skip to content

Commit 2540d4e

Browse files
FeilixXclaude
andcommitted
feat: IPC lifecycle redesign — per-PID sockets, flock guard, daemon.addr discovery
Replace fragile PID lockfile + fixed socket/pipe names with: - Per-PID unique addresses (daemon-{pid}.sock / \.\pipe\punkgo-kernel-{pid}) - flock on daemon.addr for single-instance guard (OS auto-releases on crash) - daemon.addr file for service discovery (pid + endpoint) - --replace flag for graceful daemon replacement via IPC shutdown command Eliminates stale socket "Address already in use" (macOS) and "Access Denied" (Windows) errors after unclean daemon shutdown. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7ea2df3 commit 2540d4e

8 files changed

Lines changed: 455 additions & 60 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ All notable changes to PunkGo Kernel will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.4.0] - 2026-03-13
9+
10+
### Changed
11+
12+
- **IPC lifecycle redesign** — daemon now uses per-PID socket/pipe names (`daemon-{pid}.sock` / `\\.\pipe\punkgo-kernel-{pid}`), eliminating stale socket/pipe issues after crashes on all platforms
13+
- **Single-instance guard** — replaced PID lockfile with `flock` on `daemon.addr` (auto-released on process death by OS)
14+
- **Service discovery** — daemon writes `daemon.addr` file with PID + endpoint; clients read it to find the daemon
15+
- **`--replace` flag** — gracefully stops old daemon via IPC shutdown command, then takes over
16+
- **IPC shutdown command**`kind: "shutdown"` request triggers graceful daemon shutdown
17+
18+
### Removed
19+
20+
- PID lockfile (`daemon.pid`) — superseded by flock on `daemon.addr`
21+
- Interactive "Kill it? [y/N]" prompt — replaced by `--replace` flag
22+
823
## [0.3.0] - 2026-03-13
924

1025
### Fixed

Cargo.lock

Lines changed: 36 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ members = [
77
resolver = "2"
88

99
[workspace.package]
10-
version = "0.3.0"
10+
version = "0.4.0"
1111
edition = "2024"
1212
authors = ["Felix <feijiu@punkgo.ai>"]
1313
license = "MIT"
@@ -32,3 +32,4 @@ toml = "0.8"
3232
tempfile = "3.16"
3333
uuid = { version = "1.21.0", features = ["v4", "serde"] }
3434
interprocess = { version = "2.4", features = ["tokio"] }
35+
fs2 = "0.4"

crates/punkgo-kernel/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ default = []
1717
testkit = []
1818

1919
[dependencies]
20-
punkgo-core = { path = "../punkgo-core", version = "0.3.0" }
20+
punkgo-core = { path = "../punkgo-core", version = "0.4.0" }
2121
anyhow.workspace = true
2222
async-trait.workspace = true
2323
serde.workspace = true
@@ -31,6 +31,7 @@ tracing.workspace = true
3131
tracing-subscriber.workspace = true
3232
uuid.workspace = true
3333
interprocess.workspace = true
34+
fs2.workspace = true
3435

3536
[dev-dependencies]
3637
tempfile.workspace = true

crates/punkgo-kernel/src/daemon/ipc.rs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use interprocess::local_socket::{
44
GenericFilePath, GenericNamespaced, ListenerOptions, Name, ToFsName, ToNsName,
55
traits::tokio::Listener as _,
66
};
7-
use punkgo_core::protocol::{RequestEnvelope, ResponseEnvelope};
7+
use punkgo_core::protocol::{RequestEnvelope, RequestType, ResponseEnvelope};
88
use punkgo_kernel::Kernel;
9+
use serde_json::json;
910
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader};
1011
use tracing::{error, info, warn};
1112

@@ -17,23 +18,32 @@ fn endpoint_to_name(endpoint: &str) -> std::io::Result<Name<'_>> {
1718
}
1819
}
1920

20-
pub async fn run_ipc_server(kernel: Arc<Kernel>, endpoint: &str) -> std::io::Result<()> {
21+
pub async fn run_ipc_server(
22+
kernel: Arc<Kernel>,
23+
endpoint: &str,
24+
shutdown_tx: tokio::sync::watch::Sender<bool>,
25+
) -> std::io::Result<()> {
2126
let name = endpoint_to_name(endpoint)?;
2227
let listener = ListenerOptions::new().name(name).create_tokio()?;
2328
info!(endpoint = endpoint, "IPC server listening");
2429

2530
loop {
2631
let conn = listener.accept().await?;
2732
let kernel = Arc::clone(&kernel);
33+
let shutdown_tx = shutdown_tx.clone();
2834
tokio::spawn(async move {
29-
if let Err(err) = handle_connection(conn, kernel).await {
35+
if let Err(err) = handle_connection(conn, kernel, shutdown_tx).await {
3036
warn!(error = %err, "IPC client disconnected with error");
3137
}
3238
});
3339
}
3440
}
3541

36-
async fn handle_connection<S>(stream: S, kernel: Arc<Kernel>) -> std::io::Result<()>
42+
async fn handle_connection<S>(
43+
stream: S,
44+
kernel: Arc<Kernel>,
45+
shutdown_tx: tokio::sync::watch::Sender<bool>,
46+
) -> std::io::Result<()>
3747
where
3848
S: AsyncRead + AsyncWrite + Unpin,
3949
{
@@ -58,6 +68,25 @@ where
5868
};
5969
info!(request_id = %request.request_id, "ipc request received");
6070

71+
// Check for shutdown command before dispatching to kernel.
72+
if matches!(request.request_type, RequestType::Read) {
73+
if let Some(kind) = request
74+
.payload
75+
.get("kind")
76+
.and_then(serde_json::Value::as_str)
77+
{
78+
if kind == "shutdown" {
79+
let response = ResponseEnvelope::ok(
80+
request.request_id.clone(),
81+
json!({"message": "shutting down"}),
82+
);
83+
write_response(&mut writer, &response).await?;
84+
let _ = shutdown_tx.send(true);
85+
return Ok(());
86+
}
87+
}
88+
}
89+
6190
let response = kernel.handle_request(request).await;
6291
if let Err(err) = write_response(&mut writer, &response).await {
6392
error!(error = %err, "failed to write IPC response");

0 commit comments

Comments
 (0)