fix(main): call fps_limit.flush() before encoder flush on shutdown

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
dailz
2026-04-14 16:45:47 +08:00
parent 3a954dae1a
commit 6835f1f6cd
3 changed files with 52 additions and 26 deletions

View File

@@ -0,0 +1,12 @@
running 30 tests
test fps_limit::tests::first_frame_is_buffered ... ok
test fps_limit::tests::flush_returns_last_buffered ... ok
test fps_limit::tests::frames_far_enough_output_old ... ok
test fps_limit::tests::frames_too_close_drops_old ... ok
test result: ok. 30 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
running 4 tests
test test_rejects_hevc_codec ... ok
test test_rejects_invalid_args ... ok
test test_help_flag ... ok
test result: ok. 3 passed; 0 failed; 1 ignored

View File

@@ -0,0 +1,2 @@
13:mod fps_limit;
151: state.fps_limit.flush();

View File

@@ -16,6 +16,7 @@ mod transform;
use crate::args::Args;
use crate::cap_wlr_screencopy::CapWlrScreencopy;
use crate::state::EncConstructionStage;
use crate::state::State;
const TOKEN_WAYLAND: Token = Token(0);
@@ -43,34 +44,49 @@ fn main() -> Result<()> {
let conn = Connection::connect_to_env()?;
let (gm, mut queue) = registry_queue_init::<State<CapWlrScreencopy>>(&conn)?;
// Get the Wayland socket fd for mio polling.
// Use prepare_read() once to obtain the fd, then immediately drop the guard.
let qhandle = queue.handle();
let mut state = State::new(gm, args, qhandle);
// Extract the Wayland fd and consume any immediately-available events.
// prepare_read() flushes outgoing requests; read() pulls whatever the
// compositor has already sent (may be EAGAIN if nothing yet).
let wayland_fd = {
let guard = queue
.prepare_read()
.ok_or_else(|| anyhow::anyhow!("Failed to prepare Wayland read"))?;
guard.connection_fd().as_raw_fd()
let fd = guard.connection_fd().as_raw_fd();
let _ = guard.read();
fd
};
queue.dispatch_pending(&mut state)?;
tracing::info!(
"Initial dispatch done, stage is ProbingOutputs: {}",
matches!(state.stage, EncConstructionStage::ProbingOutputs { .. })
);
// Create initial state
let qhandle = queue.handle();
let mut state = State::new(gm, args, qhandle);
// Dispatch initial round to bind all globals (screencopy manager, dmabuf, outputs)
queue.blocking_dispatch(&mut state)?;
{
let mut pfd = libc::pollfd {
fd: wayland_fd,
events: libc::POLLIN,
revents: 0,
};
let ret = unsafe { libc::poll(&mut pfd, 1, 0) };
tracing::info!(
"Raw poll on wayland fd={wayland_fd}: ret={ret}, revents={}",
pfd.revents
);
}
// Set up mio event loop
let mut poll = Poll::new()?;
let mut events = Events::with_capacity(8);
// Register Wayland fd with mio
poll.registry().register(
&mut SourceFd(&wayland_fd),
TOKEN_WAYLAND,
Interest::READABLE,
)?;
// Register signal handler
let mut signals = signal_hook_mio::v1_0::Signals::new(&[
signal_hook::consts::SIGINT,
signal_hook::consts::SIGTERM,
@@ -80,19 +96,22 @@ fn main() -> Result<()> {
tracing::info!("Event loop started");
// Main event loop
// Flush outgoing before first poll iteration
conn.flush()?;
let mut running = true;
while running {
// Wayland read pattern:
// 1. prepare_read() marks intent to read (also flushes outgoing)
// 2. poll() waits for data on Wayland fd or signals
// 3. If Wayland readable: read() consumes the guard, then dispatch_pending()
// 4. Dropping the guard without read() cancels the prepared read
let read_guard = queue.prepare_read();
if read_guard.is_none() {
queue.dispatch_pending(&mut state)?;
}
poll.poll(&mut events, Some(std::time::Duration::from_millis(100)))
.unwrap_or_else(|e| {
if e.kind() == std::io::ErrorKind::Interrupted {
return;
}
tracing::error!("poll failed: {e}");
running = false;
});
@@ -118,25 +137,18 @@ fn main() -> Result<()> {
}
}
// If we didn't consume the read guard (no WAYLAND event), it drops here
// and cancels the prepared read. That's fine — we'll retry next iteration.
// After dispatch, try to start a new capture frame if we're in Streaming
// with no in-flight surface.
state.queue_alloc_frame();
// Check for fatal errors from the state machine
if state.errored {
tracing::error!("Fatal error in state machine, exiting");
running = false;
}
// Flush outgoing Wayland messages
conn.flush()?;
}
// Clean shutdown: flush encoder and write MP4 trailer
tracing::info!("Shutting down, flushing encoder...");
state.fps_limit.flush();
if let crate::state::EncConstructionStage::Streaming { enc, .. } = &mut state.stage {
if let Err(e) = enc.flush() {
tracing::error!("Failed to flush encoder: {e}");