Skip to content
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
28 changes: 28 additions & 0 deletions .changeset/loader-cache-stays-small.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
"fiber": patch
---

A collection whose OpenAPI schemas refer to each other in a ring no longer
writes a loader cache of gigabytes, and the MCP server no longer hangs on one.

The cache stored every endpoint's request and response schema with each `$ref`
expanded in place, stopping only where a reference met itself on the current
path. Schemas that refer to each other — an operand that is an object or a
function, each of which holds operands — have a number of such paths that
grows factorially, and every endpoint got its own copy: one real collection's
cache reached 2.5 GB, a single endpoint's schema 252 MB. Every MCP call, down
to `list_sections`, read that file first, so the server answered `initialize`
and then nothing else.

Schemas now live in a file of their own beside the endpoint list, stored as the
document wrote them, with each definition they reach kept once. An endpoint's
schema is put together when it is opened or asked for by `get_endpoint`, with
anything used more than once written a single time under `$defs` — which the
editor's linting already reads — so it can never be larger than the document it
came from. Listing, searching and deciding whether a call is allowed read only
the endpoint list. Request bodies built from such schemas are bounded too,
since a depth limit alone still let them branch into hundreds of kilobytes.

A cache written by an earlier version still lists its endpoints straight away:
reading stops at the end of the endpoint list, before the schemas. Its schemas
are left unread until the next refresh writes both files.
45 changes: 35 additions & 10 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,19 @@ mod gui {
/// `loader_schema` used to deserialize the whole on-disk file — every
/// expanded schema included — on each endpoint click. The first read (or
/// the last successful run) lives here so later clicks are a map lookup.
///
/// Schemas are held apart from endpoints, and read the first time one is
/// asked for: the sidebar never needs them.
struct LoaderMem {
inner: Mutex<HashMap<String, loader::LoaderCache>>,
schemas: Mutex<HashMap<String, Arc<loader::LoaderSchemas>>>,
}

impl LoaderMem {
fn new() -> Self {
Self {
inner: Mutex::new(HashMap::new()),
schemas: Mutex::new(HashMap::new()),
}
}

Expand Down Expand Up @@ -112,11 +117,20 @@ mod gui {
}

fn schema(&self, dir: &Path, section_id: &str, endpoint_id: &str) -> EndpointSchemas {
let mut map = self.lock();
let cache = Self::fill(&mut map, dir, section_id);
let schemas = self
.schemas
.lock()
.unwrap_or_else(|err| err.into_inner())
.entry(section_id.to_string())
.or_insert_with(|| {
Arc::new(loader::read_schemas(dir, section_id).unwrap_or_default())
})
.clone();
// Outside the lock: bundling is per endpoint and needs only the
// shared, immutable copy.
EndpointSchemas {
request: cache.schemas.get(endpoint_id).cloned(),
response: cache.response_schemas.get(endpoint_id).cloned(),
request: schemas.request_for(endpoint_id),
response: schemas.response_for(endpoint_id),
}
}

Expand All @@ -125,12 +139,25 @@ mod gui {
Self::fill(&mut map, dir, section_id).endpoints.clone()
}

fn remember(&self, section_id: &str, cache: loader::LoaderCache) {
fn remember(
&self,
section_id: &str,
cache: loader::LoaderCache,
schemas: loader::LoaderSchemas,
) {
self.lock().insert(section_id.to_string(), cache);
self.schemas
.lock()
.unwrap_or_else(|err| err.into_inner())
.insert(section_id.to_string(), Arc::new(schemas));
}

fn forget(&self, section_id: &str) {
self.lock().remove(section_id);
self.schemas
.lock()
.unwrap_or_else(|err| err.into_inner())
.remove(section_id);
}
}

Expand Down Expand Up @@ -513,7 +540,7 @@ mod gui {
&section,
);

let (endpoints, schemas, response_schemas, pages) = loader::run(&config, fetcher).await?;
let (endpoints, schemas, pages) = loader::run(&config, fetcher).await?;

let previous = mem.endpoints(&paths.loaders, &section_id);
let (added, removed) = loader::diff(&previous, &endpoints);
Expand All @@ -522,13 +549,11 @@ mod gui {
let cache = loader::LoaderCache {
loaded_at,
endpoints: endpoints.clone(),
schemas,
response_schemas,
};
if let Err(err) = loader::write_cache(&paths.loaders, &section_id, &cache) {
if let Err(err) = loader::write_cache(&paths.loaders, &section_id, &cache, &schemas) {
::log::warn!("could not cache loader output: {err}");
}
mem.remember(&section_id, cache);
mem.remember(&section_id, cache, schemas);

Ok(LoaderRun {
endpoints,
Expand Down
Loading
Loading