move more "common" environment logic to prepEnv

This commit is contained in:
Hazelnoot 2025-11-11 23:59:33 -05:00
parent ece03bef61
commit 29c069e39a
3 changed files with 286 additions and 34 deletions

View file

@ -8,8 +8,6 @@
*/
import cluster from 'node:cluster';
import { EventEmitter } from 'node:events';
import { inspect } from 'node:util';
import chalk from 'chalk';
import Xev from 'xev';
import { coreLogger, coreEnvService, coreLoggerService } from '@/boot/coreLogger.js';
@ -18,8 +16,6 @@ import { masterMain } from './master.js';
import { workerMain } from './worker.js';
import { readyRef } from './ready.js';
import 'reflect-metadata';
process.title = `Misskey (${cluster.isPrimary ? 'master' : 'worker'})`;
prepEnv();
@ -53,36 +49,6 @@ async function main() {
cluster.fork();
});
// Display detail of unhandled promise rejection
if (!envOption.quiet) {
process.on('unhandledRejection', e => {
logger.error('Unhandled rejection:', inspect(e));
});
}
process.on('uncaughtException', (err) => {
// Workaround for https://github.com/node-fetch/node-fetch/issues/954
if (String(err).match(/^TypeError: .+ is an? url with embedded credentials.$/)) {
logger.debug('Suppressed node-fetch issue#954, but the current job may fail.');
return;
}
// Workaround for https://github.com/node-fetch/node-fetch/issues/1845
if (String(err) === 'TypeError: Cannot read properties of undefined (reading \'body\')') {
logger.debug('Suppressed node-fetch issue#1845, but the current job may fail.');
return;
}
// Throw all other errors to avoid inconsistent state.
// (per NodeJS docs, it's unsafe to suppress arbitrary errors in an uncaughtException handler.)
throw err;
});
// Display detail of uncaught exception
process.on('uncaughtExceptionMonitor', (err, origin) => {
logger.error(`Uncaught exception (${origin}):`, err);
});
// Dying away...
process.on('disconnect', () => {
logger.warn('IPC channel disconnected! The process may soon die.');

View file

@ -4,6 +4,14 @@
*/
import { EventEmitter } from 'node:events';
import { inspect } from 'node:util';
import { coreLogger, coreEnvService } from '@/boot/coreLogger.js';
import { renderInlineError } from '@/misc/render-inline-error.js';
// import { patchPromiseTypes } from '@/misc/patch-promise-types.js';
// Polyfill reflection metadata *without* loading dependencies that may corrupt native types.
// https://github.com/microsoft/reflect-metadata?tab=readme-ov-file#es-modules-in-nodejsbrowser-typescriptbabel-bundlers
import 'reflect-metadata/lite';
/**
* Configures Node.JS global runtime options for values appropriate for Sharkey.
@ -16,4 +24,41 @@ export function prepEnv() {
// Avoid warnings like "11 message listeners added to [Commander]. MaxListeners is 10."
// This is expected due to use of NestJS lifecycle hooks.
EventEmitter.defaultMaxListeners = 128;
// // In non-production environments, patch the Promise type to report unsafe usage.
// // This can identify subtle bugs at the expense of reduced JIT performance.
// const isProduction = coreEnvService.env.NODE_ENV === 'production';
// if (!isProduction) {
// patchPromiseTypes();
// }
// Workaround certain 3rd-party bugs
process.on('uncaughtException', (err) => {
// Workaround for https://github.com/node-fetch/node-fetch/issues/954
if (String(err).match(/^TypeError: .+ is an? url with embedded credentials.$/)) {
coreLogger.debug('Suppressed node-fetch issue#954, but the current job may fail.');
return;
}
// Workaround for https://github.com/node-fetch/node-fetch/issues/1845
if (String(err) === 'TypeError: Cannot read properties of undefined (reading \'body\')') {
coreLogger.debug('Suppressed node-fetch issue#1845, but the current job may fail.');
return;
}
// Throw all other errors to avoid inconsistent state.
// (per NodeJS docs, it's unsafe to suppress arbitrary errors in an uncaughtException handler.)
coreLogger.error(`Uncaught exception: ${renderInlineError(err)}`, {
error: inspect(err),
});
throw err;
});
// Log uncaught promise rejections
process.on('unhandledRejection', (error, promise) => {
coreLogger.error(`Unhandled rejection: ${renderInlineError(error)}`, {
error: inspect(error),
promise: inspect(promise),
});
});
}

View file

@ -0,0 +1,241 @@
// /*
// * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
// * SPDX-License-Identifier: AGPL-3.0-only
// */
//
// import { coreLogger, coreEnvService } from '@/boot/coreLogger.js';
// import { isError } from '@/misc/is-error.js';
// import { promiseTry } from '@/misc/promise-try.js';
// import type { EnvService } from '@/global/EnvService.js';
// import type Logger from '@/logger.js';
//
// // Make sure we only run it once!
// let haveRunPatch = false;
//
// // Back up the original unpatched implementations
// const nativeThen = Promise.prototype.then;
// const nativeCatch = Promise.prototype.catch;
// const nativeFinally = Promise.prototype.finally;
// const nativeReject = Promise.reject;
// const nativeTry = promiseTry; // native or polyfill
// const nativeWithResolvers = Promise.withResolvers;
//
// const isPatchedSymbol = Symbol('isPatched');
//
// function makePatched<T extends object>(target: T): T {
// setPatched(target, true);
// return target;
// }
//
// function setPatched(target: object, isPatched: boolean) {
// Reflect.set(target, isPatchedSymbol, isPatched);
// }
//
// function isPatched(target: object) {
// return Reflect.get(target, isPatchedSymbol) === true;
// }
//
// /**
// * Patches the global Promise class and static methods to detect improper use.
// */
// export function patchPromiseTypes(services?: { logger?: Logger, envService?: EnvService }) {
// const envService = services?.envService ?? coreEnvService;
// const logger = services?.logger ?? coreLogger;
//
// if (haveRunPatch) {
// logger.debug('Skipping patchPromiseTypes - already patched.');
// return;
// }
// haveRunPatch = true;
//
//
//
// logger.info('Promise debugging is enabled; the global Promise type will be patched with additional verification routines.');
//
//
// }
//
// export function installPromisePatches(logger: Logger) {
// // Defined here for access to services
// function check(
// error: unknown,
// promise: Promise<unknown> | null,
// continuation: {
// resolve?: ((result: unknown) => unknown) | null,
// reject?: ((error: unknown) => unknown) | null,
// },
// ) {
// // instanceof checks are not reliable under jest!
// // https://github.com/jestjs/jest/issues/2549#issuecomment-2800060383
// if (!isError(error)) {
// const stack = new Error().stack;
// const type = error && typeof(error) === 'object' && 'name' in error && typeof(error.name) === 'string'
// ? `object[${error.name}]`
// : typeof(error);
//
// logger.error(`Detected improper use of Promise: rejected with non-Error type ${type}`, { promise, error, stack, continuation });
// }
//
// if (String(error) === '#<Event>') {
// const stack = new Error().stack;
// logger.error('FOUND THE FUCKER:', { promise, error, stack, continuation });
// }
// }
//
// function patchCallback<TInput, TOutput, TCallback extends ((input: TInput) => Promise<TOutput>) | null | undefined>(
// callback: TCallback,
// checks: {
// input?: 'thrown' | 'returned' | 'both',
// output?: 'thrown' | 'returned' | 'both',
// },
// meta: {
// promise?: Promise<unknown>,
// continuation?: {
// resolve?: ((result: unknown) => unknown) | null,
// reject?: ((error: unknown) => unknown) | null,
// },
// },
// ): TCallback {
// if (callback == null) {
// return callback;
// }
//
// if (isPatched(callback)) {
// return callback;
// }
//
// async function checkSomething<T = unknown>(thing: (() => T | Promise<T>), mode: undefined | 'returned' | 'thrown' | 'both'): Promise<T> {
// try {
// const returnedThing = await thing();
// if (mode === 'returned' || mode === 'both') {
// check(returnedThing, meta);
// }
// return returnedThing;
// } catch (thrownThing) {
// if (mode === 'thrown' || mode === 'both') {
// check(thrownThing, meta);
// }
// throw thrownThing;
// }
// }
//
// return makePatched(async (input: TInput): Promise<TOutput> => {
// // Check input asynchronously
// const returnedInput = await checkSomething(() => input, checks.input);
// return await checkSomething(() => callback(returnedInput), checks.output);
// }) as TCallback;
// }
//
// // function patchProducer<TOutput, TProducer extends (() => Promise<TOutput>) | null | undefined>(
// // producer: TProducer,
// // promise: Promise<unknown> | null,
// // continuation: {
// // resolve?: ((result: unknown) => unknown) | null,
// // reject?: ((error: unknown) => unknown) | null,
// // },
// // ): TProducer {
// // if (producer == null) {
// // return producer;
// // }
// //
// // if (isPatched(producer)) {
// // return producer;
// // }
// //
// // return makePatched(() => {
// // // Check the output
// // const result = nativeTry(producer);
// // result
// // .catch(resolvedError => {
// // check(resolvedError, promise, continuation);
// // });
// // return result;
// // }) as TProducer;
// // }
//
// // Defined here for access to services and check()
// class PatchedPromise<T> extends Promise<T> {
// constructor(executor: (resolve: (value: (PromiseLike<T> | T)) => void, reject: (reason?: unknown) => void) => void) {
// super((resolve, reject) => {
// reject = patchCallback(reject, { input: 'both' }, { promise: this });
// executor(resolve, reject);
// });
// setPatched(this, true);
// }
// }
// setPatched(PatchedPromise, true);
//
// logger.debug('Patching Promise.then prototype method...');
// Promise.prototype.then = makePatched(function<T, TResult1 = T, TResult2 = never>(this: Promise<T>, resolve?: ((value: T) => (PromiseLike<TResult1> | TResult1)) | undefined | null, reject?: ((reason: unknown) => (PromiseLike<TResult2> | TResult2)) | undefined | null): Promise<TResult1 | TResult2> {
// reject = patchCombined(reject, this, { resolve, reject });
// return nativeThen.call(this, resolve, reject) as Promise<TResult1 | TResult2>;
// });
//
// logger.debug('Patching Promise.catch prototype method...');
// Promise.prototype.catch = makePatched(function<T, TResult = never>(this: Promise<T>, reject?: ((reason: unknown) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult> {
// reject = patchCombined(reject, this, { reject });
// return nativeCatch.call(this, reject) as Promise<T | TResult>;
// });
//
// logger.debug('Patching Promise.finally prototype method...');
// Promise.prototype.finally = makePatched(function<T>(this: Promise<T>, _finally?: (() => Promise<void> | void) | undefined | null): Promise<T> {
// _finally = patchProducer(_finally, this, { resolve: _finally, reject: _finally });
// return nativeFinally.call(this, _finally) as Promise<T>;
// });
//
// logger.debug('Patching Promise.reject static method...');
// Promise.reject = patchCombined(nativeReject, null, {});
//
// logger.debug('Patching Promise.try static method...');
// Promise.try = makePatched(async <T, U extends unknown[]>(callbackFn: (...args: U) => T | PromiseLike<T>, ...args: U): Promise<Awaited<T>> => {
// const promise = nativeTry(callbackFn, ...args);
// try {
// return await promise;
// } catch (err) {
// check(err, promise, {}, 'source');
// throw err;
// }
// });
//
// logger.debug('Patching Promise.withResolvers static method...');
// Promise.withResolvers = makePatched(<T>(): PromiseWithResolvers<T> => {
// // let resolve: ((value: T | PromiseLike<T>) => void) | undefined = undefined;
// // let reject: ((reason?: unknown) => void) | undefined = undefined;
// // const promise = new PatchedPromise((resolver, rejecter) => {
// // resolve = resolver;
// // reject = rejecter;
// // });
// //
// // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
// // return { resolve: resolve!, reject: reject!, promise } as PromiseWithResolvers<T>;
// const res = nativeWithResolvers<T>();
// if (isPatched(res.reject)) {
// return res;
// }
//
// const { promise, resolve, reject } = res;
// const patchedReject = async (err: unknown) => {
// check(await err, promise, {}, 'callback');
// reject(err);
// };
// return { promise, resolve, reject: patchedReject };
// });
//
// logger.debug('Patching Promise constructor...');
// // Copy all new static methods from Promise to PatchedPromise
// for (const prop of Reflect.ownKeys(Promise)) {
// const hasExisting = Reflect.getOwnPropertyDescriptor(PatchedPromise, prop) != null;
// if (hasExisting) continue;
//
// const value = Reflect.get(Promise, prop);
// if (typeof(value) !== 'function') continue;
//
// const descriptor = Reflect.getOwnPropertyDescriptor(Promise, prop);
// if (!descriptor) continue;
//
// Object.defineProperty(PatchedPromise, prop, descriptor);
// }
// // Replace Promise with PatchedPromise
// global.Promise = PatchedPromise;
// globalThis.Promise = PatchedPromise;
// }