Skip to main content

Module cookbook

Module cookbook 

Source
Expand description

Complete cache API recipes.

§Cookbook

These recipes use only the cache API. Fetching binaries, converting them to GSYM, and enforcing worker limits remain application responsibilities.

§Parse or construct a build ID

use gsym_cache::BuildId;

let parsed: BuildId = "0123abcdef".parse()?;
let binary = BuildId::new([0x01, 0x23, 0xab, 0xcd, 0xef])?;
assert_eq!(parsed, binary);

The hexadecimal parser accepts upper- or lowercase input and formatting always produces lowercase. Empty IDs, odd-length hexadecimal strings, invalid digits, and IDs too long for the .build-id layout are rejected.

§Read an entry

use gsym_cache::{BuildId, Cache, CacheEpoch};

let cache = Cache::open("/var/cache/my-profiler/gsym", CacheEpoch::new(1))?;
let build_id: BuildId = "0123abcdef".parse()?;

if let Some(entry) = cache.lookup(&build_id)? {
    println!("{} cached bytes", entry.len());
    let file = entry.into_file();
    consume(file);
}

The entry owns a read-only file descriptor. It can be borrowed with file, converted to std::fs::File, or passed to an API accepting std::os::fd::AsFd.

§Publish a generated file

use std::fs::File;
use std::io;
use gsym_cache::{BuildId, Cache, CacheEpoch, PopulationOutcome};

let cache = Cache::open("/var/cache/my-profiler/gsym", CacheEpoch::new(1))?;
let build_id: BuildId = "0123abcdef".parse()?;

match cache.try_begin_population(&build_id)? {
    PopulationOutcome::Acquired(population) => {
        let mut input = File::open("generated.gsym")?;
        let mut output = population.into_writer()?;
        io::copy(&mut input, &mut output)?;
        let outcome = output.publish()?;
        println!("published: {}", outcome.is_published());
    }
    PopulationOutcome::Present(entry) => {
        println!("already cached: {} bytes", entry.len());
    }
    PopulationOutcome::Suppressed(failure) => {
        println!("suppressed until {:?}", failure.expires_at());
    }
    PopulationOutcome::Busy => println!("retry later"),
}

Do not reopen the staging path or make the staged file externally writable. The writer capability exists to keep verification and publication within one exclusive lifecycle.

§Suppress a failed conversion

use std::time::Duration;
use gsym_cache::{BuildId, Cache, CacheEpoch, FailureKind, PopulationOutcome};

let cache = Cache::open("/var/cache/my-profiler/gsym", CacheEpoch::new(1))?;
let build_id = BuildId::new([0x12; 20])?;

if let PopulationOutcome::Acquired(population) = cache.try_begin_population(&build_id)? {
    let failure = population.record_failure_for(
        FailureKind::MissingInput,
        Duration::from_secs(30),
    )?;
    println!("retry at {:?}", failure.expires_at());
}

A writer that already contains partial output can record the same failure; its temporary file is discarded before the lock is released.

§Track access and maintain capacity

use std::time::Duration;
use gsym_cache::{ByteLimit, Cache, CacheEpoch, EntryLimit, PrunePolicy};

let cache = Cache::open("/var/cache/my-profiler/gsym", CacheEpoch::new(1))?;
let policy = PrunePolicy::new(ByteLimit::try_from(10 * 1024 * 1024 * 1024_u64)?)
    .max_entries(EntryLimit::try_from(100_000_u64)?)
    .max_unused_age(Duration::from_secs(30 * 24 * 60 * 60));

if let Some(report) = cache.prune(policy)?.into_report() {
    println!(
        "removed {}; {} entries and {} bytes remain",
        report.removed, report.after.entries, report.after.bytes,
    );
}

if let Some(report) = cache.scrub()?.into_report() {
    println!(
        "checked {}; removed {} corrupt objects",
        report.checked, report.removed_corrupt,
    );
}

Applications with many worker processes normally elect one periodic maintenance caller. The cache still returns Busy safely if schedules overlap.