All checks were successful
build-image / build (push) Successful in 13m13s
The previous fix (yielding to the event loop between turf.union calls) was not enough: once the merged polygon gets complex with thousands of subscriptions, a single turf.union call can itself take seconds, and yielding between iterations doesn't help when one iteration alone blocks that long. Confirmed in staging: the site was still fully unresponsive (Cloudflare 524, curl hanging 2+ minutes, healthcheck failing) while a recompute ran. Validation/decoration (addNoisy/noNoisy, cheap) stays on the main thread; the actual circle+union chain now runs in a worker_thread (private/workers/unionWorker.js, plain CommonJS so meteor build copies it verbatim instead of compiling it) so the main event loop serving DDP/HTTP is never blocked by it, regardless of how slow any single turf call gets.
40 lines
1.6 KiB
JavaScript
40 lines
1.6 KiB
JavaScript
// Plain CommonJS worker_threads entry point, NOT part of the Meteor imports
|
|
// graph (lives under private/ so `meteor build` copies it verbatim into
|
|
// programs/server/assets/app/workers/unionWorker.js instead of compiling it).
|
|
//
|
|
// Runs the CPU-heavy part of the subscriptions geo-union (circle generation +
|
|
// sequential turf.union chain) off the main thread. A single tunion() call
|
|
// against an already-complex merged polygon can itself take seconds once
|
|
// there are thousands of subscriptions — yielding between iterations on the
|
|
// main thread doesn't help when one iteration alone blocks that long, so this
|
|
// needs a real worker thread, not just cooperative scheduling.
|
|
//
|
|
// workerData.subs must already be validated (location.lat/lon/distance
|
|
// present) and decorated (addNoisy/noNoisy already applied) by the caller.
|
|
const { parentPort, workerData } = require('worker_threads');
|
|
const tcircle = require('@turf/circle').default;
|
|
const tunion = require('@turf/union').default;
|
|
const ttrunc = require('@turf/truncate').default;
|
|
|
|
const truncOptions = { precision: 6, coordinates: 2 };
|
|
|
|
const run = () => {
|
|
const { subs } = workerData;
|
|
const unionGroup = subs.map(s => tcircle(
|
|
[s.location.lon, s.location.lat],
|
|
s.distance,
|
|
{ units: 'kilometers', steps: 144 }
|
|
));
|
|
|
|
let unionTemp = unionGroup.length > 0 ? ttrunc(unionGroup[0], truncOptions) : null;
|
|
for (let i = 1; i < unionGroup.length; i += 1) {
|
|
unionTemp = ttrunc(tunion(unionTemp, unionGroup[i]), truncOptions);
|
|
}
|
|
return unionTemp;
|
|
};
|
|
|
|
try {
|
|
parentPort.postMessage({ union: run() });
|
|
} catch (e) {
|
|
parentPort.postMessage({ error: e.message });
|
|
}
|