-
Notifications
You must be signed in to change notification settings - Fork 104
feat(server): Garbage collector thread for project cache eviction [INGEST-1355] #1410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
317145f
4a7a16e
7e6820b
142c5c9
72c5936
c3a452a
ec3140b
8f1e417
e6f53f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,12 +5,16 @@ pub enum RelayGauges { | |
| /// The state of Relay with respect to the upstream connection. | ||
| /// Possible values are `0` for normal operations and `1` for a network outage. | ||
| NetworkOutage, | ||
|
|
||
| /// The number of items currently in the garbage disposal queue. | ||
| ProjectCacheGarbageQueueSize, | ||
| } | ||
|
|
||
| impl GaugeMetric for RelayGauges { | ||
| fn name(&self) -> &'static str { | ||
| match self { | ||
| RelayGauges::NetworkOutage => "upstream.network_outage", | ||
| RelayGauges::ProjectCacheGarbageQueueSize => "project_cache.garbage.queue_size", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if all this was to get rid of the nonsensical tag, I'd have rather gone with passing a string for the tag into the constructor. but whatever, either will do.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The main motivation was to get rid of the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (note i approved so i'm not asking for changes) |
||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| use std::sync::atomic::{AtomicUsize, Ordering}; | ||
| use std::sync::{mpsc, Arc}; | ||
| use std::thread::JoinHandle; | ||
|
|
||
| /// Garbage disposal agent. | ||
| /// | ||
| /// Spawns a background thread which drops items sent to it via [`GarbageDisposal::dispose`]. | ||
| pub struct GarbageDisposal<T> { | ||
| tx: mpsc::Sender<T>, | ||
| queue_size: Arc<AtomicUsize>, | ||
| } | ||
|
|
||
| impl<T: Send + 'static> GarbageDisposal<T> { | ||
| /// Returns a new instance plus a handle to join on the background thread. | ||
| /// Currently only used in tests. | ||
| fn new_joinable() -> (Self, JoinHandle<()>) { | ||
| let (tx, rx) = mpsc::channel(); | ||
|
|
||
| let queue_size = Arc::new(AtomicUsize::new(0)); | ||
| let queue_size_clone = queue_size.clone(); | ||
| let join_handle = std::thread::spawn(move || { | ||
| relay_log::debug!("Start garbage collection thread"); | ||
| while let Ok(object) = rx.recv() { | ||
jan-auer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| queue_size_clone.fetch_sub(1, Ordering::Relaxed); // Wraps around on overflow | ||
| drop(object); | ||
| } | ||
| relay_log::debug!("Stop garbage collection thread"); | ||
| }); | ||
|
|
||
| (Self { tx, queue_size }, join_handle) | ||
| } | ||
|
|
||
| /// Spawns a new garbage disposal instance. | ||
| /// Every instance has its own background thread that received items to be dropped via | ||
| /// [`Self::dispose`]. | ||
| /// When the instance is dropped, the background thread stops automatically. | ||
| pub fn new() -> Self { | ||
| let (instance, _) = Self::new_joinable(); | ||
| instance | ||
| } | ||
|
|
||
| /// Defers dropping an object by sending it to the background thread. | ||
| pub fn dispose(&self, object: T) { | ||
| self.queue_size.fetch_add(1, Ordering::Relaxed); | ||
| self.tx | ||
| .send(object) | ||
jjbayer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| .map_err(|e| { | ||
| relay_log::error!("Failed to send object to garbage disposal thread, drop here"); | ||
| drop(e.0); | ||
| }) | ||
| .ok(); | ||
| } | ||
|
|
||
| /// Get current queue size. | ||
| pub fn queue_size(&self) -> usize { | ||
| self.queue_size.load(Ordering::Relaxed) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use std::{ | ||
| sync::{Arc, Mutex}, | ||
| thread::ThreadId, | ||
| }; | ||
|
|
||
| use super::GarbageDisposal; | ||
|
|
||
| struct SomeStruct { | ||
| thread_ids: Arc<Mutex<Vec<ThreadId>>>, | ||
| } | ||
|
|
||
| impl Drop for SomeStruct { | ||
| fn drop(&mut self) { | ||
| self.thread_ids | ||
| .lock() | ||
| .unwrap() | ||
| .push(std::thread::current().id()) | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_garbage_disposal() { | ||
| let thread_ids = Arc::new(Mutex::new(Vec::<ThreadId>::new())); | ||
|
|
||
| let x1 = SomeStruct { | ||
| thread_ids: thread_ids.clone(), | ||
| }; | ||
| drop(x1); | ||
|
|
||
| let x2 = SomeStruct { | ||
| thread_ids: thread_ids.clone(), | ||
| }; | ||
|
|
||
| let (garbage, join_handle) = GarbageDisposal::new_joinable(); | ||
| garbage.dispose(x2); | ||
| drop(garbage); // breaks the while loop by dropping rx | ||
| join_handle.join().ok(); // wait for thread to finish its work | ||
|
|
||
| let thread_ids = thread_ids.lock().unwrap(); | ||
| assert_eq!(thread_ids.len(), 2); | ||
| assert_eq!(thread_ids[0], std::thread::current().id()); | ||
| assert!(thread_ids[0] != thread_ids[1]); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this alone was enough reason for me to report queue size as u64 rather than usize :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you clarify? You could equally have the queue_size as
usizehere and then cast this to an f64.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, why is this
f64and notu64? Only just noticed that and now I'm confused.u64.usizeis arbitrary as exposing it as au64.usizeand you're supposedly casting here. This is misleading.