Attempt 4...
This commit is contained in:
parent
4dcb10d9f1
commit
eee56bc5f4
1 changed files with 69 additions and 28 deletions
97
src/main.rs
97
src/main.rs
|
|
@ -116,6 +116,22 @@ fn is_eagain_message(msg: &str) -> bool {
|
||||||
|| lowered.contains("wouldblock")
|
|| lowered.contains("wouldblock")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transient errors from `session.open()` that should be retried with event
|
||||||
|
/// loop pumping:
|
||||||
|
///
|
||||||
|
/// • **EAGAIN** – libseat's seat manager hasn't finished processing a prior
|
||||||
|
/// request. Well-documented; all compositors retry.
|
||||||
|
///
|
||||||
|
/// • **EACCES / EPERM** – On logind-managed sessions (common with Ly, greetd,
|
||||||
|
/// and similar display managers) the session activation inside logind may
|
||||||
|
/// lag behind libseat's own `is_active()` flag. logind's `TakeDevice`
|
||||||
|
/// D-Bus method returns EACCES until it has fully activated the session on
|
||||||
|
/// the foreground VT. Pumping the event loop lets the remaining activation
|
||||||
|
/// messages propagate, after which the retry succeeds.
|
||||||
|
fn is_transient_open_error(msg: &str) -> bool {
|
||||||
|
is_eagain_message(msg) || is_permission_denied_message(msg)
|
||||||
|
}
|
||||||
|
|
||||||
fn enumerate_drm_card_paths() -> Vec<std::path::PathBuf> {
|
fn enumerate_drm_card_paths() -> Vec<std::path::PathBuf> {
|
||||||
let mut paths: Vec<std::path::PathBuf> = std::fs::read_dir("/dev/dri")
|
let mut paths: Vec<std::path::PathBuf> = std::fs::read_dir("/dev/dri")
|
||||||
.ok()
|
.ok()
|
||||||
|
|
@ -556,6 +572,11 @@ fn run_drm(config: Config) -> Result<(), anyhow::Error> {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
info!("LibSeat session created successfully");
|
info!("LibSeat session created successfully");
|
||||||
|
info!(
|
||||||
|
seat = %session.seat(),
|
||||||
|
libseat_backend = %std::env::var("LIBSEAT_BACKEND").unwrap_or_else(|_| "auto (not set)".into()),
|
||||||
|
"libseat environment"
|
||||||
|
);
|
||||||
let mut session = session;
|
let mut session = session;
|
||||||
|
|
||||||
// Create the event loop early—before opening any DRM devices.
|
// Create the event loop early—before opening any DRM devices.
|
||||||
|
|
@ -696,27 +717,34 @@ fn run_drm(config: Config) -> Result<(), anyhow::Error> {
|
||||||
// handling through the dispatch/retry loop below.
|
// handling through the dispatch/retry loop below.
|
||||||
let open_flags = OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY;
|
let open_flags = OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY;
|
||||||
|
|
||||||
// Round-robin EAGAIN retry across all GPU candidates.
|
// Round-robin retry across all GPU candidates for transient errors.
|
||||||
//
|
//
|
||||||
// The old per-GPU retry loop had a structural flaw: EAGAIN from libseat is
|
// Both EAGAIN and EACCES/EPERM are retried with event-loop pumping:
|
||||||
// a seat-level condition (the seat manager hasn't finished processing an
|
|
||||||
// earlier request), not a GPU-level one. If every candidate gets EAGAIN the
|
|
||||||
// old loop would spend its entire 500 ms budget on the first GPU and then
|
|
||||||
// immediately abandon all remaining ones without any further dispatching.
|
|
||||||
//
|
//
|
||||||
// The new approach keeps a "pending" list of candidates that returned EAGAIN.
|
// • EAGAIN: libseat's seat manager hasn't finished processing an earlier
|
||||||
// After each global round it pumps the event loop once (50 ms) and retries
|
// request — a seat-level condition, not GPU-specific.
|
||||||
// all pending candidates. Up to MAX_EAGAIN_ROUNDS × 50 ms ≤ 1 s total.
|
//
|
||||||
const MAX_EAGAIN_ROUNDS: u32 = 20;
|
// • EACCES/EPERM: On logind-managed sessions (e.g. launched by Ly, greetd)
|
||||||
let mut eagain_pending: Vec<std::path::PathBuf> = gpu_candidates;
|
// the session activation inside logind can lag behind libseat's own
|
||||||
'outer: for round in 0..=MAX_EAGAIN_ROUNDS {
|
// is_active() flag. logind's TakeDevice D-Bus call returns EACCES until
|
||||||
let mut still_eagain: Vec<std::path::PathBuf> = Vec::new();
|
// it has fully activated the session on the foreground VT. Pumping the
|
||||||
for gpu_path in eagain_pending {
|
// event loop lets the remaining activation messages propagate.
|
||||||
|
//
|
||||||
|
// After each round the loop dispatches the event loop for 50 ms and retries
|
||||||
|
// all still-pending candidates. Budget: MAX_TRANSIENT_ROUNDS × 50 ms ≈ 2 s.
|
||||||
|
const MAX_TRANSIENT_ROUNDS: u32 = 40;
|
||||||
|
let mut transient_pending: Vec<std::path::PathBuf> = gpu_candidates;
|
||||||
|
'outer: for round in 0..=MAX_TRANSIENT_ROUNDS {
|
||||||
|
let mut still_transient: Vec<std::path::PathBuf> = Vec::new();
|
||||||
|
for gpu_path in transient_pending {
|
||||||
if round == 0 {
|
if round == 0 {
|
||||||
info!("Opening DRM device: {:?}", gpu_path);
|
info!("Opening DRM device: {:?}", gpu_path);
|
||||||
}
|
}
|
||||||
match session.open(&gpu_path, open_flags) {
|
match session.open(&gpu_path, open_flags) {
|
||||||
Ok(fd) => {
|
Ok(fd) => {
|
||||||
|
if round > 0 {
|
||||||
|
info!(gpu = %gpu_path.display(), round, "device opened after transient retries");
|
||||||
|
}
|
||||||
info!(gpu = %gpu_path.display(), "using GPU");
|
info!(gpu = %gpu_path.display(), "using GPU");
|
||||||
chosen_gpu_path = Some(gpu_path);
|
chosen_gpu_path = Some(gpu_path);
|
||||||
chosen_drm_fd = Some(fd);
|
chosen_drm_fd = Some(fd);
|
||||||
|
|
@ -724,41 +752,54 @@ fn run_drm(config: Config) -> Result<(), anyhow::Error> {
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let detail = e.to_string();
|
let detail = e.to_string();
|
||||||
if is_eagain_message(&detail) {
|
if is_transient_open_error(&detail) {
|
||||||
if round == 0 {
|
if round == 0 {
|
||||||
|
let kind = if is_eagain_message(&detail) {
|
||||||
|
"EAGAIN"
|
||||||
|
} else {
|
||||||
|
"permission denied (transient on logind sessions)"
|
||||||
|
};
|
||||||
info!(
|
info!(
|
||||||
gpu = %gpu_path.display(),
|
gpu = %gpu_path.display(),
|
||||||
"libseat returned EAGAIN; will retry after event loop pump..."
|
"libseat returned {kind}; will retry after event loop pump..."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
still_eagain.push(gpu_path);
|
still_transient.push(gpu_path);
|
||||||
} else {
|
} else {
|
||||||
let is_perm = is_permission_denied_message(&detail);
|
|
||||||
warn!(
|
warn!(
|
||||||
gpu = %gpu_path.display(),
|
gpu = %gpu_path.display(),
|
||||||
permission_related = is_perm,
|
|
||||||
round,
|
round,
|
||||||
"Failed to open DRM device: {detail}"
|
"Failed to open DRM device (non-transient): {detail}"
|
||||||
);
|
);
|
||||||
open_errors.push(format!("{}: {detail}", gpu_path.display()));
|
open_errors.push(format!("{}: {detail}", gpu_path.display()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eagain_pending = still_eagain;
|
transient_pending = still_transient;
|
||||||
if eagain_pending.is_empty() {
|
if transient_pending.is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if round < MAX_EAGAIN_ROUNDS {
|
if round < MAX_TRANSIENT_ROUNDS {
|
||||||
let _ = event_loop.dispatch(Some(Duration::from_millis(50)), &mut pre_init);
|
let _ = event_loop.dispatch(Some(Duration::from_millis(50)), &mut pre_init);
|
||||||
} else {
|
} else {
|
||||||
// Final round exhausted; record EAGAIN as a permanent failure for
|
// Final round exhausted; log diagnostics for each remaining candidate.
|
||||||
// each remaining candidate so the error message is informative.
|
for gpu_path in &transient_pending {
|
||||||
for gpu_path in &eagain_pending {
|
let perms_hint = match std::fs::metadata(gpu_path) {
|
||||||
|
Ok(meta) => {
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
format!("(device node mode={:#o}, uid={}, gid={})",
|
||||||
|
meta.mode(), meta.uid(), meta.gid())
|
||||||
|
}
|
||||||
|
Err(e) => format!("(could not stat device: {e})"),
|
||||||
|
};
|
||||||
open_errors.push(format!(
|
open_errors.push(format!(
|
||||||
"{}: libseat returned EAGAIN after {} retries (seat manager unresponsive?)",
|
"{}: transient open error persisted after {} retries {}. \
|
||||||
|
If this is EACCES, ensure Ly's PAM config includes pam_systemd.so \
|
||||||
|
and the logind session is active (`loginctl session-status`)",
|
||||||
gpu_path.display(),
|
gpu_path.display(),
|
||||||
MAX_EAGAIN_ROUNDS,
|
MAX_TRANSIENT_ROUNDS,
|
||||||
|
perms_hint,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue