Skip to content

Commit c14f41f

Browse files
committed
virtio-consoe: Flush port tx before exiting the vm
We should wait until the output currently in the queue of the guest is written and only then exit the libkrun process. This fixes an issue where you would sometimes not get the full output of a program inside the vm (we would call exit sooner, than we finished all the writes). Signed-off-by: Matej Hrica <[email protected]>
1 parent 29b6e8d commit c14f41f

File tree

6 files changed

+78
-14
lines changed

6 files changed

+78
-14
lines changed

src/devices/src/virtio/console/device.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::virtio::console::port::Port;
2424
use crate::virtio::console::port_queue_mapping::{
2525
num_queues, port_id_to_queue_idx, QueueDirection,
2626
};
27-
use crate::virtio::PortDescription;
27+
use crate::virtio::{PortDescription, VmmExitObserver};
2828

2929
pub(crate) const CONTROL_RXQ_INDEX: usize = 2;
3030
pub(crate) const CONTROL_TXQ_INDEX: usize = 3;
@@ -360,3 +360,13 @@ impl VirtioDevice for Console {
360360
}
361361
}
362362
}
363+
364+
impl VmmExitObserver for Console {
365+
fn on_vmm_exit(&mut self) {
366+
for port in &mut self.ports {
367+
port.flush();
368+
}
369+
370+
log::trace!("Console on_vmm_exit finished");
371+
}
372+
}

src/devices/src/virtio/console/port.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::borrow::Cow;
2+
use std::sync::atomic::{AtomicBool, Ordering};
23
use std::sync::Arc;
34
use std::thread::JoinHandle;
45
use std::{mem, thread};
@@ -33,6 +34,7 @@ enum PortState {
3334
output: Option<Box<dyn PortOutput + Send>>,
3435
},
3536
Active {
37+
stop: Arc<AtomicBool>,
3638
rx_thread: Option<JoinHandle<()>>,
3739
tx_thread: Option<JoinHandle<()>>,
3840
},
@@ -126,12 +128,36 @@ impl Port {
126128
thread::spawn(move || process_rx(mem, rx_queue, irq_signaler, input, control, port_id))
127129
});
128130

129-
let tx_thread = output
130-
.map(|output| thread::spawn(move || process_tx(mem, tx_queue, irq_signaler, output)));
131+
let stop = Arc::new(AtomicBool::new(false));
132+
let tx_thread = output.map(|output| {
133+
let stop = stop.clone();
134+
thread::spawn(move || process_tx(mem, tx_queue, irq_signaler, output, stop))
135+
});
131136

132137
self.state = PortState::Active {
138+
stop,
133139
rx_thread,
134140
tx_thread,
135141
}
136142
}
143+
144+
pub fn flush(&mut self) {
145+
if let PortState::Active {
146+
stop,
147+
tx_thread,
148+
rx_thread: _,
149+
} = &mut self.state
150+
{
151+
stop.store(true, Ordering::Release);
152+
if let Some(tx_thread) = mem::take(tx_thread) {
153+
tx_thread.thread().unpark();
154+
if let Err(e) = tx_thread.join() {
155+
log::error!(
156+
"Failed to flush tx for port {port_id}, thread panicked: {e:?}",
157+
port_id = self.port_id
158+
)
159+
}
160+
}
161+
};
162+
}
137163
}

src/devices/src/virtio/console/process_tx.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::sync::atomic::{AtomicBool, Ordering};
2+
use std::sync::Arc;
13
use std::{io, thread};
24

35
use vm_memory::{GuestMemory, GuestMemoryError, GuestMemoryMmap, GuestMemoryRegion};
@@ -11,9 +13,12 @@ pub(crate) fn process_tx(
1113
mut queue: Queue,
1214
irq: IRQSignaler,
1315
mut output: Box<dyn PortOutput + Send>,
16+
stop: Arc<AtomicBool>,
1417
) {
1518
loop {
16-
let head = pop_head_blocking(&mut queue, &mem, &irq);
19+
let Some(head) = pop_head_blocking(&mut queue, &mem, &irq, &stop) else {
20+
return;
21+
};
1722

1823
let head_index = head.index;
1924
let mut bytes_written = 0;
@@ -48,13 +53,17 @@ fn pop_head_blocking<'mem>(
4853
queue: &mut Queue,
4954
mem: &'mem GuestMemoryMmap,
5055
irq: &IRQSignaler,
51-
) -> DescriptorChain<'mem> {
56+
stop: &AtomicBool,
57+
) -> Option<DescriptorChain<'mem>> {
5258
loop {
5359
match queue.pop(mem) {
54-
Some(descriptor) => break descriptor,
60+
Some(descriptor) => break Some(descriptor),
5561
None => {
5662
irq.signal_used_queue("tx queue empty, parking");
5763
thread::park();
64+
if stop.load(Ordering::Acquire) {
65+
break None;
66+
}
5867
log::trace!("tx unparked, queue len {}", queue.len(mem))
5968
}
6069
}

src/devices/src/virtio/device.rs

+11
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,17 @@ pub trait VirtioDevice: AsAny + Send {
125125
}
126126
}
127127

