Skip to content

Commit 38db91f

Browse files
committed
ref(builds-cacher): cache-only Node server, no fetches or writes
Make _webi/builds-cacher.js and _webi/transform-releases.js read exclusively from ~/.cache/webi/legacy/<name>.json and remove every code path that fetched from upstream or wrote to disk. The Go cache daemon (webicached) is now the sole writer; the Node server is a thin reader. builds-cacher.js: - Resolve cache files via Os.homedir() + '/.cache/webi/legacy/' instead of the cacheDir argument. Drop the 'caches' constructor parameter. - Remove getLatestBuilds / getLatestBuildsInner — they require()d per-package releases.js modules, fetched upstream, and wrote <yyyy-mm>/<name>.json + .updated.txt to disk. - Remove the process.nextTick stale-refresh hook in _doGetPackages. Cold reads return what's on disk; if the file is missing, return empty meta instead of fetching. - Remove freshenRandomPackage and its supporting state (bc._staleNames, bc._freshenTimeout, bc._staleAge). The hourly background freshener competed with webicached for the same files. - In getProjectTypeByEntry, decide selfhosted vs valid by probing for the cache file rather than require()-ing releases.js. Drop the not_found / 'PROBLEM/SOLUTION/npm clean-install' diagnostic in getProjectsByType — the cache-file probe replaces the module-load failure mode. transform-releases.js: - Remove Releases.get and the _normalize import. Replace getCachedReleases's fetch+race+stale-age machinery with a single Fs.readFile of ~/.cache/webi/legacy/<pkg>.json. - Drop the in-process version re-sort in createFormatsSorter; the cache file is already version-sorted by webicached, so the sorter only re-orders within the same version. No callers' public signatures change. Every other file is untouched — the per-package releases.js modules, _common/*.js fetchers, and _webi/normalize.js still exist on disk but are no longer reachable from the request path.
1 parent da10371 commit 38db91f

3 files changed

Lines changed: 57 additions & 293 deletions

File tree

_webi/builds-cacher.js

Lines changed: 21 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
var BuildsCacher = module.exports;
44

55
let Fs = require('node:fs/promises');
6+
let Os = require('node:os');
67
let Path = require('node:path');
78

9+
let LEGACY_CACHE_DIR = Path.join(Os.homedir(), '.cache/webi/legacy');
10+
811
let HostTargets = require('./build-classifier/host-targets.js');
912
let Lexver = require('./build-classifier/lexver.js');
1013
let Triplet = require('./build-classifier/triplet.js');
1114

12-
var ALIAS_RE = /^alias: (\w+)$/m;
15+
var ALIAS_RE = /^alias: ([\w.-]+)$/m;
1316

