fix(plugins): correct JSDoc for callHandlerWithTimeout timeout behavior
This commit is contained in:
parent
f7681e6fe9
commit
20615bbb13
@ -5,6 +5,7 @@
|
||||
* error handling, priority ordering, and async support.
|
||||
*/
|
||||
|
||||
import { withTimeout } from "../node-host/with-timeout.js";
|
||||
import { concatOptionalTextSegments } from "../shared/text/join-segments.js";
|
||||
import type { PluginRegistry } from "./registry.js";
|
||||
import type {
|
||||
@ -108,10 +109,15 @@ export type HookRunnerLogger = {
|
||||
error: (message: string) => void;
|
||||
};
|
||||
|
||||
/** Default timeout for async plugin hook handlers (ms). */
|
||||
const DEFAULT_HOOK_TIMEOUT_MS = 10_000;
|
||||
|
||||
export type HookRunnerOptions = {
|
||||
logger?: HookRunnerLogger;
|
||||
/** If true, errors in hooks will be caught and logged instead of thrown */
|
||||
catchErrors?: boolean;
|
||||
/** Per-handler timeout for async hooks (ms). Defaults to 10 000. Set 0 to disable. */
|
||||
hookTimeoutMs?: number;
|
||||
};
|
||||
|
||||
export type PluginTargetedInboundClaimOutcome =
|
||||
@ -159,6 +165,27 @@ function getHooksForNameAndPlugin<K extends PluginHookName>(
|
||||
export function createHookRunner(registry: PluginRegistry, options: HookRunnerOptions = {}) {
|
||||
const logger = options.logger;
|
||||
const catchErrors = options.catchErrors ?? true;
|
||||
const hookTimeoutMs =
|
||||
typeof options.hookTimeoutMs === "number" && options.hookTimeoutMs > 0
|
||||
? options.hookTimeoutMs
|
||||
: options.hookTimeoutMs === 0
|
||||
? undefined // explicitly disabled
|
||||
: DEFAULT_HOOK_TIMEOUT_MS;
|
||||
|
||||
/**
|
||||
* Execute a single async handler with the configured timeout.
|
||||
* Throws on timeout; callers catch via handleHookError.
|
||||
*/
|
||||
async function callHandlerWithTimeout<T>(
|
||||
fn: () => Promise<T>,
|
||||
hookName: PluginHookName,
|
||||
pluginId: string,
|
||||
): Promise<T> {
|
||||
if (!hookTimeoutMs) {
|
||||
return fn();
|
||||
}
|
||||
return withTimeout(() => fn(), hookTimeoutMs, `${hookName} handler from ${pluginId}`);
|
||||
}
|
||||
|
||||
const mergeBeforeModelResolve = (
|
||||
acc: PluginHookBeforeModelResolveResult | undefined,
|
||||
@ -253,7 +280,11 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
|
||||
const promises = hooks.map(async (hook) => {
|
||||
try {
|
||||
await (hook.handler as (event: unknown, ctx: unknown) => Promise<void>)(event, ctx);
|
||||
await callHandlerWithTimeout(
|
||||
() => (hook.handler as (event: unknown, ctx: unknown) => Promise<void>)(event, ctx),
|
||||
hookName,
|
||||
hook.pluginId,
|
||||
);
|
||||
} catch (err) {
|
||||
handleHookError({ hookName, pluginId: hook.pluginId, error: err });
|
||||
}
|
||||
@ -283,9 +314,11 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
|
||||
for (const hook of hooks) {
|
||||
try {
|
||||
const handlerResult = await (
|
||||
hook.handler as (event: unknown, ctx: unknown) => Promise<TResult>
|
||||
)(event, ctx);
|
||||
const handlerResult = await callHandlerWithTimeout(
|
||||
() => (hook.handler as (event: unknown, ctx: unknown) => Promise<TResult>)(event, ctx),
|
||||
hookName,
|
||||
hook.pluginId,
|
||||
);
|
||||
|
||||
if (handlerResult !== undefined && handlerResult !== null) {
|
||||
if (mergeResults && result !== undefined) {
|
||||
@ -317,7 +350,23 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
|
||||
logger?.debug?.(`[hooks] running ${hookName} (${hooks.length} handlers, first-claim wins)`);
|
||||
|
||||
return await runClaimingHooksList(hooks, hookName, event, ctx);
|
||||
for (const hook of hooks) {
|
||||
try {
|
||||
const handlerResult = await callHandlerWithTimeout(
|
||||
() =>
|
||||
(hook.handler as (event: unknown, ctx: unknown) => Promise<TResult | void>)(event, ctx),
|
||||
hookName,
|
||||
hook.pluginId,
|
||||
);
|
||||
if (handlerResult?.handled) {
|
||||
return handlerResult;
|
||||
}
|
||||
} catch (err) {
|
||||
handleHookError({ hookName, pluginId: hook.pluginId, error: err });
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function runClaimingHookForPlugin<
|
||||
@ -338,23 +387,14 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
`[hooks] running ${hookName} for ${pluginId} (${hooks.length} handlers, targeted)`,
|
||||
);
|
||||
|
||||
return await runClaimingHooksList(hooks, hookName, event, ctx);
|
||||
}
|
||||
|
||||
async function runClaimingHooksList<
|
||||
K extends PluginHookName,
|
||||
TResult extends { handled: boolean },
|
||||
>(
|
||||
hooks: Array<PluginHookRegistration<K> & { pluginId: string }>,
|
||||
hookName: K,
|
||||
event: Parameters<NonNullable<PluginHookRegistration<K>["handler"]>>[0],
|
||||
ctx: Parameters<NonNullable<PluginHookRegistration<K>["handler"]>>[1],
|
||||
): Promise<TResult | undefined> {
|
||||
for (const hook of hooks) {
|
||||
try {
|
||||
const handlerResult = await (
|
||||
hook.handler as (event: unknown, ctx: unknown) => Promise<TResult | void>
|
||||
)(event, ctx);
|
||||
const handlerResult = await callHandlerWithTimeout(
|
||||
() =>
|
||||
(hook.handler as (event: unknown, ctx: unknown) => Promise<TResult | void>)(event, ctx),
|
||||
hookName,
|
||||
hook.pluginId,
|
||||
);
|
||||
if (handlerResult?.handled) {
|
||||
return handlerResult;
|
||||
}
|
||||
@ -400,9 +440,12 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
let firstError: string | null = null;
|
||||
for (const hook of hooks) {
|
||||
try {
|
||||
const handlerResult = await (
|
||||
hook.handler as (event: unknown, ctx: unknown) => Promise<TResult | void>
|
||||
)(event, ctx);
|
||||
const handlerResult = await callHandlerWithTimeout(
|
||||
() =>
|
||||
(hook.handler as (event: unknown, ctx: unknown) => Promise<TResult | void>)(event, ctx),
|
||||
hookName,
|
||||
hook.pluginId,
|
||||
);
|
||||
if (handlerResult?.handled) {
|
||||
return { status: "handled", result: handlerResult };
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user