diff --git a/packages/composer-runtime/lib/context.js b/packages/composer-runtime/lib/context.js index 3df8247bae..b21c61944d 100644 --- a/packages/composer-runtime/lib/context.js +++ b/packages/composer-runtime/lib/context.js @@ -55,6 +55,20 @@ class Context { LOG.exit(method); } + /** + * Get a compiled script bundle from the cache. + * @param {string} businessNetworkHash The hash of the business network definition. + * @return {CompiledScriptBundle} The cached compiled script bundle, or null if + * there is no entry in the cache for the specified business network definition. + */ + static getCachedCompiledScriptBundle(businessNetworkHash) { + const method = 'getCachedCompiledScriptBundle'; + LOG.entry(method, businessNetworkHash); + const result = compiledScriptBundleCache.get(businessNetworkHash); + LOG.exit(method, result); + return result; + } + /** * Store a compiled script bundle in the cache. * @param {string} businessNetworkHash The hash of the business network definition. @@ -67,6 +81,20 @@ class Context { LOG.exit(method); } + /** + * Get a compiled query bundle from the cache. + * @param {string} businessNetworkHash The hash of the business network definition. + * @return {CompiledQueryBundle} The cached compiled query bundle, or null if + * there is no entry in the cache for the specified business network definition. + */ + static getCachedCompiledQueryBundle(businessNetworkHash) { + const method = 'getCachedCompiledQueryBundle'; + LOG.entry(method, businessNetworkHash); + const result = compiledQueryBundleCache.get(businessNetworkHash); + LOG.exit(method, result); + return result; + } + /** * Store a compiled query bundle in the cache. * @param {string} businessNetworkHash The hash of the business network definition. @@ -79,6 +107,20 @@ class Context { LOG.exit(method); } + /** + * Get a compiled ACL bundle from the cache. + * @param {string} businessNetworkHash The hash of the business network definition. + * @return {CompiledAclBundle} The cached compiled ACL bundle, or null if + * there is no entry in the cache for the specified business network definition. + */ + static getCachedCompiledAclBundle(businessNetworkHash) { + const method = 'getCachedCompiledAclBundle'; + LOG.entry(method, businessNetworkHash); + const result = compiledAclBundleCache.get(businessNetworkHash); + LOG.exit(method, result); + return result; + } + /** * Store a compiled ACL bundle in the cache. * @param {string} businessNetworkHash The hash of the business network definition. @@ -197,7 +239,7 @@ class Context { const method = 'loadCompiledScriptBundle'; LOG.entry(method); LOG.debug(method, 'Looking in cache for compiled script bundle', businessNetworkRecord.hash); - let compiledScriptBundle = compiledScriptBundleCache.get(businessNetworkRecord.hash); + let compiledScriptBundle = Context.getCachedCompiledScriptBundle(businessNetworkRecord.hash); if (compiledScriptBundle) { LOG.debug(method, 'Compiled script bundle is in cache'); return Promise.resolve(compiledScriptBundle); @@ -227,7 +269,7 @@ class Context { const method = 'loadCompiledQueryBundle'; LOG.entry(method); LOG.debug(method, 'Looking in cache for compiled query bundle', businessNetworkRecord.hash); - let compiledQueryBundle = compiledQueryBundleCache.get(businessNetworkRecord.hash); + let compiledQueryBundle = Context.getCachedCompiledQueryBundle(businessNetworkRecord.hash); if (compiledQueryBundle) { LOG.debug(method, 'Compiled query bundle is in cache'); return Promise.resolve(compiledQueryBundle); @@ -257,7 +299,7 @@ class Context { const method = 'loadCompiledAclBundle'; LOG.entry(method); LOG.debug(method, 'Looking in cache for compiled ACL bundle', businessNetworkRecord.hash); - let compiledAclBundle = compiledAclBundleCache.get(businessNetworkRecord.hash); + let compiledAclBundle = Context.getCachedCompiledAclBundle(businessNetworkRecord.hash); if (compiledAclBundle) { LOG.debug(method, 'Compiled ACL bundle is in cache'); return Promise.resolve(compiledAclBundle); diff --git a/packages/composer-runtime/lib/engine.js b/packages/composer-runtime/lib/engine.js index 4dc458ca3b..66b4be2023 100644 --- a/packages/composer-runtime/lib/engine.js +++ b/packages/composer-runtime/lib/engine.js @@ -153,19 +153,28 @@ class Engine { Context.cacheBusinessNetwork(businessNetworkHash, businessNetworkDefinition); // Cache the compiled script bundle. - compiledScriptBundle = context.getScriptCompiler().compile(businessNetworkDefinition.getScriptManager()); - LOG.debug(method, 'Loaded compiled script bundle, storing in cache'); - Context.cacheCompiledScriptBundle(businessNetworkHash, compiledScriptBundle); + compiledScriptBundle = Context.getCachedCompiledScriptBundle(businessNetworkHash); + if (!compiledScriptBundle) { + compiledScriptBundle = context.getScriptCompiler().compile(businessNetworkDefinition.getScriptManager()); + LOG.debug(method, 'Loaded compiled script bundle, storing in cache'); + Context.cacheCompiledScriptBundle(businessNetworkHash, compiledScriptBundle); + } // Cache the compiled query bundle. - compiledQueryBundle = context.getQueryCompiler().compile(businessNetworkDefinition.getQueryManager()); - LOG.debug(method, 'Loaded compiled query bundle, storing in cache'); - Context.cacheCompiledQueryBundle(businessNetworkHash, compiledQueryBundle); + compiledQueryBundle = Context.getCachedCompiledQueryBundle(businessNetworkHash); + if (!compiledQueryBundle) { + compiledQueryBundle = context.getQueryCompiler().compile(businessNetworkDefinition.getQueryManager()); + LOG.debug(method, 'Loaded compiled query bundle, storing in cache'); + Context.cacheCompiledQueryBundle(businessNetworkHash, compiledQueryBundle); + } // Cache the compiled ACL bundle. - compiledAclBundle = context.getAclCompiler().compile(businessNetworkDefinition.getAclManager(), businessNetworkDefinition.getScriptManager()); - LOG.debug(method, 'Loaded compiled ACL bundle, storing in cache'); - Context.cacheCompiledAclBundle(businessNetworkHash, compiledAclBundle); + compiledAclBundle = Context.getCachedCompiledAclBundle(businessNetworkHash); + if (!compiledAclBundle) { + compiledAclBundle = context.getAclCompiler().compile(businessNetworkDefinition.getAclManager(), businessNetworkDefinition.getScriptManager()); + LOG.debug(method, 'Loaded compiled ACL bundle, storing in cache'); + Context.cacheCompiledAclBundle(businessNetworkHash, compiledAclBundle); + } // Get the sysdata collection where the business network definition is stored. LOG.debug(method, 'Loaded business network definition, storing in $sysdata collection'); diff --git a/packages/composer-runtime/test/engine.js b/packages/composer-runtime/test/engine.js index b8feea6fa8..a3af25626f 100644 --- a/packages/composer-runtime/test/engine.js +++ b/packages/composer-runtime/test/engine.js @@ -284,6 +284,108 @@ describe('Engine', () => { }); }); + it('should reuse the cached compiled script bundle', () => { + let sysdata = sinon.createStubInstance(DataCollection); + let sysregistries = sinon.createStubInstance(DataCollection); + mockDataService.ensureCollection.withArgs('$sysdata').resolves(sysdata); + let mockBusinessNetworkDefinition = sinon.createStubInstance(BusinessNetworkDefinition); + let mockScriptManager = sinon.createStubInstance(ScriptManager); + mockBusinessNetworkDefinition.getScriptManager.returns(mockScriptManager); + mockBusinessNetworkDefinition.getIdentifier.returns('test'); + sandbox.stub(BusinessNetworkDefinition, 'fromArchive').resolves(mockBusinessNetworkDefinition); + let mockScriptCompiler = sinon.createStubInstance(ScriptCompiler); + let mockCompiledScriptBundle = sinon.createStubInstance(CompiledScriptBundle); + mockScriptCompiler.compile.throws(new Error('should not be called')); + mockContext.getScriptCompiler.returns(mockScriptCompiler); + Context.cacheCompiledScriptBundle('dc9c1c09907c36f5379d615ae61c02b46ba254d92edb77cb63bdcc5247ccd01c', mockCompiledScriptBundle); + let mockQueryCompiler = sinon.createStubInstance(QueryCompiler); + let mockCompiledQueryBundle = sinon.createStubInstance(CompiledQueryBundle); + mockQueryCompiler.compile.returns(mockCompiledQueryBundle); + mockContext.getQueryCompiler.returns(mockQueryCompiler); + let mockAclCompiler = sinon.createStubInstance(AclCompiler); + let mockCompiledAclBundle = sinon.createStubInstance(CompiledAclBundle); + mockAclCompiler.compile.returns(mockCompiledAclBundle); + mockContext.getAclCompiler.returns(mockAclCompiler); + sysdata.add.withArgs('businessnetwork', sinon.match.any).resolves(); + mockDataService.ensureCollection.withArgs('$sysregistries').resolves(sysregistries); + mockRegistryManager.ensure.withArgs('Transaction', 'default', 'Default Transaction Registry').resolves(); + sandbox.stub(Context, 'cacheBusinessNetwork'); + sandbox.stub(Context, 'cacheCompiledScriptBundle'); + mockRegistryManager.createDefaults.resolves(); + return engine.init(mockContext, 'init', ['aGVsbG8gd29ybGQ=','{}']) + .then(() => { + sinon.assert.notCalled(Context.cacheCompiledScriptBundle); + }); + }); + + it('should reuse the cached compiled query bundle', () => { + let sysdata = sinon.createStubInstance(DataCollection); + let sysregistries = sinon.createStubInstance(DataCollection); + mockDataService.ensureCollection.withArgs('$sysdata').resolves(sysdata); + let mockBusinessNetworkDefinition = sinon.createStubInstance(BusinessNetworkDefinition); + let mockScriptManager = sinon.createStubInstance(ScriptManager); + mockBusinessNetworkDefinition.getScriptManager.returns(mockScriptManager); + mockBusinessNetworkDefinition.getIdentifier.returns('test'); + sandbox.stub(BusinessNetworkDefinition, 'fromArchive').resolves(mockBusinessNetworkDefinition); + let mockScriptCompiler = sinon.createStubInstance(ScriptCompiler); + let mockCompiledScriptBundle = sinon.createStubInstance(CompiledScriptBundle); + mockScriptCompiler.compile.returns(mockCompiledScriptBundle); + mockContext.getScriptCompiler.returns(mockScriptCompiler); + let mockQueryCompiler = sinon.createStubInstance(QueryCompiler); + let mockCompiledQueryBundle = sinon.createStubInstance(CompiledQueryBundle); + mockQueryCompiler.compile.throws(new Error('should not be called')); + mockContext.getQueryCompiler.returns(mockQueryCompiler); + Context.cacheCompiledQueryBundle('dc9c1c09907c36f5379d615ae61c02b46ba254d92edb77cb63bdcc5247ccd01c', mockCompiledQueryBundle); + let mockAclCompiler = sinon.createStubInstance(AclCompiler); + let mockCompiledAclBundle = sinon.createStubInstance(CompiledAclBundle); + mockAclCompiler.compile.returns(mockCompiledAclBundle); + mockContext.getAclCompiler.returns(mockAclCompiler); + sysdata.add.withArgs('businessnetwork', sinon.match.any).resolves(); + mockDataService.ensureCollection.withArgs('$sysregistries').resolves(sysregistries); + mockRegistryManager.ensure.withArgs('Transaction', 'default', 'Default Transaction Registry').resolves(); + sandbox.stub(Context, 'cacheBusinessNetwork'); + sandbox.stub(Context, 'cacheCompiledQueryBundle'); + mockRegistryManager.createDefaults.resolves(); + return engine.init(mockContext, 'init', ['aGVsbG8gd29ybGQ=','{}']) + .then(() => { + sinon.assert.notCalled(Context.cacheCompiledQueryBundle); + }); + }); + + it('should reuse the cached compiled ACL bundle', () => { + let sysdata = sinon.createStubInstance(DataCollection); + let sysregistries = sinon.createStubInstance(DataCollection); + mockDataService.ensureCollection.withArgs('$sysdata').resolves(sysdata); + let mockBusinessNetworkDefinition = sinon.createStubInstance(BusinessNetworkDefinition); + let mockScriptManager = sinon.createStubInstance(ScriptManager); + mockBusinessNetworkDefinition.getScriptManager.returns(mockScriptManager); + mockBusinessNetworkDefinition.getIdentifier.returns('test'); + sandbox.stub(BusinessNetworkDefinition, 'fromArchive').resolves(mockBusinessNetworkDefinition); + let mockScriptCompiler = sinon.createStubInstance(ScriptCompiler); + let mockCompiledScriptBundle = sinon.createStubInstance(CompiledScriptBundle); + mockScriptCompiler.compile.returns(mockCompiledScriptBundle); + mockContext.getScriptCompiler.returns(mockScriptCompiler); + let mockQueryCompiler = sinon.createStubInstance(QueryCompiler); + let mockCompiledQueryBundle = sinon.createStubInstance(CompiledQueryBundle); + mockQueryCompiler.compile.returns(mockCompiledQueryBundle); + mockContext.getQueryCompiler.returns(mockQueryCompiler); + let mockAclCompiler = sinon.createStubInstance(AclCompiler); + let mockCompiledAclBundle = sinon.createStubInstance(CompiledAclBundle); + mockAclCompiler.compile.throws(new Error('should not be called')); + mockContext.getAclCompiler.returns(mockAclCompiler); + Context.cacheCompiledAclBundle('dc9c1c09907c36f5379d615ae61c02b46ba254d92edb77cb63bdcc5247ccd01c', mockCompiledAclBundle); + sysdata.add.withArgs('businessnetwork', sinon.match.any).resolves(); + mockDataService.ensureCollection.withArgs('$sysregistries').resolves(sysregistries); + mockRegistryManager.ensure.withArgs('Transaction', 'default', 'Default Transaction Registry').resolves(); + sandbox.stub(Context, 'cacheBusinessNetwork'); + sandbox.stub(Context, 'cacheCompiledAclBundle'); + mockRegistryManager.createDefaults.resolves(); + return engine.init(mockContext, 'init', ['aGVsbG8gd29ybGQ=','{}']) + .then(() => { + sinon.assert.notCalled(Context.cacheCompiledAclBundle); + }); + }); + it('should throw if an error occurs', () => { let mockDataCollection = sinon.createStubInstance(DataCollection); mockDataService.getCollection.rejects();