mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-09-20 11:23:19 +03:00
147 lines
5.3 KiB
Rust
147 lines
5.3 KiB
Rust
//! Reproduction for issue #478: "Too many references: cannot splice (os error 109)",
|
|
//! which is ETOOMANYREFS, on a wlroots compositor.
|
|
//!
|
|
//! `State::add_client` creates a virtual keyboard per emulation client and hands the
|
|
//! compositor a keymap file descriptor. It is the same descriptor every time, and
|
|
//! Wayland caps descriptors per sendmsg (MAX_FDS_OUT), so under client churn they
|
|
//! queue faster than the compositor drains them until the send is refused.
|
|
//!
|
|
//! Run under a wlroots compositor:
|
|
//! cargo run --release -p input-emulation --example keymap_fd_churn
|
|
//! cargo run --release -p input-emulation --example keymap_fd_churn -- --control
|
|
//!
|
|
//! Churn mode creates one client per iteration. Control mode holds the client count
|
|
//! at one and sends the same number of events, which is what distinguishes client
|
|
//! creation from the event path.
|
|
//!
|
|
//! The descriptor limit matters. `too_many_unix_fds()` compares the USER's total
|
|
//! in-flight SCM_RIGHTS count against the SENDER's RLIMIT_NOFILE, so a limit below
|
|
//! the machine's ambient in-flight count fails at iteration 0 regardless of this bug,
|
|
//! and a limit far above it never trips. On the machine this was written on, ambient
|
|
//! sits near 1024 and `ulimit -n 2048` fails at about iteration 900 before the fix and
|
|
//! runs clean after it.
|
|
//!
|
|
//! Motion events are sent with dx and dy of zero so the example never moves the real
|
|
//! cursor.
|
|
|
|
#[cfg(wlroots)]
|
|
use input_emulation::{Backend, EmulationHandle, InputEmulation};
|
|
#[cfg(wlroots)]
|
|
use input_event::{Event, PointerEvent};
|
|
|
|
/// number of simulated peer reconnects
|
|
#[cfg(wlroots)]
|
|
const ITERATIONS: u64 = 2000;
|
|
|
|
/// how often to sample the descriptor count
|
|
#[cfg(wlroots)]
|
|
const SAMPLE_EVERY: u64 = 100;
|
|
|
|
/// counts open descriptors of this process.
|
|
/// returns None rather than 0 when the count itself fails for lack of a descriptor,
|
|
/// so an exhausted process is never reported as using none.
|
|
#[cfg(wlroots)]
|
|
fn open_fds() -> Option<usize> {
|
|
std::fs::read_dir("/proc/self/fd").ok().map(|d| d.count())
|
|
}
|
|
|
|
#[cfg(wlroots)]
|
|
fn fds_display(fds: Option<usize>) -> String {
|
|
match fds {
|
|
Some(n) => n.to_string(),
|
|
None => "unreadable (process out of descriptors)".to_string(),
|
|
}
|
|
}
|
|
|
|
#[cfg(wlroots)]
|
|
fn main() {
|
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
.enable_all()
|
|
.build()
|
|
.expect("failed to build runtime");
|
|
|
|
runtime.block_on(async {
|
|
let mut emulation = match InputEmulation::new(Some(Backend::Wlroots)).await {
|
|
Ok(e) => e,
|
|
Err(e) => {
|
|
eprintln!("could not create wlroots emulation backend: {e}");
|
|
eprintln!("this example requires a running wlroots compositor");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
// control mode reuses a single client, so only the event path runs.
|
|
// if descriptors stay flat here but climb in the default mode, client
|
|
// creation is the source and the event path is exonerated.
|
|
let control = std::env::args().any(|a| a == "--control");
|
|
|
|
let baseline = open_fds().unwrap_or(0);
|
|
println!(
|
|
"mode: {}",
|
|
if control {
|
|
"control (one client, N events)"
|
|
} else {
|
|
"churn (N clients)"
|
|
}
|
|
);
|
|
println!("baseline open fds: {baseline}");
|
|
println!("running {ITERATIONS} iterations");
|
|
|
|
if control {
|
|
emulation.create(0).await;
|
|
}
|
|
|
|
for i in 0..ITERATIONS {
|
|
let handle = if control { 0 } else { i as EmulationHandle };
|
|
|
|
if !control {
|
|
// mirrors do_emulation_session: an unseen peer address creates a client
|
|
emulation.create(handle).await;
|
|
}
|
|
|
|
// a zero motion event is harmless but forces a flush of the queued keymap fd
|
|
let event = Event::Pointer(PointerEvent::Motion {
|
|
time: 0,
|
|
dx: 0.0,
|
|
dy: 0.0,
|
|
});
|
|
|
|
if let Err(e) = emulation.consume(event, handle).await {
|
|
println!();
|
|
println!("FAILED at iteration {i}");
|
|
println!(
|
|
"open fds: {} (baseline {baseline})",
|
|
fds_display(open_fds())
|
|
);
|
|
println!("error: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
|
|
if i % SAMPLE_EVERY == 0 {
|
|
let now = open_fds();
|
|
let delta = now.map(|n| n.saturating_sub(baseline));
|
|
println!(
|
|
" iteration {i:>5}: open fds {:>6}{}",
|
|
fds_display(now),
|
|
delta
|
|
.map(|d| format!(" (+{d} over baseline)"))
|
|
.unwrap_or_default(),
|
|
);
|
|
}
|
|
}
|
|
|
|
let final_fds = open_fds();
|
|
println!();
|
|
println!("completed {ITERATIONS} iterations without error");
|
|
println!("open fds: {} (baseline {baseline})", fds_display(final_fds));
|
|
emulation.terminate().await;
|
|
});
|
|
}
|
|
|
|
#[cfg(not(wlroots))]
|
|
fn main() {
|
|
eprintln!("this example requires the wlroots backend, which is only built on");
|
|
eprintln!("unix targets other than macos with the `wlroots` feature enabled");
|
|
std::process::exit(1);
|
|
}
|