general improvement and bug fixes, cursor scaling, DMA-BUF routing and nested backend recovery
ignore performance-baseline.md more improvement and code quality fixes
This commit is contained in:
1 parent
405c74af9f
commit
73f21e20ce
23 files changed
+2287
-457
No files matched your search
+122
-26
@@ -1,15 +1,25 @@
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
use serde_json::Value as Json;
|
||||
use tokio::sync::Notify;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Bridge(Arc<BridgeInner>);
|
||||
|
||||
struct Connection {
|
||||
stream: UnixStream,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
struct BridgeInner {
|
||||
stream: std::sync::Mutex<UnixStream>,
|
||||
connection: std::sync::Mutex<Connection>,
|
||||
shutdown: UnixStream,
|
||||
closed: AtomicBool,
|
||||
changed: Notify,
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
impl Bridge {
|
||||
@@ -19,19 +29,32 @@ impl Bridge {
|
||||
.ok_or("Missing runtime directory")?
|
||||
.join("ferese/control.sock");
|
||||
let stream = UnixStream::connect(path).map_err(|error| error.to_string())?;
|
||||
Self::from_stream(stream, Duration::from_secs(2))
|
||||
}
|
||||
|
||||
fn from_stream(stream: UnixStream, timeout: Duration) -> Result<Self, String> {
|
||||
stream
|
||||
.set_read_timeout(Some(Duration::from_secs(2)))
|
||||
.set_read_timeout(Some(timeout))
|
||||
.map_err(|error| error.to_string())?;
|
||||
stream
|
||||
.set_write_timeout(Some(Duration::from_secs(2)))
|
||||
.set_write_timeout(Some(timeout))
|
||||
.map_err(|error| error.to_string())?;
|
||||
let shutdown = stream.try_clone().map_err(|error| error.to_string())?;
|
||||
Ok(Self(Arc::new(BridgeInner {
|
||||
stream: std::sync::Mutex::new(stream),
|
||||
connection: std::sync::Mutex::new(Connection { stream, id: 0 }),
|
||||
shutdown,
|
||||
closed: AtomicBool::new(false),
|
||||
changed: Notify::new(),
|
||||
timeout,
|
||||
})))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn test_pair() -> (Self, UnixStream) {
|
||||
let (stream, peer) = UnixStream::pair().unwrap();
|
||||
(Self::from_stream(stream, Duration::from_secs(1)).unwrap(), peer)
|
||||
}
|
||||
|
||||
pub(crate) async fn call(&self, command: &'static str, args: Json) -> Result<Json, String> {
|
||||
self.request(command, args, false).await
|
||||
}
|
||||
@@ -43,28 +66,68 @@ impl Bridge {
|
||||
|
||||
async fn request(&self, command: &'static str, args: Json, wait: bool) -> Result<Json, String> {
|
||||
let bridge = self.clone();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut stream = bridge.0.stream.lock().map_err(|_| "Compositor connection failed")?;
|
||||
stream
|
||||
.set_read_timeout(if wait { None } else { Some(Duration::from_secs(2)) })
|
||||
.map_err(|error| error.to_string())?;
|
||||
match tokio::task::spawn_blocking(move || bridge.request_sync(command, args, wait)).await {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
self.close();
|
||||
Err(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn request_sync(&self, command: &'static str, args: Json, wait: bool) -> Result<Json, String> {
|
||||
let mut connection = match self.0.connection.lock() {
|
||||
Ok(connection) => connection,
|
||||
Err(_) => {
|
||||
self.close();
|
||||
return Err("Compositor connection failed".into());
|
||||
}
|
||||
};
|
||||
|
||||
let response = (|| {
|
||||
if self.is_closed() {
|
||||
return Err("Compositor connection is closed".to_owned());
|
||||
}
|
||||
|
||||
connection.id = connection.id.checked_add(1).ok_or("Compositor request IDs exhausted")?;
|
||||
let request = ferese_ipc::Request {
|
||||
version: ferese_ipc::VERSION,
|
||||
id: 1,
|
||||
id: connection.id,
|
||||
kind: "command".into(),
|
||||
command: command.into(),
|
||||
args,
|
||||
};
|
||||
ferese_ipc::write_frame(&mut *stream, &request).map_err(|error| error.to_string())?;
|
||||
let response: ferese_ipc::Response =
|
||||
ferese_ipc::read_frame(&mut *stream).map_err(|error| error.to_string())?;
|
||||
if let Some(error) = response.error {
|
||||
return Err(error.message);
|
||||
connection
|
||||
.stream
|
||||
.set_read_timeout(if wait { None } else { Some(self.0.timeout) })
|
||||
.map_err(|error| error.to_string())?;
|
||||
ferese_ipc::write_frame(&mut connection.stream, &request).map_err(|error| error.to_string())?;
|
||||
let value: Json = ferese_ipc::read_frame(&mut connection.stream).map_err(|error| error.to_string())?;
|
||||
let has_result = value.get("result").is_some();
|
||||
let response: ferese_ipc::Response = serde_json::from_value(value).map_err(|error| error.to_string())?;
|
||||
if response.id != request.id || response.version != ferese_ipc::VERSION {
|
||||
return Err("Unexpected compositor response ID or protocol version".into());
|
||||
}
|
||||
response.result.ok_or("Missing compositor response".into())
|
||||
})
|
||||
.await
|
||||
.map_err(|error| error.to_string())?
|
||||
|
||||
if has_result == response.error.is_some() {
|
||||
return Err("Invalid compositor response outcome".into());
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
})();
|
||||
|
||||
match response {
|
||||
Ok(response) => match response.error {
|
||||
Some(error) => Err(error.message),
|
||||
None => Ok(response.result.unwrap_or(Json::Null)),
|
||||
},
|
||||
Err(error) => {
|
||||
// Poison the stream before releasing the request lock so a
|
||||
// queued caller cannot read a late or partial response.
|
||||
self.close();
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn capture_watch(&self, session: u64) -> Result<Json, String> {
|
||||
@@ -72,24 +135,52 @@ impl Bridge {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) fn is_closed(&self) -> bool {
|
||||
self.0.closed.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub(crate) async fn terminated(&self) {
|
||||
let changed = self.0.changed.notified();
|
||||
tokio::pin!(changed);
|
||||
changed.as_mut().enable();
|
||||
if !self.is_closed() {
|
||||
changed.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn closed(&self) {
|
||||
use std::io::Read;
|
||||
let Ok(reader) = self.0.shutdown.try_clone() else {
|
||||
return;
|
||||
};
|
||||
if reader.set_nonblocking(true).is_err() {
|
||||
return;
|
||||
}
|
||||
let Ok(reader) = tokio::io::unix::AsyncFd::new(reader) else {
|
||||
return;
|
||||
};
|
||||
loop {
|
||||
let Ok(mut ready) = reader.readable().await else {
|
||||
let readable = tokio::select! {
|
||||
_ = self.terminated() => return,
|
||||
ready = reader.readable() => ready,
|
||||
};
|
||||
let Ok(mut ready) = readable else {
|
||||
return;
|
||||
};
|
||||
match ready.try_io(|reader| {
|
||||
let mut socket = reader.get_ref();
|
||||
socket.read(&mut [0_u8; 1])
|
||||
use std::os::fd::AsRawFd;
|
||||
let mut byte = 0u8;
|
||||
// Per-call nonblocking peek leaves the shared descriptor flags
|
||||
// and response bytes untouched for the request reader.
|
||||
let result = unsafe {
|
||||
libc::recv(
|
||||
reader.get_ref().as_raw_fd(),
|
||||
(&mut byte as *mut u8).cast(),
|
||||
1,
|
||||
libc::MSG_PEEK | libc::MSG_DONTWAIT,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
Err(std::io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(result)
|
||||
}
|
||||
}) {
|
||||
Ok(_) => return,
|
||||
Err(_) => continue,
|
||||
@@ -98,6 +189,11 @@ impl Bridge {
|
||||
}
|
||||
|
||||
pub(crate) fn close(&self) {
|
||||
self.0.closed.store(true, Ordering::SeqCst);
|
||||
let _ = self.0.shutdown.shutdown(std::net::Shutdown::Both);
|
||||
self.0.changed.notify_waiters();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
Reference in new issue
Block a user