relay_server/services/projects/cache/
handle.rs

1use std::fmt;
2use std::sync::Arc;
3
4use relay_base_schema::project::ProjectKey;
5use relay_config::Config;
6use relay_system::Addr;
7use tokio::sync::broadcast;
8
9use super::state::Shared;
10use crate::services::projects::cache::service::ProjectChange;
11use crate::services::projects::cache::{Project, ProjectCache};
12
13/// A synchronous handle to the [`ProjectCache`].
14///
15/// The handle allows lock free access to cached projects. It also acts as an interface
16/// to the [`ProjectCacheService`](super::ProjectCacheService).
17#[derive(Clone)]
18pub struct ProjectCacheHandle {
19    pub(super) shared: Arc<Shared>,
20    pub(super) config: Arc<Config>,
21    pub(super) service: Addr<ProjectCache>,
22    pub(super) project_changes: broadcast::Sender<ProjectChange>,
23}
24
25impl ProjectCacheHandle {
26    /// Returns the current project state for the `project_key`.
27    pub fn get(&self, project_key: ProjectKey) -> Project<'_> {
28        let project = self.shared.get_or_create(project_key);
29        // Always trigger a fetch after retrieving the project to make sure the state is up to date.
30        self.fetch(project_key);
31
32        Project::new(project, &self.config)
33    }
34
35    /// Triggers a fetch/update check in the project cache for the supplied project.
36    pub fn fetch(&self, project_key: ProjectKey) {
37        self.service.send(ProjectCache::Fetch(project_key));
38    }
39
40    /// Returns a subscription to all [`ProjectChange`]'s.
41    ///
42    /// This stream notifies the subscriber about project state changes in the project cache.
43    /// Events may arrive in arbitrary order and be delivered multiple times.
44    pub fn changes(&self) -> broadcast::Receiver<ProjectChange> {
45        self.project_changes.subscribe()
46    }
47}
48
49impl fmt::Debug for ProjectCacheHandle {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.debug_struct("ProjectCacheHandle")
52            .field("shared", &self.shared)
53            .finish()
54    }
55}
56
57#[cfg(test)]
58mod test {
59    use super::*;
60    use crate::services::projects::project::ProjectState;
61
62    impl ProjectCacheHandle {
63        /// Creates a new [`ProjectCacheHandle`] for testing only.
64        ///
65        /// A project cache handle created this way does not require a service to function.
66        pub fn for_test() -> Self {
67            Self {
68                shared: Default::default(),
69                config: Default::default(),
70                service: Addr::dummy(),
71                project_changes: broadcast::channel(999_999).0,
72            }
73        }
74
75        /// Sets the project state for a project.
76        ///
77        /// This can be used to emulate a project cache update in tests.
78        pub fn test_set_project_state(&self, project_key: ProjectKey, state: ProjectState) {
79            let is_pending = state.is_pending();
80            self.shared.test_set_project_state(project_key, state);
81            if is_pending {
82                let _ = self
83                    .project_changes
84                    .send(ProjectChange::Evicted(project_key));
85            } else {
86                let _ = self.project_changes.send(ProjectChange::Ready(project_key));
87            }
88        }
89
90        /// Returns `true` if there is a project created for this `project_key`.
91        ///
92        /// A project is automatically created on access via [`Self::get`].
93        pub fn test_has_project_created(&self, project_key: ProjectKey) -> bool {
94            self.shared.test_has_project_created(project_key)
95        }
96
97        /// The amount of fetches triggered for projects.
98        ///
99        /// A fetch is triggered for both [`Self::get`] and [`Self::fetch`].
100        pub fn test_num_fetches(&self) -> u64 {
101            self.service.len()
102        }
103    }
104}