Skip to content
Snippets Groups Projects
Commit b3692a84 authored by richard.petersen's avatar richard.petersen :sailboat:
Browse files

Fix: Multiple manifests are fetched at the same time

Root cause: Due to the timeout, every request that was launched while fetching the previous manifests could led to another fetch and another cache.warmup
Solution: Be more synchronous
parent 75de44ee
No related branches found
No related tags found
No related merge requests found
...@@ -26,7 +26,8 @@ describe('Manifest service', () => { ...@@ -26,7 +26,8 @@ describe('Manifest service', () => {
beforeEach(async () => { beforeEach(async () => {
mockserver = await createMockServer({ port }) mockserver = await createMockServer({ port })
mockserver.respondWith({ mockserver.respondWith({
'/api/manifest.json': generateSimpleViteManifest({ 'example.js': 'test' }) '/api/manifest.json': generateSimpleViteManifest({ 'example.js': 'test' }),
'/example.js': ''
}) })
}) })
...@@ -54,7 +55,8 @@ describe('Manifest service', () => { ...@@ -54,7 +55,8 @@ describe('Manifest service', () => {
mockserver.close() mockserver.close()
mockserver = await createMockServer({ port }) mockserver = await createMockServer({ port })
mockserver.respondWith({ mockserver.respondWith({
'/api/manifest.json': generateSimpleViteManifest({ 'example.js': 'other' }) '/api/manifest.json': generateSimpleViteManifest({ 'example.js': 'other' }),
'/example.js': ''
}) })
await new Promise(resolve => setTimeout(resolve, 150)) await new Promise(resolve => setTimeout(resolve, 150))
...@@ -75,7 +77,8 @@ describe('Manifest service', () => { ...@@ -75,7 +77,8 @@ describe('Manifest service', () => {
mockserver.close() mockserver.close()
mockserver = await createMockServer({ port }) mockserver = await createMockServer({ port })
mockserver.respondWith({ mockserver.respondWith({
'/api/manifest.json': generateSimpleViteManifest({ 'example.js': 'other' }) '/api/manifest.json': generateSimpleViteManifest({ 'example.js': 'other' }),
'/example.js': ''
}) })
// wait some time // wait some time
...@@ -97,8 +100,12 @@ describe('Manifest service', () => { ...@@ -97,8 +100,12 @@ describe('Manifest service', () => {
mockserver.close() mockserver.close()
mockserver = await createMockServer({ port }) mockserver = await createMockServer({ port })
mockserver.respondWith({ '/api/manifest.json': generateSimpleViteManifest({ 'example1.js': 'other' }) }) mockserver.respondWith({
mockserver.respondWith({ '/api/no2/manifest.json': generateSimpleViteManifest({ 'example2.js': 'thing' }) }) '/api/manifest.json': generateSimpleViteManifest({ 'example1.js': 'other' }),
'/api/no2/manifest.json': generateSimpleViteManifest({ 'example2.js': 'thing' }),
'/example1.js': '',
'/example2.js': ''
})
process.env.CACHE_TTL = 1 process.env.CACHE_TTL = 1
const app = createApp() const app = createApp()
......
...@@ -18,7 +18,7 @@ import promBundle from 'express-prom-bundle' ...@@ -18,7 +18,7 @@ import promBundle from 'express-prom-bundle'
import swaggerUi from 'swagger-ui-express' import swaggerUi from 'swagger-ui-express'
import yaml from 'js-yaml' import yaml from 'js-yaml'
import fs from 'fs' import fs from 'fs'
import { getCSSDependenciesFor, getDependencies, getOxManifests, getVersion } from './manifests.js' import { getCSSDependenciesFor, getDependencies, getOxManifests, getVersion, loadViteManifests, viteManifestToDeps } from './manifests.js'
import { fileCache } from './files.js' import { fileCache } from './files.js'
const ignorePaths = ['/ready', '/healthy'] const ignorePaths = ['/ready', '/healthy']
...@@ -37,7 +37,9 @@ export function createApp () { ...@@ -37,7 +37,9 @@ export function createApp () {
const healthCheck = new health.HealthChecker() const healthCheck = new health.HealthChecker()
const readinessCheck = new health.ReadinessCheck('getDependencies', async function () { const readinessCheck = new health.ReadinessCheck('getDependencies', async function () {
try { try {
await getDependencies() const viteManifests = await loadViteManifests()
const deps = viteManifestToDeps(viteManifests)
await fileCache.warmUp(viteManifests, deps)
} catch (e) { } catch (e) {
logger.error(`Failed to get dependencies: ${e.message}`) logger.error(`Failed to get dependencies: ${e.message}`)
throw e throw e
......
...@@ -6,43 +6,60 @@ import { config } from './config.js' ...@@ -6,43 +6,60 @@ import { config } from './config.js'
import { hash } from './util.js' import { hash } from './util.js'
export const loadViteManifests = (() => { export const loadViteManifests = (() => {
let cache let cachePromise
let lastCacheTime let lastCacheTime
let lastHash
return async function loadViteManifests ({ useCache = true } = {}) { async function reload () {
await config.load()
// vite manifests contains a set of objects with the vite-manifests
// from the corresponding registered services
const viteManifests = await Promise.all(config.urls.map(async url => {
const { origin } = new URL(url)
// fetch the manifests
const result = await fetch(url)
if (!result.ok) throw new Error(`Failed to load manifest for url ${result.url} (Status: ${result.status}: ${result.statusText})`)
try {
const manifest = await result.json()
for (const file in manifest) {
manifest[file].meta = manifest[file].meta || {}
manifest[file].meta.baseUrl = origin
}
return manifest
} catch (err) {
throw new Error(`Failed to load manifest for url ${result.url}: ${err}`)
}
}))
// combine all manifests by keys. With duplicates, last wins
return viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {})
}
return function loadViteManifests ({ useCache = true } = {}) {
const CACHE_TTL = parseInt(process.env.CACHE_TTL) const CACHE_TTL = parseInt(process.env.CACHE_TTL)
if (!cache || useCache === false || +new Date() > lastCacheTime + CACHE_TTL) { if (!cachePromise || useCache === false || +new Date() > lastCacheTime + CACHE_TTL) {
await config.load() cachePromise = reload()
// vite manifests contains a set of objects with the vite-manifests cachePromise.then(manifests => {
// from the corresponding registered services // update cache promise
const viteManifests = await Promise.all(config.urls.map(async url => { const newHash = hash(manifests)
const { origin } = new URL(url) if (newHash !== lastHash) {
// fetch the manifests if (lastHash) {
const result = await fetch(url) const deps = viteManifestToDeps(manifests)
if (!result.ok) throw new Error(`Failed to load manifest for url ${result.url} (Status: ${result.status}: ${result.statusText})`) // asynchronously rewarm the cache
try { fileCache.warmUp(manifests, deps)
const manifest = await result.json()
for (const file in manifest) {
manifest[file].meta = manifest[file].meta || {}
manifest[file].meta.baseUrl = origin
} }
return manifest lastHash = newHash
} catch (err) { Object.defineProperty(manifests, '__hash__', {
throw new Error(`Failed to load manifest for url ${result.url}: ${err}`) enumerable: false,
writable: true
})
manifests.__hash__ = newHash
} }
})) })
// combine all manifests by keys. With duplicates, last wins
const viteManifest = viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {})
// only update that object if it really changed to prevent any further parsing from being triggered
if (!cache || JSON.stringify(viteManifest) !== JSON.stringify(cache)) {
cache = viteManifest
}
lastCacheTime = +new Date() lastCacheTime = +new Date()
} }
return cache return cachePromise
} }
})() })()
...@@ -99,7 +116,6 @@ export const getDependencies = (() => { ...@@ -99,7 +116,6 @@ export const getDependencies = (() => {
const viteManifest = await loadViteManifests() const viteManifest = await loadViteManifests()
if (viteManifest !== prevViteManifest) { if (viteManifest !== prevViteManifest) {
depCache = viteManifestToDeps(viteManifest) depCache = viteManifestToDeps(viteManifest)
fileCache.warmUp(viteManifest, depCache)
prevViteManifest = viteManifest prevViteManifest = viteManifest
} }
return depCache return depCache
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment