Skip to content

Commit 4d5cd37

Browse files
RoyLinRoyLin
authored andcommitted
fix: fix PTY stdin freeze and add run pull progress and improve size display
- Fix exec/shell -it hang on macOS: tokio::io::stdin() uses kqueue which does not generate readiness events for TTY fds in raw mode; replace with spawn_blocking + std::io::stdin() + mpsc channel so raw-mode keystrokes are reliably forwarded to the guest PTY - Add pull progress to `a3s-box run`: VmManager gains set_pull_progress_fn; layout.rs wires it into ImagePuller so runs that need to fetch a missing image print per-layer progress instead of silently hanging at "Creating box..." - Fix size display: small layers (< 1 MB) now show KB / B instead of "0.0 MB"; applies to both `pull` and `run` progress output - Bump version to 0.8.2
1 parent d6849f8 commit 4d5cd37

7 files changed

Lines changed: 75 additions & 20 deletions

File tree

src/Cargo.lock

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

src/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ members = [
1212
resolver = "2"
1313

1414
[workspace.package]
15-
version = "0.8.1"
15+
version = "0.8.2"
1616
edition = "2021"
1717
authors = ["A3S Lab Team"]
1818
license = "MIT"

src/cli/src/commands/exec.rs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,6 @@ pub(crate) async fn run_pty_session(
194194
mut writer: a3s_transport::FrameWriter<tokio::io::WriteHalf<tokio::net::UnixStream>>,
195195
) -> i32 {
196196
use a3s_box_core::pty::{FRAME_PTY_DATA, FRAME_PTY_ERROR, FRAME_PTY_EXIT};
197-
use tokio::io::AsyncReadExt;
198197

199198
// Task 1: Read from guest PTY → write to stdout
200199
let reader_task = tokio::spawn(async move {
@@ -233,25 +232,44 @@ pub(crate) async fn run_pty_session(
233232
}
234233
});
235234

236-
// Task 2: Read from stdin + handle SIGWINCH → send frames to guest
235+
// Task 2: Read from stdin + handle SIGWINCH → send frames to guest.
236+
//
237+
// tokio::io::stdin() uses kqueue on macOS which does not generate
238+
// readiness events for TTY fds in raw mode, causing reads to block
239+
// indefinitely. Use a dedicated blocking thread via spawn_blocking +
240+
// an mpsc channel to relay stdin bytes into the async writer task.
237241
let writer_task = tokio::spawn(async move {
238-
let mut stdin = tokio::io::stdin();
239-
let mut buf = [0u8; 4096];
242+
let (tx, mut rx) = tokio::sync::mpsc::channel::<Vec<u8>>(64);
243+
244+
tokio::task::spawn_blocking(move || {
245+
use std::io::Read;
246+
let mut stdin = std::io::stdin();
247+
let mut buf = [0u8; 4096];
248+
loop {
249+
match stdin.read(&mut buf) {
250+
Ok(0) | Err(_) => break,
251+
Ok(n) => {
252+
if tx.blocking_send(buf[..n].to_vec()).is_err() {
253+
break;
254+
}
255+
}
256+
}
257+
}
258+
});
240259

241260
let mut sigwinch =
242261
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::window_change()).ok();
243262

244263
loop {
245264
tokio::select! {
246-
result = stdin.read(&mut buf) => {
247-
match result {
248-
Ok(0) => break,
249-
Ok(n) => {
250-
if writer.write_data(&buf[..n]).await.is_err() {
265+
data = rx.recv() => {
266+
match data {
267+
Some(bytes) => {
268+
if writer.write_data(&bytes).await.is_err() {
251269
break;
252270
}
253271
}
254-
Err(_) => break,
272+
None => break,
255273
}
256274
},
257275
_ = async {

src/cli/src/commands/pull.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,14 @@ pub async fn execute(args: PullArgs) -> Result<(), Box<dyn std::error::Error>> {
6161
println!("Pulling {}...", args.image);
6262
puller = puller.with_progress_fn(std::sync::Arc::new(|current, total, digest, size| {
6363
let short = &digest[digest.len().saturating_sub(12)..];
64-
let mb = size as f64 / 1_048_576.0;
65-
println!(" [{current}/{total}] {short}: {mb:.1} MB");
64+
let size_str = if size >= 1_048_576 {
65+
format!("{:.1} MB", size as f64 / 1_048_576.0)
66+
} else if size >= 1024 {
67+
format!("{:.1} KB", size as f64 / 1024.0)
68+
} else {
69+
format!("{} B", size)
70+
};
71+
println!(" [{current}/{total}] {short}: {size_str}");
6672
}));
6773
}
6874
let image = puller.pull(&args.image).await?;

src/cli/src/commands/run.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,22 @@ async fn setup_and_boot(args: &RunArgs) -> Result<RunContext, Box<dyn std::error
189189
&BoxRecord::make_short_id(&box_id)
190190
);
191191

192+
let image_name = args.common.image.clone();
193+
vm.set_pull_progress_fn(std::sync::Arc::new(move |current, total, digest, size| {
194+
if current == 1 {
195+
println!("Pulling {}...", image_name);
196+
}
197+
let short = &digest[digest.len().saturating_sub(12)..];
198+
let size_str = if size >= 1_048_576 {
199+
format!("{:.1} MB", size as f64 / 1_048_576.0)
200+
} else if size >= 1024 {
201+
format!("{:.1} KB", size as f64 / 1024.0)
202+
} else {
203+
format!("{} B", size)
204+
};
205+
println!(" [{current}/{total}] {short}: {size_str}");
206+
}));
207+
192208
connect_network(args.common.network.as_deref(), &box_id, &name)?;
193209
vm.boot().await?;
194210

src/runtime/src/vm/layout.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ impl VmManager {
6464
if let Some(ref m) = self.prom {
6565
puller = puller.set_metrics(m.clone());
6666
}
67+
if let Some(ref f) = self.pull_progress_fn {
68+
puller = puller.with_progress_fn(f.clone());
69+
}
6770

6871
tracing::info!(reference = %reference, "Pulling OCI image from registry");
6972

src/runtime/src/vm/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ pub struct VmManager {
114114

115115
/// Exit code captured from the shim process after it exits.
116116
pub(crate) shim_exit_code: Option<i32>,
117+
118+
/// Optional progress callback for image pulls: `(current, total, digest, size_bytes)`.
119+
pub(crate) pull_progress_fn: Option<Arc<dyn Fn(usize, usize, &str, i64) + Send + Sync>>,
117120
}
118121

119122
impl VmManager {
@@ -141,6 +144,7 @@ impl VmManager {
141144
pty_socket_path: None,
142145
prom: None,
143146
shim_exit_code: None,
147+
pull_progress_fn: None,
144148
}
145149
}
146150

@@ -167,6 +171,7 @@ impl VmManager {
167171
pty_socket_path: None,
168172
prom: None,
169173
shim_exit_code: None,
174+
pull_progress_fn: None,
170175
}
171176
}
172177

@@ -197,6 +202,7 @@ impl VmManager {
197202
pty_socket_path: None,
198203
prom: None,
199204
shim_exit_code: None,
205+
pull_progress_fn: None,
200206
}
201207
}
202208

@@ -247,6 +253,12 @@ impl VmManager {
247253
self.rootfs_provider.name()
248254
}
249255

256+
/// Set a progress callback for image pulls: `(current, total, digest, size_bytes)`.
257+
/// Called once per layer when `run` pulls an image that is not yet cached.
258+
pub fn set_pull_progress_fn(&mut self, f: Arc<dyn Fn(usize, usize, &str, i64) + Send + Sync>) {
259+
self.pull_progress_fn = Some(f);
260+
}
261+
250262
/// Attach Prometheus metrics to this VM manager.
251263
pub fn set_metrics(&mut self, metrics: crate::prom::RuntimeMetrics) {
252264
self.prom = Some(metrics);

0 commit comments

Comments
 (0)