128+
pub trait VmmExitObserver {
129+
/// Callback to finish processing or cleanup the device resources
130+
fn on_vmm_exit(&mut self) {}
131+
}
132+
133+
impl<F: Fn()> VmmExitObserver for F {
134+
fn on_vmm_exit(&mut self) {
135+
self()
136+
}
137+
}
138+
128139
impl std::fmt::Debug for dyn VirtioDevice {
129140
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
130141
write!(f, "VirtioDevice type {}", self.device_type())

src/vmm/src/builder.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -515,12 +515,12 @@ pub fn build_microvm(
515515
let shm_region = None;
516516

517517
let mut vmm = Vmm {
518-
//events_observer: Some(Box::new(SerialStdin::get())),
519518
guest_memory,
520519
arch_memory_info,
521520
kernel_cmdline,
522521
vcpus_handles: Vec::new(),
523522
exit_evt,
523+
exit_observers: Vec::new(),
524524
vm,
525525
mmio_device_manager,
526526
#[cfg(target_arch = "x86_64")]
@@ -603,6 +603,9 @@ pub fn build_microvm(
603603
vmm.start_vcpus(vcpus)
604604
.map_err(StartMicrovmError::Internal)?;
605605

606+
// Clippy thinks we don't need Arc<Mutex<...
607+
// but we don't want to change the event_manager interface
608+
#[allow(clippy::arc_with_non_send_sync)]
606609
let vmm = Arc::new(Mutex::new(vmm));
607610
event_manager
608611
.add_subscriber(vmm.clone())
@@ -1096,6 +1099,8 @@ fn attach_console_devices(
10961099

10971100
let console = Arc::new(Mutex::new(devices::virtio::Console::new(ports).unwrap()));
10981101

1102+
vmm.exit_observers.push(console.clone());
1103+
10991104
if let Some(intc) = intc {
11001105
console.lock().unwrap().set_intc(intc);
11011106
}

src/vmm/src/lib.rs

+10-7
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use macos::vstate;
3838
use std::fmt::{Display, Formatter};
3939
use std::io;
4040
use std::os::unix::io::AsRawFd;
41-
use std::sync::Mutex;
41+
use std::sync::{Arc, Mutex};
4242
#[cfg(target_os = "linux")]
4343
use std::time::Duration;
4444

@@ -52,6 +52,7 @@ use crate::vstate::{Vcpu, VcpuHandle, VcpuResponse, Vm};
5252
use arch::ArchMemoryInfo;
5353
use arch::DeviceType;
5454
use arch::InitrdConfig;
55+
use devices::virtio::VmmExitObserver;
5556
use devices::BusDevice;
5657
use kernel::cmdline::Cmdline as KernelCmdline;
5758
use polly::event_manager::{self, EventManager, Subscriber};
@@ -181,8 +182,6 @@ pub type Result<T> = std::result::Result<T, Error>;
181182

182183
/// Contains the state and associated methods required for the Firecracker VMM.
183184
pub struct Vmm {
184-
//events_observer: Option<Box<dyn VmmEventsObserver>>,
185-
186185
// Guest VM core resources.
187186
guest_memory: GuestMemoryMmap,
188187
arch_memory_info: ArchMemoryInfo,
@@ -192,6 +191,7 @@ pub struct Vmm {
192191
vcpus_handles: Vec<VcpuHandle>,
193192
exit_evt: EventFd,
194193
vm: Vm,
194+
exit_observers: Vec<Arc<Mutex<dyn VmmExitObserver>>>,
195195

196196
// Guest VM devices.
197197
mmio_device_manager: MMIODeviceManager,
@@ -213,10 +213,6 @@ impl Vmm {
213213
pub fn start_vcpus(&mut self, mut vcpus: Vec<Vcpu>) -> Result<()> {
214214
let vcpu_count = vcpus.len();
215215

216-
//if let Some(observer) = self.events_observer.as_mut() {
217-
// observer.on_vmm_boot().map_err(Error::VmmObserverInit)?;
218-
//}
219-
220216
Vcpu::register_kick_signal_handler();
221217

222218
self.vcpus_handles.reserve(vcpu_count);
@@ -336,6 +332,13 @@ impl Vmm {
336332
log::error!("Failed to restore terminal to canonical mode: {e}")
337333
}
338334

335+
for observer in &self.exit_observers {
336+
observer
337+
.lock()
338+
.expect("Poisoned mutex for exit observer")
339+
.on_vmm_exit();
340+
}
341+
339342
// Exit from Firecracker using the provided exit code. Safe because we're terminating
340343
// the process anyway.
341344
unsafe {

0 commit comments

Comments
 (0)