mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-06 14:07:51 +00:00
* fix(kv): keep disableSharing entries out of other apps' events (PUT-1876) Private kv keys and values are no longer delivered to cross-app subscriptions, including forwarded and durable deliveries. * fix(fs): refuse usernames whose home path still holds another account's rows (PUT-1877) Username claims (signup, save_account, change-username, OIDC, team provisioning) now also check for leftover rows under /<name>/, and the root listing only returns the actor's own home. * fix(puter-js): reconnect the events channel after a server-side disconnect (PUT-1881) A server hang-up is retried with a bounded backoff; persistent handlers survive it and get an optional onError when the connection can't be restored. * fix(backend): let the graceful shutdown finish before telemetry exits (PUT-1948) The telemetry preload no longer exits on signals; the entry point drains, tears down top-down, flushes telemetry last, then exits. DB pools stay open until teardown. * fix(fs): reserve quota for signed uploads in progress (PUT-1880) startWrite/startBatchWrite hold each upload's declared size in a per-owner cache reservation until it completes, aborts or expires, so a burst of starts can't all read the same committed usage. Caps in-flight uploads at 10,000 per account. * fix(fs): refuse to complete a signed upload whose object never arrived (PUT-1951) Completion now 400s when the object store reports the key missing (transient errors stay lenient), abort no longer deletes an object a live entry still uses, and a ghost entry is only removed after a second miss at its recorded location. * fix(metering): move usage to v2 keys with a small totals item, per-model detail shards and a 3-month TTL (PUT-1949) The billed item holds only totals; per-model detail lives in 100 hash-sharded items read in one batch and cached for 60s, and more than 5,000 distinct usage types in a month fold into other. Global and per-app aggregates store totals only, appTotals comes from a prefix listing, a refused path only skips the key that refused it, exact reads near the allowance are throttled per key, and every metering record expires after 3 months. September usage restarts at deploy; the monthly-charge claim stays on the v1 key through September so recurring charges don't fire twice. * test(events): let the worker backoff test tolerate a loaded run * feat(events): scale KV event fan-out with the key owner's plan A KV change delivers to up to 512 matching subscriptions (2,048 filter evaluations) for a paid key owner and 128 (512) otherwise, inline values are dropped once more than 128 match, and paid accounts may hold up to 512 share handles per app. The owner's plan is only looked up when a change has more candidates than any cap. * test(metering): seed the 150-app listing test directly so it fits under coverage
68 lines
2.3 KiB
TypeScript
68 lines
2.3 KiB
TypeScript
/*
|
|
* Copyright (C) 2024-present Puter Technologies Inc.
|
|
*
|
|
* This file is part of Puter.
|
|
*
|
|
* Puter is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published
|
|
* by the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* Hand-off between the telemetry preload and the entry point that owns process
|
|
* exit: the preload registers how to stop the SDK, the owner calls
|
|
* `shutdownTelemetry()` as its last shutdown step. Imports nothing, so reaching
|
|
* it never loads the SDK.
|
|
*/
|
|
type TelemetryShutdown = () => Promise<void>;
|
|
|
|
let registered: TelemetryShutdown | null = null;
|
|
|
|
/** Returns a function that removes the registration. */
|
|
export const registerTelemetryShutdown = (
|
|
shutdown: TelemetryShutdown,
|
|
): (() => void) => {
|
|
registered = shutdown;
|
|
return () => {
|
|
if (registered === shutdown) registered = null;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Flush and stop telemetry if a preload registered it. Settles within
|
|
* `timeoutMs` even when the export endpoint is gone, and never rejects.
|
|
*/
|
|
export const shutdownTelemetry = async (timeoutMs: number): Promise<void> => {
|
|
const shutdown = registered;
|
|
registered = null;
|
|
if (!shutdown) return;
|
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
const timedOut = new Promise<'timeout'>((resolve) => {
|
|
timer = setTimeout(() => resolve('timeout'), timeoutMs);
|
|
});
|
|
try {
|
|
const outcome = await Promise.race([
|
|
shutdown().then(() => 'done' as const),
|
|
timedOut,
|
|
]);
|
|
if (outcome === 'timeout') {
|
|
console.warn(
|
|
`[telemetry] shutdown did not finish within ${timeoutMs}ms`,
|
|
);
|
|
}
|
|
} catch (err) {
|
|
console.error('[telemetry] shutdown error', err);
|
|
} finally {
|
|
clearTimeout(timer);
|
|
}
|
|
};
|