diff --git a/__tests__/server/utils/__snapshots__/pollModuleMap.spec.js.snap b/__tests__/server/utils/__snapshots__/pollModuleMap.spec.js.snap index 1af2d62c4..3154b586b 100644 --- a/__tests__/server/utils/__snapshots__/pollModuleMap.spec.js.snap +++ b/__tests__/server/utils/__snapshots__/pollModuleMap.spec.js.snap @@ -29,18 +29,3 @@ exports[`pollModuleMap polling monitor logs when polling is not considered stopp "pollModuleMap: polling is working as expected. Last poll: 2000ms ago, Max poll: 10000ms.", ] `; - -exports[`pollModuleMap restores the state config on poll failure 1`] = ` -[ - [ - { - "client": { - "someUrl": "http://client.com", - }, - "server": { - "someUrl": "http://server.com", - }, - }, - ], -] -`; diff --git a/__tests__/server/utils/loadModules.spec.jsx b/__tests__/server/utils/loadModules.spec.jsx index 4429e810d..bb5a5fe4e 100644 --- a/__tests__/server/utils/loadModules.spec.jsx +++ b/__tests__/server/utils/loadModules.spec.jsx @@ -46,9 +46,11 @@ const RootModule = () => ({}); let modules; let fetchedModuleMap; -function setFetchedRootModuleVersion(version) { + +// set new version ensuring that updateModuleRegistry will be called. +function setFetchedModuleVersion(name = 'some-root', version = '1.0.0') { modules = { - 'some-root': { + [name]: { node: { url: `https://example.com/cdn/some-root/${version}/some-root.node.js`, integrity: '4y45hr', @@ -70,7 +72,9 @@ function setFetchedRootModuleVersion(version) { describe('loadModules', () => { beforeAll(() => { getModule.mockImplementation(() => RootModule); - updateModuleRegistry.mockImplementation(() => modules); + updateModuleRegistry.mockImplementation(() => ({ + loadedModules: modules, + })); global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve(fetchedModuleMap), @@ -83,25 +87,26 @@ describe('loadModules', () => { }); it('updates the holocron module registry', async () => { - setFetchedRootModuleVersion('1.1.1'); + setFetchedModuleVersion('some-root', '1.1.1'); await loadModules(); expect(updateModuleRegistry).toHaveBeenCalledWith({ moduleMap: addBaseUrlToModuleMap(fetchedModuleMap), batchModulesToUpdate: require('../../../src/server/utils/batchModulesToUpdate').default, getModulesToUpdate: require('../../../src/server/utils/getModulesToUpdate').default, onModuleLoad: require('../../../src/server/utils/onModuleLoad').default, + listRejectedModules: true, }); }); it('updates the client module map cache', async () => { - setFetchedRootModuleVersion('1.1.2'); + setFetchedModuleVersion('some-root', '1.1.2'); await loadModules(); expect(getClientModuleMapCache()).toMatchSnapshot(); }); it('returns loaded modules', async () => { - setFetchedRootModuleVersion('2.0.0'); - const loadedModules = await loadModules(); + setFetchedModuleVersion('some-root', '2.0.0'); + const { loadedModules } = await loadModules(); expect(loadedModules).toEqual({ 'some-root': { browser: { @@ -121,8 +126,8 @@ describe('loadModules', () => { }); it('doesnt update caches when there are no changes', async () => { - setFetchedRootModuleVersion('1.1.3'); - updateModuleRegistry.mockImplementationOnce(() => ({})); + setFetchedModuleVersion('some-root', '1.1.3'); + updateModuleRegistry.mockImplementationOnce(() => ({ loadedModules: {}, rejectedModules: {} })); await loadModules(); expect(getClientModuleMapCache()).toMatchSnapshot(); }); @@ -131,21 +136,49 @@ describe('loadModules', () => { RootModule[CONFIGURATION_KEY] = { csp: "default-src 'none';", }; - setFetchedRootModuleVersion('1.1.4'); + setFetchedModuleVersion('some-root', '1.1.4'); await loadModules(); expect(updateCSP).toHaveBeenCalledWith("default-src 'none';"); }); it('calls updateCSP even when csp is not set', async () => { - setFetchedRootModuleVersion('1.1.5'); + setFetchedModuleVersion('some-root', '1.1.5'); delete RootModule[CONFIGURATION_KEY].csp; await loadModules(); expect(updateCSP).toHaveBeenCalledWith(undefined); }); + it('updates rejected module cache', async () => { + setFetchedModuleVersion('rejected-module', '1.0.0'); + const rejectedModules = { + 'rejected-module': { + node: { + url: 'https://example.com/cdn/rejected-module/1.0.0/rejected-module.node.js', + integrity: '4y45hr', + }, + browser: { + url: 'https://example.com/cdn/rejected-module/1.0.0/rejected-module.browser.js', + integrity: 'nggdfhr34', + }, + legacyBrowser: { + url: 'https://example.com/cdn/rejected-module/1.0.0/rejected-module.legacy.browser.js', + integrity: '7567ee', + }, + reasonForRejection: 'not compatible', + }, + }; + updateModuleRegistry.mockImplementationOnce(() => ({ rejectedModules })); + // first call to populate cache + await loadModules(); + // second call to retrieve from cache + const loadedModules = await loadModules(); + expect(updateModuleRegistry.mock.calls).toHaveLength(1); + expect(loadedModules.rejectedModules).toEqual(rejectedModules); + }); + describe('when root module not loaded', () => { beforeAll(() => { - updateModuleRegistry.mockImplementation(() => ({ + const loadedModules = { 'not-a-root': { node: { url: 'https://example.com/cdn/not-a-root/2.2.2/not-a-root.node.js', @@ -160,11 +193,15 @@ describe('loadModules', () => { integrity: '7567ee', }, }, + }; + updateModuleRegistry.mockImplementation(() => ({ + loadedModules, + rejectedModules: {}, })); }); it('does not attempt to update CSP', async () => { - setFetchedRootModuleVersion('1.1.6'); + setFetchedModuleVersion('not-a-root', '2.2.2'); await loadModules(); expect(updateCSP).not.toHaveBeenCalled(); }); @@ -172,7 +209,7 @@ describe('loadModules', () => { describe('when module map does not change', () => { beforeAll(async () => { - setFetchedRootModuleVersion('1.1.1'); + setFetchedModuleVersion('some-root', '1.1.1'); await loadModules(); jest.clearAllMocks(); }); @@ -181,7 +218,7 @@ describe('loadModules', () => { expect(updateModuleRegistry).not.toHaveBeenCalled(); }); it('does not return any modules', async () => { - const loadedModules = await loadModules(); + const { loadedModules } = await loadModules(); expect(loadedModules).toEqual({}); }); }); diff --git a/__tests__/server/utils/pollModuleMap.spec.js b/__tests__/server/utils/pollModuleMap.spec.js index f5e9bf74c..a08ccf8b4 100644 --- a/__tests__/server/utils/pollModuleMap.spec.js +++ b/__tests__/server/utils/pollModuleMap.spec.js @@ -19,21 +19,6 @@ jest.spyOn(global, 'setTimeout'); jest.spyOn(global, 'setInterval'); jest.spyOn(global, 'setImmediate'); -jest.mock('../../../src/server/utils/stateConfig', () => ({ - setStateConfig: jest.fn(), - getClientStateConfig: jest.fn(), - getServerStateConfig: jest.fn(), - backupModuleStateConfig: jest.fn(() => ({ - client: { - someUrl: 'http://client.com', - }, - server: { - someUrl: 'http://server.com', - }, - })), - restoreModuleStateConfig: jest.fn(), -})); - describe('pollModuleMap', () => { jest.spyOn(console, 'log').mockImplementation(() => {}); jest.spyOn(console, 'warn').mockImplementation(() => {}); @@ -45,10 +30,6 @@ describe('pollModuleMap', () => { let setGauge; let resetGauge; let holocronMetrics; - let getModulesUsingExternals; - let setModulesUsingExternals; - let restoreModuleStateConfig; - let backupModuleStateConfig; function load({ min, max } = {}) { jest.resetModules(); @@ -65,7 +46,7 @@ describe('pollModuleMap', () => { process.env.ONE_MAP_POLLING_MAX = `${max}`; } - loadModulesPromise = Promise.resolve({}); + loadModulesPromise = Promise.resolve({ loadModules: {}, rejectedModules: {} }); jest.mock('../../../src/server/utils/loadModules', () => jest.fn()); loadModules = require('../../../src/server/utils/loadModules'); loadModules.mockImplementation(() => loadModulesPromise); @@ -79,9 +60,6 @@ describe('pollModuleMap', () => { holocron: holocronMetrics, } = require('../../../src/server/metrics')); - ({ getModulesUsingExternals, setModulesUsingExternals } = require('../../../src/server/utils/onModuleLoad')); - ({ restoreModuleStateConfig, backupModuleStateConfig } = require('../../../src/server/utils/stateConfig')); - return require('../../../src/server/utils/pollModuleMap'); } @@ -127,77 +105,22 @@ describe('pollModuleMap', () => { ); }); - it('calls loadModules', () => { + it('calls loadModules', async () => { const { default: pollModuleMap } = load(); - pollModuleMap(); + await pollModuleMap(); expect(console.log).toHaveBeenCalledWith('pollModuleMap: polling...'); expect(loadModules).toHaveBeenCalledTimes(1); expect(incrementCounter).toHaveBeenCalledTimes(1); expect(incrementCounter).toHaveBeenCalledWith(holocronMetrics.moduleMapPoll); - return loadModulesPromise; }); it('schedules a new polling', async () => { const { default: pollModuleMap } = load(); await pollModuleMap(); expect(loadModules).toHaveBeenCalledTimes(1); - return loadModulesPromise - .then(() => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout.mock.calls[0][0]).toBe(pollModuleMap); - }); - }); - - it('does not reset the modules using externals on poll success', () => { - const { default: pollModuleMap } = load(); - pollModuleMap(); - - return loadModulesPromise - .then(() => { - expect(getModulesUsingExternals).toHaveBeenCalledTimes(1); - expect(setModulesUsingExternals).not.toHaveBeenCalled(); - }); - }); - - it('resets the modules using externals on poll failure', async () => { - const { default: pollModuleMap } = load(); - - console.log - // monitor setup - .mockImplementationOnce(() => { /* noop a few times */ }) - // pollModuleMap run 1 - .mockImplementationOnce(() => { throw new Error('STDOUT pipe closed unexpectedly'); }); - - await pollModuleMap(); - - expect(getModulesUsingExternals).toHaveBeenCalledTimes(1); - expect(setModulesUsingExternals).toHaveBeenCalledTimes(1); - expect(setModulesUsingExternals).toHaveBeenCalledWith(['module-a', 'module-b']); - }); - - it('restores the state config on poll failure', async () => { - const { default: pollModuleMap } = load(); - - console.log - // monitor setup - .mockImplementationOnce(() => { /* noop a few times */ }) - // pollModuleMap run 1 - .mockImplementationOnce(() => { throw new Error('STDOUT pipe closed unexpectedly'); }); - - await pollModuleMap(); - expect(backupModuleStateConfig).toHaveBeenCalled(); - expect(restoreModuleStateConfig.mock.calls).toMatchSnapshot(); - }); - - it('does not reset the modules when error was thrown getting modules using externals', async () => { - const { default: pollModuleMap } = load(); - - getModulesUsingExternals.mockImplementationOnce(() => { throw new Error('failed to get modules using externals'); }); - - await pollModuleMap(); - expect(getModulesUsingExternals).toHaveBeenCalledTimes(1); - expect(setModulesUsingExternals).not.toHaveBeenCalled(); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout.mock.calls[0][0]).toBe(pollModuleMap); }); it('schedules a new polling despite console.log throwing on the initial check', async () => { @@ -289,83 +212,88 @@ describe('pollModuleMap', () => { setTimeout.mockImplementationOnce(() => ({ unref: mockUnref })); await pollModuleMap(); expect(loadModules).toHaveBeenCalledTimes(1); - - return loadModulesPromise - .then(() => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(mockUnref).toHaveBeenCalledTimes(1); - }); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(mockUnref).toHaveBeenCalledTimes(1); }); it('resets the time to the next polling to the minimum if there were updates', async () => { - const { default: pollModuleMap } = load(); - const moduleMapUpdates = { 'module-name': '1.2.3' }; - loadModulesPromise = Promise.resolve(moduleMapUpdates); + const { default: pollModuleMap, MIN_POLL_TIME } = load(); + const moduleMapUpdates = { 'module-name': 'module-data-here' }; + loadModulesPromise = Promise.resolve({ loadedModules: moduleMapUpdates }); await pollModuleMap(); - expect(loadModules).toHaveBeenCalledTimes(1); - return loadModulesPromise - .then(() => { - expect(console.log).toHaveBeenCalledTimes(3); - expect(console.log.mock.calls[2][0]).toMatch(/^pollModuleMap: 1 modules loaded\/updated:$/); - expect(console.log.mock.calls[2][1]).toEqual(moduleMapUpdates); + expect(loadModules).toHaveBeenCalledTimes(1); + expect(console.log).toHaveBeenCalledTimes(3); + expect(console.log.mock.calls[2][0]).toMatch(/^pollModuleMap: 1 modules loaded\/updated:$/); + expect(console.log.mock.calls[2][1]).toEqual(moduleMapUpdates); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenCalledWith(pollModuleMap, 5e3); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenCalledWith(pollModuleMap, MIN_POLL_TIME); - expect(setGauge).toHaveBeenCalledTimes(1); - expect(setGauge).toHaveBeenCalledWith(holocronMetrics.moduleMapPollWait, 5); - expect(resetGauge).toHaveBeenCalledTimes(1); - expect(resetGauge).toHaveBeenCalledWith(holocronMetrics.moduleMapPollConsecutiveErrors); - }); + expect(setGauge).toHaveBeenCalledTimes(1); + expect(setGauge).toHaveBeenCalledWith(holocronMetrics.moduleMapPollWait, 5); + expect(resetGauge).toHaveBeenCalledTimes(1); + expect(resetGauge).toHaveBeenCalledWith(holocronMetrics.moduleMapPollConsecutiveErrors); }); it('increases the time to the next polling if there were no updates', async () => { - const { default: pollModuleMap } = load(); + const { default: pollModuleMap, MIN_POLL_TIME } = load(); await pollModuleMap(); expect(loadModules).toHaveBeenCalledTimes(1); - return loadModulesPromise - .then(() => { - expect(console.log).toHaveBeenCalledTimes(3); - expect(console.log.mock.calls[2][0]).toMatch( - /^pollModuleMap: no updates, looking again in \d+s$/ - ); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout.mock.calls[0][0]).toBe(pollModuleMap); - expect(setTimeout.mock.calls[0][1]).toBeGreaterThanOrEqual(5e3 * 1.25); - - expect(setGauge).toHaveBeenCalledTimes(1); - expect(setGauge.mock.calls[0][0]).toBe(holocronMetrics.moduleMapPollWait); - expect(setGauge.mock.calls[0][1]).toBeGreaterThanOrEqual(5 * 1.25); - // ensure we're using seconds, not milliseconds - expect(setGauge.mock.calls[0][1]).toBeLessThan(8 * 1.25); - }); + expect(console.log).toHaveBeenCalledTimes(3); + expect(console.log.mock.calls[2][0]).toMatch( + /^pollModuleMap: no updates, looking again in \d+s$/ + ); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout.mock.calls[0][0]).toBe(pollModuleMap); + // check that the interval has increased + expect(setTimeout.mock.calls[0][1]).toBeGreaterThanOrEqual(MIN_POLL_TIME * 1.25); + + expect(setGauge).toHaveBeenCalledTimes(1); + expect(setGauge.mock.calls[0][0]).toBe(holocronMetrics.moduleMapPollWait); + expect(setGauge.mock.calls[0][1]).toBeGreaterThanOrEqual(5 * 1.25); + // ensure we're using seconds, not milliseconds + expect(setGauge.mock.calls[0][1]).toBeLessThan(8 * 1.25); }); it('resets the time to the next polling to the minimum if there were errors', async () => { - const { default: pollModuleMap } = load(); + const { default: pollModuleMap, MIN_POLL_TIME } = load(); const error = { message: 'sample test error' }; loadModulesPromise = Promise.reject(error); await pollModuleMap(); expect(loadModules).toHaveBeenCalledTimes(1); + expect(console.error).toHaveBeenCalledTimes(1); + expect(console.error).toHaveBeenCalledWith('pollModuleMap: error polling', error); + + expect(setTimeout).toHaveBeenCalledTimes(1); + // check that the interval has reset + expect(setTimeout).toHaveBeenCalledWith(pollModuleMap, MIN_POLL_TIME); + + expect(setGauge).toHaveBeenCalledTimes(1); + expect(setGauge).toHaveBeenCalledWith(holocronMetrics.moduleMapPollWait, 5); + expect(incrementGauge).toHaveBeenCalledTimes(1); + expect(incrementGauge).toHaveBeenCalledWith(holocronMetrics.moduleMapPollConsecutiveErrors); + }); + + it('resets the time to the next polling to the minimum if there were rejected modules', async () => { + const { default: pollModuleMap, MIN_POLL_TIME } = load(); + loadModulesPromise = Promise.resolve({ rejectedModules: { 'bad-module': { reasonForRejection: 'not compatible' } } }); + await pollModuleMap(); + expect(loadModules).toHaveBeenCalledTimes(1); - return loadModulesPromise - // catch and then so as to run after pollModuleMap's error handling - .catch((err) => err) - .then(() => { - expect(console.error).toHaveBeenCalledTimes(1); - expect(console.error).toHaveBeenCalledWith('pollModuleMap: error polling', error); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenCalledWith(pollModuleMap, 5e3); - - expect(setGauge).toHaveBeenCalledTimes(1); - expect(setGauge).toHaveBeenCalledWith(holocronMetrics.moduleMapPollWait, 5); - expect(incrementGauge).toHaveBeenCalledTimes(1); - expect(incrementGauge).toHaveBeenCalledWith(holocronMetrics.moduleMapPollConsecutiveErrors); - }); + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith('pollModuleMap: 1 modules rejected:', ['bad-module: not compatible']); + + expect(setTimeout).toHaveBeenCalledTimes(1); + // check that the poll interval is reset to min. + expect(setTimeout).toHaveBeenCalledWith(pollModuleMap, MIN_POLL_TIME); + + expect(setGauge).toHaveBeenCalledTimes(1); + expect(setGauge).toHaveBeenCalledWith(holocronMetrics.moduleMapPollWait, 5); + expect(incrementGauge).toHaveBeenCalledTimes(1); + expect(incrementGauge).toHaveBeenCalledWith(holocronMetrics.moduleMapPollConsecutiveErrors); }); it('schedules a new polling despite console.error throwing on the initial check', async () => { @@ -426,11 +354,7 @@ describe('pollModuleMap', () => { const { default: pollModuleMap, getModuleMapHealth } = load(); await pollModuleMap(); expect(loadModules).toHaveBeenCalledTimes(1); - - return loadModulesPromise - .then(() => { - expect(getModuleMapHealth()).toBe(true); - }); + expect(getModuleMapHealth()).toBe(true); }); it('marks the module map as unhealthy if there is an error', async () => { @@ -440,12 +364,7 @@ describe('pollModuleMap', () => { loadModulesPromise = Promise.reject(error); await pollModuleMap(); expect(loadModules).toHaveBeenCalledTimes(1); - return loadModulesPromise - // catch and then so as to run after pollModuleMap's error handling - .catch((err) => err) - .then(() => { - expect(getModuleMapHealth()).toBe(false); - }); + expect(getModuleMapHealth()).toBe(false); }); describe('polling monitor', () => { diff --git a/package-lock.json b/package-lock.json index ee68ad3d0..068fc1de2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "fastify": "^4.10.2", "fastify-plugin": "^4.2.0", "helmet": "^6.0.0", - "holocron": "^1.6.0", + "holocron": "^1.7.0", "holocron-module-route": "^1.3.0", "if-env": "^1.0.4", "immutable": "^4.1.0", @@ -14673,9 +14673,9 @@ } }, "node_modules/holocron": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/holocron/-/holocron-1.6.0.tgz", - "integrity": "sha512-dZpmB8oPcd2cM3XtbFovZMfIJsVE9QVZOvf5/xQAjVhiU2DpI9qoqvhOJYBQPICkION1hlJRmENrHi6MUgUVKQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/holocron/-/holocron-1.7.0.tgz", + "integrity": "sha512-gz88zT+JuIWC19o+7tgFl9whUqrCS5TGFga2QSJU3CLLYzlYMf1UsW13lGE1QjBIQDDwYlwPY2+FSUeejeg9mg==", "dependencies": { "@americanexpress/vitruvius": "^2.0.0", "hoist-non-react-statics": "^3.3.0", diff --git a/package.json b/package.json index dcac40292..83e42ddbb 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "fastify": "^4.10.2", "fastify-plugin": "^4.2.0", "helmet": "^6.0.0", - "holocron": "^1.6.0", + "holocron": "^1.7.0", "holocron-module-route": "^1.3.0", "if-env": "^1.0.4", "immutable": "^4.1.0", diff --git a/src/server/utils/loadModules.js b/src/server/utils/loadModules.js index 3e8c5a42a..505aa311f 100644 --- a/src/server/utils/loadModules.js +++ b/src/server/utils/loadModules.js @@ -27,6 +27,7 @@ import { updateCSP } from '../plugins/csp'; import addBaseUrlToModuleMap from './addBaseUrlToModuleMap'; let cachedModuleMapHash; +let rejectedModulesCache = {}; const loadModules = async () => { const moduleMapResponse = await fetch(process.env.HOLOCRON_MODULE_MAP_URL); @@ -34,18 +35,19 @@ const loadModules = async () => { const moduleMapHash = hash(moduleMap); if (cachedModuleMapHash && cachedModuleMapHash === moduleMapHash) { - return {}; + return { loadedModules: {}, rejectedModules: rejectedModulesCache }; } cachedModuleMapHash = moduleMapHash; const serverConfig = getServerStateConfig(); - - const loadedModules = await updateModuleRegistry({ + const { loadedModules = {}, rejectedModules = {} } = await updateModuleRegistry({ moduleMap, batchModulesToUpdate, onModuleLoad, getModulesToUpdate, + listRejectedModules: true, }); + rejectedModulesCache = rejectedModules; const loadedModuleNames = Object.keys(loadedModules); if (loadedModuleNames.length > 0) { @@ -54,15 +56,13 @@ const loadModules = async () => { const rootModuleLoaded = loadedModuleNames.includes(serverConfig.rootModuleName); - if (!rootModuleLoaded) { - return loadedModules; + if (rootModuleLoaded) { + const RootModule = getModule(serverConfig.rootModuleName); + const { [CONFIGURATION_KEY]: { csp } = {} } = RootModule; + updateCSP(csp); } - const RootModule = getModule(serverConfig.rootModuleName); - const { [CONFIGURATION_KEY]: { csp } = {} } = RootModule; - updateCSP(csp); - - return loadedModules; + return { loadedModules, rejectedModules }; }; export default loadModules; diff --git a/src/server/utils/pollModuleMap.js b/src/server/utils/pollModuleMap.js index 1d9137094..db8f65cf8 100644 --- a/src/server/utils/pollModuleMap.js +++ b/src/server/utils/pollModuleMap.js @@ -23,11 +23,6 @@ import { holocron as holocronMetrics, } from '../metrics'; -import { - getModulesUsingExternals, - setModulesUsingExternals, -} from './onModuleLoad'; -import { backupModuleStateConfig, restoreModuleStateConfig } from './stateConfig'; let moduleMapHealthy = null; export const getModuleMapHealth = () => moduleMapHealthy; @@ -133,26 +128,36 @@ async function pollModuleMap() { // triggers additional poll during current poll. recordPollingForMonitor(); startPollingMonitorIfNotAlready(); - let modulesUsingExternalsBeforeUpdate; - let configBeforeUpdate; - try { - configBeforeUpdate = backupModuleStateConfig(); - modulesUsingExternalsBeforeUpdate = getModulesUsingExternals(); console.log('pollModuleMap: polling...'); incrementCounter(holocronMetrics.moduleMapPoll); - const modulesLoaded = await loadModules(); + const { loadedModules = {}, rejectedModules = {} } = await loadModules(); + + const numberOfModulesLoaded = Object.keys(loadedModules).length; + const numberOfModulesRejected = Object.keys(rejectedModules).length; + + moduleMapHealthy = !numberOfModulesRejected; - moduleMapHealthy = true; - resetGauge(holocronMetrics.moduleMapPollConsecutiveErrors); - const numberOfModulesLoaded = Object.keys(modulesLoaded).length; if (numberOfModulesLoaded) { console.log( `pollModuleMap: ${numberOfModulesLoaded} modules loaded/updated:`, - modulesLoaded + loadedModules ); incrementCounter(holocronMetrics.moduleMapUpdated); + } + + if (numberOfModulesRejected) { + const rejectedModuleMessages = Object.entries(rejectedModules).map(([moduleName, { reasonForRejection }]) => `${moduleName}: ${reasonForRejection}`); + console.warn( + `pollModuleMap: ${numberOfModulesRejected} modules rejected:`, rejectedModuleMessages + ); + incrementGauge(holocronMetrics.moduleMapPollConsecutiveErrors); + } else { + resetGauge(holocronMetrics.moduleMapPollConsecutiveErrors); + } + + if (numberOfModulesLoaded || numberOfModulesRejected) { resetPollTime(); } else { incrementPollTime(); @@ -162,10 +167,6 @@ async function pollModuleMap() { } } catch (pollingError) { try { - restoreModuleStateConfig(configBeforeUpdate); - if (modulesUsingExternalsBeforeUpdate) { - setModulesUsingExternals(modulesUsingExternalsBeforeUpdate); - } resetPollTime(); moduleMapHealthy = false; incrementGauge(holocronMetrics.moduleMapPollConsecutiveErrors);