Skip to content

Commit

Permalink
wire up cache, add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
aumetra committed May 4, 2024
1 parent efdfb61 commit 22eda85
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 5 deletions.
6 changes: 6 additions & 0 deletions crates/kitsune-config/src/mrf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
use std::{collections::HashMap, num::NonZeroUsize};

#[derive(Clone, Debug, Deserialize, Serialize)]

Check warning on line 5 in crates/kitsune-config/src/mrf.rs

View check run for this annotation

Codecov / codecov/patch

crates/kitsune-config/src/mrf.rs#L5

Added line #L5 was not covered by tests
pub struct ArtifactCache {
pub path: SmolStr,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct FsKvStorage {
Expand All @@ -25,6 +30,7 @@ pub enum KvStorage {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct Configuration {
pub artifact_cache: Option<ArtifactCache>,
pub module_dir: SmolStr,
pub module_config: HashMap<SmolStr, SmolStr>,
pub storage: KvStorage,
Expand Down
6 changes: 6 additions & 0 deletions crates/kitsune-wasm-mrf/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ impl Cache {
}

Check warning on line 18 in crates/kitsune-wasm-mrf/src/cache.rs

View check run for this annotation

Codecov / codecov/patch

crates/kitsune-wasm-mrf/src/cache.rs#L18

Added line #L18 was not covered by tests

#[inline]
#[instrument(skip_all)]

Check warning on line 21 in crates/kitsune-wasm-mrf/src/cache.rs

View check run for this annotation

Codecov / codecov/patch

crates/kitsune-wasm-mrf/src/cache.rs#L21

Added line #L21 was not covered by tests
pub fn load(&self, engine: &wasmtime::Engine, component: &[u8]) -> Result<Option<Component>> {
let hash = blake3::hash(component);
let Some(precompiled) = self.inner.get(hash.as_bytes())? else {
return Ok(None);
};

debug!(hash = %hash.to_hex(), "hit component cache");

// SAFETY: The function is defined as unsafe since it is only doing very simple checks whether the precompiled component inside is actually valid
// But since we source our cache from disk, we can assume that the files are fine. If they aren't, the user has tempered with them or they were otherwise corrupted.
// If that's the case the user has bigger issues than a little memory unsafety here. And it's also nothing we can really protect against.
Expand All @@ -32,13 +35,16 @@ impl Cache {
}

#[inline]
#[instrument(skip_all)]

Check warning on line 38 in crates/kitsune-wasm-mrf/src/cache.rs

View check run for this annotation

Codecov / codecov/patch

crates/kitsune-wasm-mrf/src/cache.rs#L38

Added line #L38 was not covered by tests
pub fn store(&self, source: &[u8], component: &Component) -> Result<()> {
let hash = blake3::hash(source);
self.inner.insert(
hash.as_bytes(),
component.serialize().map_err(eyre::Report::msg)?,
)?;

debug!(hash = %hash.to_hex(), "stored component in cache");

Ok(())
}
}
34 changes: 29 additions & 5 deletions crates/kitsune-wasm-mrf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
extern crate tracing;

use self::{
cache::Cache,
ctx::{construct_store, Context},
mrf_wit::v1::fep::mrf::types::{Direction, Error as MrfError},
};
Expand Down Expand Up @@ -58,15 +59,31 @@ where
}

#[inline]
#[instrument(skip_all, fields(module_path = %module_path.display()))]

Check warning on line 62 in crates/kitsune-wasm-mrf/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/kitsune-wasm-mrf/src/lib.rs#L62

Added line #L62 was not covered by tests
fn load_mrf_module(
cache: Option<&Cache>,
engine: &Engine,
module_path: &Path,
bytes: &[u8],
) -> eyre::Result<Option<(ManifestV1<'static>, Component)>> {
let component = Component::new(engine, bytes)
.map_err(eyre::Report::msg)
.with_note(|| format!("path to the module: {}", module_path.display()))
.suggestion("Did you make the WASM file a component via `wasm-tools`?")?;
let compile_component = || {
Component::new(engine, bytes)
.map_err(eyre::Report::msg)
.with_note(|| format!("path to the module: {}", module_path.display()))
.suggestion("Did you make the WASM file a component via `wasm-tools`?")
};

Check warning on line 74 in crates/kitsune-wasm-mrf/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/kitsune-wasm-mrf/src/lib.rs#L69-L74

Added lines #L69 - L74 were not covered by tests

let component = if let Some(cache) = cache {
if let Some(component) = cache.load(engine, bytes)? {
component
} else {
let component = compile_component()?;
cache.store(bytes, &component)?;
component
}
} else {
compile_component()?
};

let Some((manifest, _section_range)) = mrf_manifest::decode(bytes)? else {
error!("missing manifest. skipping load.");
Expand Down Expand Up @@ -124,6 +141,12 @@ impl MrfService {

#[instrument(skip_all, fields(module_dir = %config.module_dir))]
pub async fn from_config(config: &MrfConfiguration) -> eyre::Result<Self> {
let cache = config
.artifact_cache
.as_ref()
.map(|cache_config| Cache::open(cache_config.path.as_str()))

Check warning on line 147 in crates/kitsune-wasm-mrf/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/kitsune-wasm-mrf/src/lib.rs#L147

Added line #L147 was not covered by tests
.transpose()?;

let storage = match config.storage {
KvStorage::Fs(FsKvStorage { ref path }) => {
kv_storage::FsBackend::from_path(path.as_str())?.into()
Expand All @@ -146,9 +169,10 @@ impl MrfService {
let wasm_data_stream = find_mrf_modules(config.module_dir.as_str())
.map_err(eyre::Report::from)
.and_then(|(module_path, wasm_data)| {
let cache = cache.as_ref();

Check warning on line 172 in crates/kitsune-wasm-mrf/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/kitsune-wasm-mrf/src/lib.rs#L172

Added line #L172 was not covered by tests
let engine = &engine;

async move { load_mrf_module(engine, &module_path, &wasm_data) }
async move { load_mrf_module(cache, engine, &module_path, &wasm_data) }

Check warning on line 175 in crates/kitsune-wasm-mrf/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/kitsune-wasm-mrf/src/lib.rs#L175

Added line #L175 was not covered by tests
});

tokio::pin!(wasm_data_stream);
Expand Down
49 changes: 49 additions & 0 deletions docs/src/configuring/mrf.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,52 @@ This configuration option tells Kitsune where to scan for WASM modules to load a
[mrf]
module-dir = "mrf-modules"
```

### `artifact-cache`

This configuration option tells Kitsune to cache compiled versions of the WASM components on-disk to improve startup times
by not having to recompile the components every time Kitsune starts up.

Simply omitting this configuration disables the caching.

```toml
[mrf.artifact-cache]
path = "./artifact-cache"
```

> Note: DO NOT modify the files inside the artifact cache. You can delete the cache to reclaim space, but DO NOT modify the files.
> The files inside the cache are treated by Kitsune as trusted executables. Modifying them may lead to unexpected behaviour and crashes.
### `storage`

Kitsune provides MRF modules with scoped key-value storages to allow them to persist data across runs for things like counters or spam lists.

We provide multiple backends here:

#### Redis

```toml
[mrf.storage]
type = "redis"
url = "redis://localhost"
pool-size = 5
```

#### Filesystem

```toml
[mrf.storage]
type = "fs"
path = "./mrf-storage"
```

### `module-config`

WASM MRFs can have configuration passed to them upon invocation. In Kitsune you configure them via key-value pairs inside your configuration as follows:

```toml
[mrf.module-config]
module-name = "my configuration"
```

The names that are used to select the configuration are sourced from the module manifest.

0 comments on commit 22eda85

Please sign in to comment.