From abfd337c14292e0a1dd80ef498d92c8be0b47621 Mon Sep 17 00:00:00 2001
From: Ian Douglas Scott <idscott@system76.com>
Date: Tue, 25 Feb 2025 13:47:28 -0800
Subject: [PATCH 1/2] Fix leaks of `SeatRc` and `KbdRc`, but using `Weak` in
 user data

This fixes https://github.com/Smithay/smithay/issues/1422.

This can be tested by adding `Drop` impls that print to those two types,
or using tools that detect leaks.
---
 src/wayland/seat/keyboard.rs | 19 ++++-----
 src/wayland/seat/mod.rs      | 80 +++++++++++++++++++++++-------------
 2 files changed, 59 insertions(+), 40 deletions(-)

diff --git a/src/wayland/seat/keyboard.rs b/src/wayland/seat/keyboard.rs
index 951c76082500..d48f7a28e49d 100644
--- a/src/wayland/seat/keyboard.rs
+++ b/src/wayland/seat/keyboard.rs
@@ -1,4 +1,4 @@
-use std::fmt;
+use std::{fmt, sync::Weak};
 
 use tracing::{error, instrument, trace, warn};
 use wayland_server::{
@@ -14,7 +14,7 @@ use super::WaylandFocus;
 use crate::{
     backend::input::{KeyState, Keycode},
     input::{
-        keyboard::{KeyboardHandle, KeyboardTarget, KeysymHandle, ModifiersState},
+        keyboard::{KbdRc, KeyboardHandle, KeyboardTarget, KeysymHandle, ModifiersState},
         Seat, SeatHandler, SeatState,
     },
     utils::Serial,
@@ -88,13 +88,15 @@ impl<D: SeatHandler + 'static> KeyboardHandle<D> {
     /// May return `None` for a valid `WlKeyboard` that was created without
     /// the keyboard capability.
     pub fn from_resource(seat: &WlKeyboard) -> Option<Self> {
-        seat.data::<KeyboardUserData<D>>()?.handle.clone()
+        Some(Self {
+            arc: seat.data::<KeyboardUserData<D>>()?.handle.as_ref()?.upgrade()?,
+        })
     }
 }
 
 /// User data for keyboard
 pub struct KeyboardUserData<D: SeatHandler> {
-    pub(crate) handle: Option<KeyboardHandle<D>>,
+    pub(crate) handle: Option<Weak<KbdRc<D>>>,
 }
 
 impl<D: SeatHandler> fmt::Debug for KeyboardUserData<D> {
@@ -122,13 +124,8 @@ where
     }
 
     fn destroyed(_state: &mut D, _client_id: ClientId, keyboard: &WlKeyboard, data: &KeyboardUserData<D>) {
-        if let Some(ref handle) = data.handle {
-            handle
-                .arc
-                .known_kbds
-                .lock()
-                .unwrap()
-                .retain(|k| k.id() != keyboard.id())
+        if let Some(ref arc) = data.handle.as_ref().and_then(|h| h.upgrade()) {
+            arc.known_kbds.lock().unwrap().retain(|k| k.id() != keyboard.id())
         }
     }
 }
diff --git a/src/wayland/seat/mod.rs b/src/wayland/seat/mod.rs
index 0e33ec0e5df6..62dee5817031 100644
--- a/src/wayland/seat/mod.rs
+++ b/src/wayland/seat/mod.rs
@@ -70,7 +70,11 @@ pub(crate) mod keyboard;
 pub(crate) mod pointer;
 mod touch;
 
-use std::{borrow::Cow, fmt, sync::Arc};
+use std::{
+    borrow::Cow,
+    fmt,
+    sync::{Arc, Weak},
+};
 
 use crate::input::{Inner, Seat, SeatHandler, SeatRc, SeatState};
 
@@ -146,7 +150,8 @@ impl<D: SeatHandler> Inner<D> {
 
 /// Global data of WlSeat
 pub struct SeatGlobalData<D: SeatHandler> {
-    arc: Arc<SeatRc<D>>,
+    // Seat arc
+    arc: Weak<SeatRc<D>>,
 }
 
 impl<D: SeatHandler> fmt::Debug for SeatGlobalData<D> {
@@ -171,12 +176,17 @@ impl<D: SeatHandler + 'static> SeatState<D> {
         <D as SeatHandler>::KeyboardFocus: WaylandFocus,
         N: Into<String>,
     {
-        let Seat { arc } = self.new_seat(name);
+        let seat = self.new_seat(name);
 
-        let global_id = display.create_global::<D, _, _>(9, SeatGlobalData { arc: arc.clone() });
-        arc.inner.lock().unwrap().global = Some(global_id);
+        let global_id = display.create_global::<D, _, _>(
+            9,
+            SeatGlobalData {
+                arc: Arc::downgrade(&seat.arc),
+            },
+        );
+        seat.arc.inner.lock().unwrap().global = Some(global_id);
 
-        Seat { arc }
+        seat
     }
 }
 
@@ -190,7 +200,7 @@ impl<D: SeatHandler + 'static> Seat<D> {
     /// Attempt to retrieve a [`Seat`] from an existing resource
     pub fn from_resource(seat: &WlSeat) -> Option<Self> {
         seat.data::<SeatUserData<D>>()
-            .map(|d| d.arc.clone())
+            .and_then(|d| d.arc.upgrade())
             .map(|arc| Self { arc })
     }
 
@@ -215,7 +225,7 @@ impl<D: SeatHandler + 'static> Seat<D> {
 
 /// User data for seat
 pub struct SeatUserData<D: SeatHandler> {
-    arc: Arc<SeatRc<D>>,
+    arc: Weak<SeatRc<D>>,
 }
 
 impl<D: SeatHandler> fmt::Debug for SeatUserData<D> {
@@ -269,18 +279,21 @@ where
     ) {
         match request {
             wl_seat::Request::GetPointer { id } => {
-                let inner = data.arc.inner.lock().unwrap();
+                let ptr_handle = data
+                    .arc
+                    .upgrade()
+                    .and_then(|arc| arc.inner.lock().unwrap().pointer.clone());
 
                 let client_scale = state.client_compositor_state(client).clone_client_scale();
                 let pointer = data_init.init(
                     id,
                     PointerUserData {
-                        handle: inner.pointer.clone(),
+                        handle: ptr_handle.clone(),
                         client_scale,
                     },
                 );
 
-                if let Some(ref ptr_handle) = inner.pointer {
+                if let Some(ref ptr_handle) = ptr_handle {
                     ptr_handle.wl_pointer.new_pointer(pointer);
                 } else {
                     // we should send a protocol error... but the protocol does not allow
@@ -288,34 +301,40 @@ where
                 }
             }
             wl_seat::Request::GetKeyboard { id } => {
-                let inner = data.arc.inner.lock().unwrap();
+                let kbd_handle = data
+                    .arc
+                    .upgrade()
+                    .and_then(|arc| arc.inner.lock().unwrap().keyboard.clone());
 
                 let keyboard = data_init.init(
                     id,
                     KeyboardUserData {
-                        handle: inner.keyboard.clone(),
+                        handle: kbd_handle.as_ref().map(|h| Arc::downgrade(&h.arc)),
                     },
                 );
 
-                if let Some(ref h) = inner.keyboard {
+                if let Some(ref h) = kbd_handle {
                     h.new_kbd(keyboard);
                 } else {
                     // same as pointer, should error but cannot
                 }
             }
             wl_seat::Request::GetTouch { id } => {
-                let inner = data.arc.inner.lock().unwrap();
+                let touch_handle = data
+                    .arc
+                    .upgrade()
+                    .and_then(|arc| arc.inner.lock().unwrap().touch.clone());
 
                 let client_scale = state.client_compositor_state(client).clone_client_scale();
                 let touch = data_init.init(
                     id,
                     TouchUserData {
-                        handle: inner.touch.clone(),
+                        handle: touch_handle.clone(),
                         client_scale,
                     },
                 );
 
-                if let Some(ref h) = inner.touch {
+                if let Some(ref h) = touch_handle {
                     h.new_touch(touch);
                 } else {
                     // same as pointer, should error but cannot
@@ -329,12 +348,13 @@ where
     }
 
     fn destroyed(_state: &mut D, _: ClientId, seat: &WlSeat, data: &SeatUserData<D>) {
-        data.arc
-            .inner
-            .lock()
-            .unwrap()
-            .known_seats
-            .retain(|s| s.id() != seat.id());
+        if let Some(arc) = data.arc.upgrade() {
+            arc.inner
+                .lock()
+                .unwrap()
+                .known_seats
+                .retain(|s| s.id() != seat.id());
+        }
     }
 }
 
@@ -362,12 +382,14 @@ where
 
         let resource = data_init.init(resource, data);
 
-        if resource.version() >= 2 {
-            resource.name(global_data.arc.name.clone());
-        }
+        if let Some(arc) = global_data.arc.upgrade() {
+            if resource.version() >= 2 {
+                resource.name(arc.name.clone());
+            }
 
-        let mut inner = global_data.arc.inner.lock().unwrap();
-        resource.capabilities(inner.compute_caps());
-        inner.known_seats.push(resource.downgrade());
+            let mut inner = arc.inner.lock().unwrap();
+            resource.capabilities(inner.compute_caps());
+            inner.known_seats.push(resource.downgrade());
+        }
     }
 }

From 2620147d451625e3caf0ab3fb3569eab7d492498 Mon Sep 17 00:00:00 2001
From: Ian Douglas Scott <idscott@system76.com>
Date: Tue, 25 Feb 2025 14:22:40 -0800
Subject: [PATCH 2/2] WIP Use `WeakLoopHandle` to avoid leaking `LoopInner`

Pending `calloop` release.
---
 Cargo.toml              | 2 +-
 src/xwayland/xwm/mod.rs | 8 +++++---
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 5c21e4ffd498..8ead20706fea 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,7 +29,7 @@ members = [
 appendlist = "1.4"
 ash = { version = "0.38.0", optional = true }
 bitflags = "2.2.1"
-calloop = "0.14.0"
+calloop = { git = "https://github.com/Smithay/calloop" } # XXX
 cursor-icon = "1.0.0"
 cgmath = "0.18.0"
 downcast-rs = "1.2.0"
diff --git a/src/xwayland/xwm/mod.rs b/src/xwayland/xwm/mod.rs
index ba0819018d65..3751b4c98f2d 100644
--- a/src/xwayland/xwm/mod.rs
+++ b/src/xwayland/xwm/mod.rs
@@ -880,11 +880,13 @@ impl X11Wm {
             span,
         };
 
-        let event_handle = handle.clone();
+        let event_handle = handle.downgrade();
         handle.insert_source(source, move |event, _, data| match event {
             calloop::channel::Event::Msg(event) => {
-                if let Err(err) = handle_event(&event_handle, data, id, event) {
-                    warn!(id = id.0, err = ?err, "Failed to handle X11 event");
+                if let Some(event_handle) = event_handle.upgrade() {
+                    if let Err(err) = handle_event(&event_handle, data, id, event) {
+                        warn!(id = id.0, err = ?err, "Failed to handle X11 event");
+                    }
                 }
             }
             calloop::channel::Event::Closed => {