1417
var LEGACY_ARCH_MAP = {
1518
'*': 'ANYARCH',
@@ -126,61 +129,8 @@ async function readFirstBytes(path) {
126129
return str;
127130
}
128131

129-
let promises = {};
130-
async function getLatestBuilds(Releases, installersDir, cacheDir, name, date) {
131-
console.info(`[INFO] getLatestBuilds: ${name}`);
132-
133-
if (!Releases) {
134-
Releases = require(`${installersDir}/${name}/releases.js`);
135-
}
136-
// TODO update all releases files with module.exports.xxxx = 'foo';
137-
if (!Releases.latest) {
138-
Releases.latest = Releases;
139-
}
140-
141-
let id = `${cacheDir}/${name}`;
142-
if (!promises[id]) {
143-
promises[id] = Promise.resolve();
144-
}
145-
146-
promises[id] = promises[id].then(async function () {
147-
return await getLatestBuildsInner(Releases, cacheDir, name, date);
148-
});
149-
150-
return await promises[id];
151-
}
152-
153-
async function getLatestBuildsInner(Releases, cacheDir, name, date) {
154-
let data = await Releases.latest();
155-
156-
if (!date) {
157-
date = new Date();
158-
}
159-
let isoDate = date.toISOString();
160-
let yearMonth = isoDate.slice(0, 7);
161-
162-
// TODO hash file
163-
let dataFile = `${cacheDir}/${yearMonth}/${name}.json`;
164-
// TODO fsstat releases.js vs require-ing time as well
165-
let tsFile = `${cacheDir}/${yearMonth}/${name}.updated.txt`;
166-
167-
let dirPath = Path.dirname(dataFile);
168-
await Fs.mkdir(dirPath, { recursive: true });
169-
170-
let json = JSON.stringify(data, null, 2);
171-
await Fs.writeFile(dataFile, json, 'utf8');
172-
173-
let seconds = date.valueOf();
174-
let ms = seconds / 1000;
175-
let msStr = ms.toFixed(3);
176-
await Fs.writeFile(tsFile, msStr, 'utf8');
177-
178-
return data;
179-
}
180-
181-
BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
132+
BuildsCacher.create = function ({ ALL_TERMS, installers }) {
182133
let installersDir = installers;
183-
let cacheDir = caches;
184134

185135
if (!ALL_TERMS) {
186136
ALL_TERMS = Triplet.TERMS_PRIMARY_MAP;
@@ -195,7 +145,6 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
195145
bc._triplets = {};
196146
bc._targetsByBuildIdCache = {};
197147
bc._caches = {};
198-
bc._staleAge = 15 * 60 * 1000;
199148
bc._allFormats = {};
200149
bc._allTriplets = {};
201150
// Per-name lock: serializes cold-cache getPackages so concurrent
@@ -219,19 +168,6 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
219168
let entries = await Fs.readdir(installersDir, { withFileTypes: true });
220169
for (let entry of entries) {
221170
let meta = await bc.getProjectTypeByEntry(entry);
222-
if (meta.type === 'not_found') {
223-
let err = meta.detail;
224-
console.error('');
225-
console.error('PROBLEM');
226-
console.error(` ${err.message}`);
227-
console.error('');
228-
console.error('SOLUTION');
229-
console.error(' npm clean-install');
230-
console.error('');
231-
throw new Error(
232-
'[SANITY FAIL] should never have missing modules in prod',
233-
);
234-
}
235171
dirs[meta.type][entry.name] = meta.detail;
236172
}
237173

@@ -300,19 +236,17 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
300236
return { type: 'alias', detail: link };
301237
}
302238

303-
let releasesPath = Path.join(path, 'releases.js');
304-
try {
305-
void require(releasesPath);
306-
} catch (err) {
307-
if (err.code !== 'MODULE_NOT_FOUND') {
308-
return { type: 'errors', detail: err };
309-
}
310-
311-
if (err.message.includes(`Cannot find module '${releasesPath}'`)) {
312-
return { type: 'selfhosted', detail: true };
313-
}
314-
315-
return { type: 'not_found', detail: err };
239+
let cacheFile = `${LEGACY_CACHE_DIR}/${entry.name}.json`;
240+
let hasCacheFile = await Fs.access(cacheFile).then(
241+
function () {
242+
return true;
243+
},
244+
function () {
245+
return false;
246+
},
247+
);
248+
if (!hasCacheFile) {
249+
return { type: 'selfhosted', detail: true };
316250
}
317251

318252
return { type: 'valid', detail: true };
@@ -337,14 +271,9 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
337271
return p;
338272
};
339273

340-
async function _doGetPackages({ Releases, name, date }) {
341-
if (!date) {
342-
date = new Date();
343-
}
344-
let isoDate = date.toISOString();
345-
let yearMonth = isoDate.slice(0, 7);
346-
let dataFile = `${cacheDir}/${yearMonth}/${name}.json`;
347-
let tsFile = `${cacheDir}/${yearMonth}/${name}.updated.txt`;
274+
async function _doGetPackages({ name }) {
275+
let dataFile = `${LEGACY_CACHE_DIR}/${name}.json`;
276+
let tsFile = `${LEGACY_CACHE_DIR}/${name}.updated.txt`;
348277

349278
let tsDate;
350279
{
@@ -398,7 +327,7 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
398327
}
399328
}
400329
if (!projInfo) {
401-
projInfo = await getLatestBuilds(Releases, installersDir, cacheDir, name);
330+
return meta;
402331
}
403332
let latestProjInfo = await BuildsCacher.transformAndUpdate(
404333
name,
@@ -409,64 +338,9 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
409338
);
410339
bc._caches[name] = latestProjInfo;
411340

412-
process.nextTick(async function () {
413-
let now = date.valueOf();
414-
let age = now - projInfo.updated;
415-
416-
let fresh = age < bc._staleAge;
417-
if (fresh) {
418-
return;
419-
}
420-
421-
projInfo = await getLatestBuilds(Releases, installersDir, cacheDir, name);
422-
let latestProjInfo = BuildsCacher.transformAndUpdate(
423-
name,
424-
projInfo,
425-
meta,
426-
date,
427-
bc,
428-
);
429-
bc._caches[name] = latestProjInfo;
430-
});
431-
432-
return projInfo;
341+
return latestProjInfo;
433342
}
434343

435-
// Makes sure that packages are updated once an hour, on average
436-
bc._staleNames = [];
437-
bc._freshenTimeout = null;
438-
bc.freshenRandomPackage = async function (minDelay) {
439-
if (!minDelay) {
440-
minDelay = 15 * 1000;
441-
}
442-
443-
if (bc._staleNames.length === 0) {
444-
let dirs = await bc.getProjectsByType();
445-
bc._staleNames = Object.keys(dirs.valid);
446-
bc._staleNames.sort(function () {
447-
return 0.5 - Math.random();
448-
});
449-
}
450-
451-
let name = bc._staleNames.pop();
452-
void (await bc.getPackages({
453-
//Releases: Releases,
454-
name: name,
455-
date: new Date(),
456-
}));
457-
console.info(`[INFO] freshenRandomPackage: ${name}`);
458-
459-
let hour = 60 * 60 * 1000;
460-
let delay = minDelay;
461-
let spread = hour / bc._staleNames.length;
462-
let seed = Math.random();
463-
delay += seed * spread;
464-
465-
clearTimeout(bc._freshenTimeout);
466-
bc._freshenTimeout = setTimeout(bc.freshenRandomPackage, delay);
467-
bc._freshenTimeout.unref();
468-
};
469-
470344
/**
471345
* Given a list of acceptable formats, get the sorted list of of formats.
472346
* Actually used (as per node _webi/lint-builds.js):

_webi/builds.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,8 @@ let bc = BuildsCacher.create({
1515
caches: CACHE_DIR,
1616
installers: INSTALLERS_DIR,
1717
});
18-
bc.freshenRandomPackage(600 * 1000);
1918

2019
Builds.init = async function () {
21-
bc.freshenRandomPackage(600 * 1000);
22-
2320
let dirs = await bc.getProjectsByType();
2421
let projNames = Object.keys(dirs.valid);
2522

0 commit comments

Comments
 (0)