Skip to content

Use a per-documentLoader resolved context cache. #552

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# jsonld ChangeLog

## 8.4.0 - 2024-xx-xx

### Fixed
- Use a per-`documentLoader` resolved context cache. Fixes an issue where
multiple document loaders are used which each have different values for
static contexts.

## 8.3.2 - 2023-12-06

### Fixed
Expand Down
69 changes: 48 additions & 21 deletions lib/jsonld.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,28 @@ const wrapper = function(jsonld) {
/** Registered RDF dataset parsers hashed by content-type. */
const _rdfParsers = {};

// resolved context cache
// TODO: consider basing max on context size rather than number
// resolved context caches
// TODO: add controls for cache resource usage
// cache size per document loader
const RESOLVED_CONTEXT_CACHE_MAX_SIZE = 100;
const _resolvedContextCache = new LRU({max: RESOLVED_CONTEXT_CACHE_MAX_SIZE});
// caches are created and indexed per documentLoader
// resources are cleaned up with WeakMap semantics for the documentLoaders
const _resolvedContextCaches = new WeakMap();
// default key to use when no documentLoader used
const _defaultDocumentLoaderKey = Symbol();

// make a ContextResolver using a per-documentLoader shared cache
function _makeContextResolver({documentLoader = _defaultDocumentLoaderKey}) {
let cache = _resolvedContextCaches.get(documentLoader);
if(!cache) {
// TODO: consider basing max on context size rather than number
cache = new LRU({max: RESOLVED_CONTEXT_CACHE_MAX_SIZE});
_resolvedContextCaches.set(documentLoader, cache);
}
return new ContextResolver({
sharedCache: cache
});
}

/* Core API */

Expand Down Expand Up @@ -152,8 +170,9 @@ jsonld.compact = async function(input, ctx, options) {
skipExpansion: false,
link: false,
issuer: new IdentifierIssuer('_:b'),
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
contextResolver: _makeContextResolver({
documentLoader: options ? options.documentLoader : undefined
})
});
if(options.link) {
// force skip expansion when linking, "link" is not part of the public
Expand Down Expand Up @@ -269,8 +288,9 @@ jsonld.expand = async function(input, options) {
// set default options
options = _setDefaults(options, {
keepFreeFloatingNodes: false,
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
contextResolver: _makeContextResolver({
documentLoader: options ? options.documentLoader : undefined
})
});

// build set of objects that may have @contexts to resolve
Expand Down Expand Up @@ -368,8 +388,9 @@ jsonld.flatten = async function(input, ctx, options) {
// set default options
options = _setDefaults(options, {
base: _isString(input) ? input : '',
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
contextResolver: _makeContextResolver({
documentLoader: options ? options.documentLoader : undefined
})
});

// expand input
Expand Down Expand Up @@ -423,8 +444,9 @@ jsonld.frame = async function(input, frame, options) {
requireAll: false,
omitDefault: false,
bnodesToClear: [],
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
contextResolver: _makeContextResolver({
documentLoader: options ? options.documentLoader : undefined
})
});

// if frame is a string, attempt to dereference remote document
Expand Down Expand Up @@ -565,8 +587,9 @@ jsonld.normalize = jsonld.canonize = async function(input, options) {
algorithm: 'URDNA2015',
skipExpansion: false,
safe: true,
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
contextResolver: _makeContextResolver({
documentLoader: options ? options.documentLoader : undefined
})
});
if('inputFormat' in options) {
if(options.inputFormat !== 'application/n-quads' &&
Expand Down Expand Up @@ -674,8 +697,9 @@ jsonld.toRDF = async function(input, options) {
options = _setDefaults(options, {
base: _isString(input) ? input : '',
skipExpansion: false,
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
contextResolver: _makeContextResolver({
documentLoader: options ? options.documentLoader : undefined
})
});

// TODO: support toRDF custom map?
Expand Down Expand Up @@ -726,8 +750,9 @@ jsonld.createNodeMap = async function(input, options) {
// set default options
options = _setDefaults(options, {
base: _isString(input) ? input : '',
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
contextResolver: _makeContextResolver({
documentLoader: options ? options.documentLoader : undefined
})
});

// expand input
Expand Down Expand Up @@ -774,8 +799,9 @@ jsonld.merge = async function(docs, ctx, options) {

// set default options
options = _setDefaults(options, {
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
contextResolver: _makeContextResolver({
documentLoader: options ? options.documentLoader : undefined
})
});

// expand all documents
Expand Down Expand Up @@ -926,8 +952,9 @@ jsonld.processContext = async function(
// set default options
options = _setDefaults(options, {
base: '',
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
contextResolver: _makeContextResolver({
documentLoader: options ? options.documentLoader : undefined
})
});

// return initial context early for null context
Expand Down
57 changes: 57 additions & 0 deletions tests/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,63 @@ describe('loading multiple levels of contexts', () => {
});
});

// check that internal caching is unique for each document loader
describe('unique document loaders caching', () => {
const documentLoader0 = url => {
if(url === 'https://example.com/context') {
return {
document: {
"@context": {
"ex": "https://example.com/0#"
}
},
// must be marked static to get into the shared cache
tag: 'static',
contextUrl: null,
documentUrl: url
};
}
};
const documentLoader1 = url => {
if(url === 'https://example.com/context') {
return {
document: {
"@context": {
"ex": "https://example.com/1#"
}
},
contextUrl: null,
documentUrl: url
};
}
};
const doc = {
"@context": "https://example.com/context",
"ex:test": "test"
};
const expected0 = [{
"https://example.com/0#test": [{
"@value": "test"
}]
}];
const expected1 = [{
"https://example.com/1#test": [{
"@value": "test"
}]
}];

it('unique document loader caches', async () => {
const expanded0 = await jsonld.expand(doc, {
documentLoader: documentLoader0
});
assert.deepEqual(expanded0, expected0);
const expanded1 = await jsonld.expand(doc, {
documentLoader: documentLoader1
});
assert.deepEqual(expanded1, expected1);
});
});

describe('url tests', () => {
it('should detect absolute IRIs', done => {
// absolute IRIs
Expand Down
Loading