Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent MaxListenersExceededWarning observed in validator's abort controller #4512

Merged
merged 4 commits into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/beacon-node/test/utils/node/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {LevelDbController} from "@lodestar/db";
import {interopSecretKey} from "@lodestar/state-transition";
import {SlashingProtection, Validator, Signer, SignerType, ValidatorProposerConfig} from "@lodestar/validator";
import type {SecretKey} from "@chainsafe/bls/types";
import {ValidatorAbortController} from "@lodestar/validator";
import {BeaconNode} from "../../../src/index.js";
import {testLogger, TestLoggerOpts} from "../logger.js";

Expand All @@ -29,6 +30,10 @@ export async function getAndInitDevValidators({
}): Promise<{validators: Validator[]; secretKeys: SecretKey[]}> {
const validators: Promise<Validator>[] = [];
const secretKeys: SecretKey[] = [];
const abortControllers: ValidatorAbortController = {
genesisReqController: new AbortController(),
validatorOpsController: new AbortController(),
};

for (let clientIndex = 0; clientIndex < validatorClientCount; clientIndex++) {
const startIndexVc = startIndex + clientIndex * validatorsPerClient;
Expand Down Expand Up @@ -67,6 +72,7 @@ export async function getAndInitDevValidators({
logger,
// eslint-disable-next-line @typescript-eslint/no-empty-function
processShutdownCallback: () => {},
abortControllers,
signers,
doppelgangerProtectionEnabled,
valProposerConfig,
Expand Down
21 changes: 16 additions & 5 deletions packages/cli/src/cmds/validator/handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {setMaxListeners} from "node:events";
import {LevelDbController} from "@lodestar/db";
import {ProcessShutdownCallback, SlashingProtection, Validator, ValidatorProposerConfig} from "@lodestar/validator";
import {getMetrics, MetricsRegister} from "@lodestar/validator";
import {getMetrics, MetricsRegister, ValidatorAbortController} from "@lodestar/validator";
import {RegistryMetricCreator, collectNodeJSMetrics, HttpMetricsServer} from "@lodestar/beacon-node";
import {getBeaconConfigFromArgs} from "../../config/index.js";
import {IGlobalArgs} from "../../options/index.js";
Expand Down Expand Up @@ -66,9 +67,19 @@ export async function validatorHandler(args: IValidatorCliArgs & IGlobalArgs): P

logSigners(logger, signers);

// This AbortController interrupts the sleep() calls when waiting for genesis
const controller = new AbortController();
onGracefulShutdownCbs.push(async () => controller.abort());
const abortControllers: ValidatorAbortController = {
// This AbortController interrupts the sleep() calls when waiting for genesis
genesisReqController: new AbortController(),
// This AbortController interrupts the validators ops: clients call, clock etc
validatorOpsController: new AbortController(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you could only need 1 AbortController, can you try that?

};

// We set infinity for abort controller used for validator operations,
// to prevent MaxListenersExceededWarning which get logged when listeners > 10
// Since it is perfectly fine to have listeners > 10
setMaxListeners(Infinity, abortControllers.validatorOpsController.signal);

onGracefulShutdownCbs.push(async () => abortControllers.genesisReqController.abort());

const dbOps = {
config,
Expand Down Expand Up @@ -107,11 +118,11 @@ export async function validatorHandler(args: IValidatorCliArgs & IGlobalArgs): P
logger,
processShutdownCallback,
signers,
abortControllers,
doppelgangerProtectionEnabled,
afterBlockDelaySlotFraction: args.afterBlockDelaySlotFraction,
valProposerConfig,
},
controller.signal,
metrics
);

Expand Down
2 changes: 1 addition & 1 deletion packages/validator/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export {Validator, ValidatorOptions} from "./validator.js";
export {Validator, ValidatorOptions, ValidatorAbortController} from "./validator.js";
export {
ValidatorStore,
SignerType,
Expand Down
18 changes: 10 additions & 8 deletions packages/validator/src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ import {Metrics} from "./metrics.js";
import {MetaDataRepository} from "./repositories/metaDataRepository.js";
import {DoppelgangerService} from "./services/doppelgangerService.js";

export type ValidatorAbortController = {genesisReqController: AbortController; validatorOpsController: AbortController};

export type ValidatorOptions = {
slashingProtection: ISlashingProtection;
dbOps: IDatabaseApiOptions;
api: Api | string;
signers: Signer[];
logger: ILogger;
processShutdownCallback: ProcessShutdownCallback;
abortControllers: ValidatorAbortController;
afterBlockDelaySlotFraction?: number;
doppelgangerProtectionEnabled?: boolean;
closed?: boolean;
Expand Down Expand Up @@ -65,7 +68,7 @@ export class Validator {
constructor(opts: ValidatorOptions, readonly genesis: Genesis, metrics: Metrics | null = null) {
const {dbOps, logger, slashingProtection, signers, valProposerConfig} = opts;
const config = createIBeaconConfig(dbOps.config, genesis.genesisValidatorsRoot);
this.controller = new AbortController();
this.controller = opts.abortControllers.validatorOpsController;
const clock = new Clock(config, logger, {genesisTime: Number(genesis.genesisTime)});
const loggerVc = getLoggerVc(logger, clock);

Expand Down Expand Up @@ -157,21 +160,20 @@ export class Validator {
}

/** Waits for genesis and genesis time */
static async initializeFromBeaconNode(
opts: ValidatorOptions,
signal?: AbortSignal,
metrics?: Metrics | null
): Promise<Validator> {
static async initializeFromBeaconNode(opts: ValidatorOptions, metrics?: Metrics | null): Promise<Validator> {
const {config} = opts.dbOps;
const {logger} = opts;
const api =
typeof opts.api === "string"
? // This new api instance can make do with default timeout as a faster timeout is
// not necessary since this instance won't be used for validator duties
getClient({baseUrl: opts.api, getAbortSignal: () => signal}, {config, logger})
getClient(
{baseUrl: opts.api, getAbortSignal: () => opts.abortControllers.genesisReqController.signal},
{config, logger}
)
: opts.api;

const genesis = await waitForGenesis(api, opts.logger, signal);
const genesis = await waitForGenesis(api, opts.logger, opts.abortControllers.genesisReqController.signal);
logger.info("Genesis available");

const {data: externalSpecJson} = await api.config.getSpec();
Expand Down