Skip to content
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

ref: Use moka for Cacher implementation #979

Merged
merged 6 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
frame's instruction address needs to be adjusted for symbolication. ([#948](https://github.com/getsentry/symbolicator/pull/948))
- Added offline mode and caching to `symbolicli`. ([#967](https://github.com/getsentry/symbolicator/pull/967),[#968](https://github.com/getsentry/symbolicator/pull/968))
- Support PortablePDB embedded sources. ([#996](https://github.com/getsentry/symbolicator/pull/996))
- Use `moka` as an in-memory `Cacher` implementation. ([#979](https://github.com/getsentry/symbolicator/pull/979))

### Internal

Expand Down
2 changes: 1 addition & 1 deletion crates/symbolicator-service/src/services/cache_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use symbolicator_sources::RemoteFile;

use crate::types::Scope;

#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct CacheKey {
pub cache_key: String,
}
Expand Down
338 changes: 163 additions & 175 deletions crates/symbolicator-service/src/services/cacher.rs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions crates/symbolicator-service/src/services/download/sentry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ impl SentryDownloader {
/// If there are cached search results this skips the actual search.
async fn cached_sentry_search(&self, query: SearchQuery) -> CacheEntry<Vec<SearchResult>> {
let query_ = query.clone();
let init_future = async {
let init_future = Box::pin(async {
tracing::debug!(
"Fetching list of Sentry debug files from {}",
&query_.index_url
Expand All @@ -112,7 +112,7 @@ impl SentryDownloader {
CancelOnDrop::new(self.runtime.spawn(future.bind_hub(sentry::Hub::current())));

future.await.map_err(|_| CacheError::InternalError)?
};
});
self.index_cache
.get_with_if(query, init_future, |entry| entry.is_err())
.await
Expand Down
22 changes: 17 additions & 5 deletions crates/symbolicator-service/src/services/objects/data_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ mod tests {
use symbolic::common::DebugId;
use tempfile::TempDir;

async fn objects_actor(tempdir: &TempDir) -> ObjectsActor {
async fn make_objects_actor(tempdir: &TempDir) -> ObjectsActor {
let meta_cache = Cache::from_config(
CacheName::ObjectMeta,
Some(tempdir.path().join("meta")),
Expand Down Expand Up @@ -299,7 +299,7 @@ mod tests {

let hitcounter = test::Server::new();
let cachedir = tempdir();
let objects_actor = objects_actor(&cachedir).await;
let objects_actor = make_objects_actor(&cachedir).await;

let find_object = FindObject {
filetypes: &[FileType::MachCode],
Expand Down Expand Up @@ -331,6 +331,9 @@ mod tests {
CacheError::DownloadError("500 Internal Server Error".into())
);
assert_eq!(hitcounter.accesses(), 3); // up to 3 tries on failure

// NOTE: creating a fresh instance to avoid in-memory cache
let objects_actor = make_objects_actor(&cachedir).await;
let result = objects_actor
.find(find_object.clone())
.await
Expand All @@ -351,7 +354,7 @@ mod tests {

let hitcounter = test::Server::new();
let cachedir = tempdir();
let objects_actor = objects_actor(&cachedir).await;
let objects_actor = make_objects_actor(&cachedir).await;

let find_object = FindObject {
filetypes: &[FileType::MachCode],
Expand Down Expand Up @@ -380,6 +383,9 @@ mod tests {
.unwrap_err();
assert_eq!(result, CacheError::NotFound);
assert_eq!(hitcounter.accesses(), 1);

// NOTE: creating a fresh instance to avoid in-memory cache
let objects_actor = make_objects_actor(&cachedir).await;
let result = objects_actor
.find(find_object.clone())
.await
Expand All @@ -397,7 +403,7 @@ mod tests {

let hitcounter = test::Server::new();
let cachedir = tempdir();
let objects_actor = objects_actor(&cachedir).await;
let objects_actor = make_objects_actor(&cachedir).await;

let find_object = FindObject {
filetypes: &[FileType::MachCode],
Expand Down Expand Up @@ -428,6 +434,9 @@ mod tests {
assert_eq!(result, err);
// XXX: why are we not trying this 3 times?
assert_eq!(hitcounter.accesses(), 1);

// NOTE: creating a fresh instance to avoid in-memory cache
let objects_actor = make_objects_actor(&cachedir).await;
let result = objects_actor
.find(find_object.clone())
.await
Expand All @@ -445,7 +454,7 @@ mod tests {

let hitcounter = test::Server::new();
let cachedir = tempdir();
let objects_actor = objects_actor(&cachedir).await;
let objects_actor = make_objects_actor(&cachedir).await;

let find_object = FindObject {
filetypes: &[FileType::MachCode],
Expand Down Expand Up @@ -475,6 +484,9 @@ mod tests {
.unwrap_err();
assert_eq!(result, err);
assert_eq!(hitcounter.accesses(), 1);

// NOTE: creating a fresh instance to avoid in-memory cache
let objects_actor = make_objects_actor(&cachedir).await;
let result = objects_actor
.find(find_object.clone())
.await
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,36 +173,35 @@ impl SymbolicatorSymbolProvider {
/// Fetches CFI for the given module, parses it into a `SymbolFile`, and stores it internally.
async fn load_cfi_module(&self, module: &(dyn Module + Sync)) -> FetchedCfiCache {
let key = LookupKey::new(module);
self.cficaches
.get_with_by_ref(&key, async {
let sources = self.sources.clone();
let scope = self.scope.clone();

let identifier = ObjectId {
code_id: key.code_id.clone(),
code_file: Some(module.code_file().into_owned()),
debug_id: key.debug_id,
debug_file: module
.debug_file()
.map(|debug_file| debug_file.into_owned()),
let load = Box::pin(async {
let sources = self.sources.clone();
let scope = self.scope.clone();

let identifier = ObjectId {
code_id: key.code_id.clone(),
code_file: Some(module.code_file().into_owned()),
debug_id: key.debug_id,
debug_file: module
.debug_file()
.map(|debug_file| debug_file.into_owned()),
object_type: self.object_type,
};

self.cficache_actor
.fetch(FetchCfiCache {
object_type: self.object_type,
};

self.cficache_actor
.fetch(FetchCfiCache {
object_type: self.object_type,
identifier,
sources,
scope,
})
// NOTE: this `bind_hub` is important!
// `load_cfi_module` is being called concurrently from `rust-minidump` via
// `join_all`. We do need proper isolation of any async task that might
// manipulate any Sentry scope.
.bind_hub(Hub::new_from_top(Hub::current()))
.await
})
.await
identifier,
sources,
scope,
})
// NOTE: this `bind_hub` is important!
// `load_cfi_module` is being called concurrently from `rust-minidump` via
// `join_all`. We do need proper isolation of any async task that might
// manipulate any Sentry scope.
.bind_hub(Hub::new_from_top(Hub::current()))
.await
});
self.cficaches.get_with_by_ref(&key, load).await
}
}

Expand Down Expand Up @@ -518,3 +517,74 @@ fn normalize_minidump_os_name(os: Os) -> &'static str {
Os::Unknown(_) => "",
}
}

#[cfg(test)]
mod tests {
use crate::services::create_service;
use crate::services::objects::{FindObject, ObjectPurpose};
use crate::services::ppdb_caches::FetchPortablePdbCache;
use crate::services::symcaches::FetchSymCache;

use super::*;

/// Tests that the size of the `compute_memoized` future does not grow out of bounds.
/// See <https://github.com/moka-rs/moka/issues/212> for one of the main issues here.
/// The size assertion will naturally change with compiler, dependency and code changes.
#[tokio::test]
async fn future_size() {
let (sym, obj) =
create_service(&Default::default(), tokio::runtime::Handle::current()).unwrap();

let provider = SymbolicatorSymbolProvider::new(
Scope::Global,
Arc::from_iter([]),
sym.cficaches.clone(),
Default::default(),
);

let module = ("foo", DebugId::nil());
let fut = provider.load_cfi_module(&module);
let size = dbg!(std::mem::size_of_val(&fut));
assert!(size > 850 && size < 900);

let req = FindObject {
filetypes: &[],
purpose: ObjectPurpose::Debug,
identifier: Default::default(),
sources: Arc::from_iter([]),
scope: Scope::Global,
};
let fut = obj.find(req);
let size = dbg!(std::mem::size_of_val(&fut));
assert!(size > 4800 && size < 4900);

let req = FetchCfiCache {
object_type: Default::default(),
identifier: Default::default(),
sources: Arc::from_iter([]),
scope: Scope::Global,
};
let fut = sym.cficaches.fetch(req);
let size = dbg!(std::mem::size_of_val(&fut));
assert!(size > 5200 && size < 5300);

let req = FetchPortablePdbCache {
identifier: Default::default(),
sources: Arc::from_iter([]),
scope: Scope::Global,
};
let fut = sym.ppdb_caches.fetch(req);
let size = dbg!(std::mem::size_of_val(&fut));
assert!(size > 5200 && size < 5300);

let req = FetchSymCache {
object_type: Default::default(),
identifier: Default::default(),
sources: Arc::from_iter([]),
scope: Scope::Global,
};
let fut = sym.symcaches.fetch(req);
let size = dbg!(std::mem::size_of_val(&fut));
assert!(size > 11200 && size < 11300);
}
}
26 changes: 9 additions & 17 deletions crates/symbolicator-service/src/services/symcaches/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,20 +443,19 @@ mod tests {

// Create the symcache for the first time. Since the bcsymbolmap is not available, names in the
// symcache will be obfuscated.
let owned_symcache = symcache_actor
let symcache = symcache_actor
.fetch(fetch_symcache.clone())
.await
.cache
.ok()
.unwrap();

let symcache = owned_symcache.get();
let sl = symcache.lookup(0x5a75).next().unwrap();
let sl = symcache.get().lookup(0x5a75).next().unwrap();
assert_eq!(
sl.file().unwrap().full_path(),
"__hidden#41_/__hidden#41_/__hidden#42_"
);
assert_eq!(sl.function().name(), "__hidden#0_");
drop(symcache);

// Copy the plist and bcsymbolmap to the temporary symbol directory so that the SymCacheActor can find them.
fs::copy(
Expand All @@ -472,37 +471,30 @@ mod tests {
.unwrap();

// Create the symcache for the second time. Even though the bcsymbolmap is now available, its absence should
// still be cached and the SymcacheActor should make no attempt to download it. Therefore, the names should
// still be cached and the SymCacheActor should make no attempt to download it. Therefore, the names should
// be obfuscated like before.
let owned_symcache = symcache_actor
let symcache = symcache_actor
.fetch(fetch_symcache.clone())
.await
.cache
.ok()
.unwrap();

let symcache = owned_symcache.get();
let sl = symcache.lookup(0x5a75).next().unwrap();
let sl = symcache.get().lookup(0x5a75).next().unwrap();
assert_eq!(
sl.file().unwrap().full_path(),
"__hidden#41_/__hidden#41_/__hidden#42_"
);
assert_eq!(sl.function().name(), "__hidden#0_");
drop(symcache);

// Sleep long enough for the negative cache entry to become invalid.
std::thread::sleep(TIMEOUT);

// Create the symcache for the third time. This time, the bcsymbolmap is downloaded and the names in the
// symcache are unobfuscated.
let owned_symcache = symcache_actor
.fetch(fetch_symcache.clone())
.await
.cache
.ok()
.unwrap();
let symcache = symcache_actor.fetch(fetch_symcache).await.cache.unwrap();

let symcache = owned_symcache.get();
let sl = symcache.lookup(0x5a75).next().unwrap();
let sl = symcache.get().lookup(0x5a75).next().unwrap();
assert_eq!(
sl.file().unwrap().full_path(),
"/Users/philipphofmann/git-repos/sentry-cocoa/Sources/Sentry/SentryMessage.m"
Expand Down
2 changes: 1 addition & 1 deletion crates/symbolicator-service/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub struct Signal(pub u32);
/// Based on scopes, access to debug files that have been cached is determined. If a file comes from
/// a public source, it can be used for any symbolication request. Otherwise, the symbolication
/// request must match the scope of a file.
#[derive(Debug, Clone, Deserialize, Serialize, Eq, Ord, PartialEq, PartialOrd)]
#[derive(Debug, Clone, Deserialize, Serialize, Eq, Ord, PartialEq, PartialOrd, Hash)]
#[serde(untagged)]
#[derive(Default)]
pub enum Scope {
Expand Down