hot_lib_reloader/
lib_reload_events.rs

1use std::{
2    borrow::BorrowMut,
3    sync::{Arc, Condvar, Mutex, mpsc},
4    time::Duration,
5};
6
7/// Signals when the library has changed.
8/// Needs to be public as it is used in `hot_module`.
9#[derive(Clone)]
10#[non_exhaustive]
11#[doc(hidden)]
12pub enum ChangedEvent {
13    LibAboutToReload(BlockReload),
14    LibReloaded,
15}
16
17impl std::fmt::Debug for ChangedEvent {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        match self {
20            Self::LibAboutToReload(_) => write!(f, "LibAboutToReload"),
21            Self::LibReloaded => write!(f, "LibReloaded"),
22        }
23    }
24}
25
26/// See [`LibReloadObserver::wait_for_about_to_reload`].
27///
28/// [`BlockReload`] is implemented using a simple counting scheme to track how
29/// many tokens are floating around. If the number reaches 0 the update can
30/// continue.
31#[derive(Debug)]
32pub struct BlockReload {
33    pub(crate) pending: Arc<(Mutex<usize>, Condvar)>,
34}
35
36impl Clone for BlockReload {
37    fn clone(&self) -> Self {
38        **(self.pending.0.lock().unwrap().borrow_mut()) += 1;
39        Self {
40            pending: self.pending.clone(),
41        }
42    }
43}
44
45impl Drop for BlockReload {
46    fn drop(&mut self) {
47        let (counter, cond) = &*self.pending;
48        *counter.lock().unwrap() -= 1;
49        cond.notify_one();
50    }
51}
52
53/// A [`LibReloadObserver`] allows to wait for library changes. See
54/// - [`LibReloadObserver::wait_for_about_to_reload`] and
55/// - [`LibReloadObserver::wait_for_reload`]
56///   for details.
57///
58/// You can use those methods individually or in combination. In particular, if you want to serialize state before a library change happens and then deserialize / migrate it when the library update is done, using both methods in combination is quite useful. Something along the lines of:
59///
60/// ```ignore
61/// #[hot_module(dylib = "lib")]
62/// mod hot_lib {
63///     #[lib_change_subscription]
64///     pub fn subscribe() -> hot_lib_reloader::LibReloadObserver { }
65/// }
66///
67/// fn test() {
68///     let lib_observer = hot_lib::subscribe();
69///
70///     /* ... */
71///
72///     // wait for reload to begin (at this point the  old version is still loaded)
73///     let update_blocker = lib_observer.wait_for_about_to_reload();
74///
75///     /* do update preparations here, e.g. serialize state */
76///
77///     // drop the blocker to allow update
78///     drop(update_blocker);
79///
80///     // wait for reload to be completed
81///     lib_observer.wait_for_reload();
82///
83///     /* new lib version is loaded now so you can e.g. restore state */
84/// }
85/// ```
86pub struct LibReloadObserver {
87    // needs to be public b/c it is used inside the [`hot_module`] macro.
88    #[doc(hidden)]
89    pub rx: mpsc::Receiver<ChangedEvent>,
90}
91
92impl LibReloadObserver {
93    /// A call to this method will do a blocking wait until the watched library is
94    /// about to change. It returns a [`BlockReload`] token. While this token is in
95    /// scope you will prevent the pending update to proceed. This is useful for
96    /// doing preparations for the update and while the old library version is still
97    /// loaded. You can for example serialize state.
98    pub fn wait_for_about_to_reload(&self) -> BlockReload {
99        loop {
100            match self.rx.recv() {
101                Ok(ChangedEvent::LibAboutToReload(block)) => return block,
102                Err(err) => {
103                    panic!("LibReloadObserver failed to wait for event from reloader: {err}")
104                }
105                _ => continue,
106            }
107        }
108    }
109
110    /// Like [`Self::wait_for_about_to_reload`] but for a limited time. In case of a timeout return `None`.
111    pub fn wait_for_about_to_reload_timeout(&self, timeout: Duration) -> Option<BlockReload> {
112        loop {
113            match self.rx.recv_timeout(timeout) {
114                Ok(ChangedEvent::LibAboutToReload(block)) => return Some(block),
115                Err(_) => return None,
116                _ => continue,
117            }
118        }
119    }
120
121    /// Will do blocking wait until a new library version is loaded.
122    pub fn wait_for_reload(&self) {
123        loop {
124            match self.rx.recv() {
125                Ok(ChangedEvent::LibReloaded) => return,
126                Err(err) => {
127                    panic!("LibReloadObserver failed to wait for event from reloader: {err}")
128                }
129                _ => continue,
130            }
131        }
132    }
133
134    /// Like [`Self::wait_for_reload`] but for a limited time. In case of a timeout return `false`.
135    pub fn wait_for_reload_timeout(&self, timeout: Duration) -> bool {
136        loop {
137            match self.rx.recv_timeout(timeout) {
138                Ok(ChangedEvent::LibReloaded) => return true,
139                Err(_) => return false,
140                _ => continue,
141            }
142        }
143    }
144}
145
146/// Needs to be public as it is used in the `hot_module` macro.
147#[derive(Default)]
148#[doc(hidden)]
149pub struct LibReloadNotifier {
150    subscribers: Arc<Mutex<Vec<mpsc::Sender<ChangedEvent>>>>,
151}
152
153impl LibReloadNotifier {
154    /// Needs to be public as it is used in the `hot_module` macro.
155    ///
156    /// The count used here represents [`BlockReload`] tokens that are still
157    /// floating around. When a token is dropped the count is decremented and
158    /// the condvar signaled.
159    #[doc(hidden)]
160    pub fn send_about_to_reload_event_and_wait_for_blocks(&self) {
161        let pending = Arc::new((Mutex::new(1), std::sync::Condvar::new()));
162        let block = BlockReload {
163            pending: pending.clone(),
164        };
165        self.notify(ChangedEvent::LibAboutToReload(block));
166        let (counter, cond) = &*pending;
167        log::trace!(
168            "about-to-change library event, waiting for {}",
169            counter.lock().unwrap()
170        );
171        let _guard = cond
172            .wait_while(counter.lock().unwrap(), |pending| {
173                log::trace!(
174                    "about-to-change library event, now waiting for {}",
175                    *pending
176                );
177                *pending > 0
178            })
179            .unwrap();
180    }
181
182    #[doc(hidden)]
183    pub fn send_reloaded_event(&self) {
184        self.notify(ChangedEvent::LibReloaded);
185    }
186
187    fn notify(&self, evt: ChangedEvent) {
188        if let Ok(mut subscribers) = self.subscribers.try_lock() {
189            let n = subscribers.len();
190            log::trace!("sending {evt:?} to {n} subscribers");
191            // keep only those subscribers that are still around and kicking.
192            subscribers.retain(|tx| tx.send(evt.clone()).is_ok());
193            let removed = n - subscribers.len();
194            if removed > 0 {
195                log::debug!(
196                    "removing {removed} subscriber{}",
197                    if removed == 1 { "" } else { "s" }
198                );
199            }
200        }
201    }
202
203    /// Needs to be public as it is used in the `hot_module` macro.
204    ///
205    /// Create a [ChangedEvent] receiver that gets signalled when the library
206    /// changes.
207    #[doc(hidden)]
208    pub fn subscribe(&mut self) -> LibReloadObserver {
209        log::trace!("subscribe to lib change");
210        let (tx, rx) = mpsc::channel();
211        let mut subscribers = self.subscribers.lock().unwrap();
212        subscribers.push(tx);
213        LibReloadObserver { rx }
214    }
215}