-
-
Notifications
You must be signed in to change notification settings - Fork 300
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
refactor: reusable thread pool to decrypt keystores #5623
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export {DecryptKeystoresThreadPool} from "./threadPool.js"; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Setting |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import {spawn, Pool, Worker, ModuleThread, QueuedTask} from "@chainsafe/threads"; | ||
import {DecryptKeystoreArgs, DecryptKeystoreWorkerAPI} from "./types.js"; | ||
import {maxPoolSize} from "./poolSize.js"; | ||
|
||
/** | ||
* Thread pool to decrypt keystores | ||
*/ | ||
export class DecryptKeystoresThreadPool { | ||
private pool: Pool<ModuleThread<DecryptKeystoreWorkerAPI>>; | ||
private tasks: QueuedTask<ModuleThread<DecryptKeystoreWorkerAPI>, Uint8Array>[] = []; | ||
private terminatePoolHandler: () => void; | ||
|
||
constructor(keystoreCount: number, private readonly signal: AbortSignal) { | ||
this.pool = Pool( | ||
() => | ||
spawn<DecryptKeystoreWorkerAPI>(new Worker("./worker.js"), { | ||
// The number below is big enough to almost disable the timeout | ||
// which helps during tests run on unpredictably slow hosts | ||
timeout: 5 * 60 * 1000, | ||
}), | ||
{ | ||
// Adjust worker pool size based on keystore count | ||
size: Math.min(keystoreCount, maxPoolSize), | ||
// Decrypt keystores in sequence, increasing concurrency does not improve performance | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did several test runs with different concurrency but it seems to just overload the threads and throttle the CPU |
||
concurrency: 1, | ||
} | ||
); | ||
// Terminate worker threads when process receives exit signal | ||
this.terminatePoolHandler = () => { | ||
void this.pool.terminate(true); | ||
}; | ||
signal.addEventListener("abort", this.terminatePoolHandler); | ||
} | ||
|
||
/** | ||
* Add keystore to the task queue to be decrypted | ||
*/ | ||
queue( | ||
args: DecryptKeystoreArgs, | ||
onDecrypted: (secretKeyBytes: Uint8Array) => void, | ||
onError: (e: Error) => void | ||
): void { | ||
const task = this.pool.queue((thread) => thread.decryptKeystore(args)); | ||
this.tasks.push(task); | ||
task.then(onDecrypted).catch(onError); | ||
} | ||
|
||
/** | ||
* Resolves once all queued tasks are completed and terminates worker threads. | ||
* Errors during executing can be captured in `onError` handler for each task. | ||
*/ | ||
async completed(): Promise<void> { | ||
await this.pool.settled(true); | ||
await this.pool.terminate(); | ||
this.signal.removeEventListener("abort", this.terminatePoolHandler); | ||
} | ||
|
||
/** | ||
* Cancel all pending tasks | ||
*/ | ||
cancel(): void { | ||
for (const task of this.tasks) { | ||
task.cancel(); | ||
} | ||
this.tasks = []; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import {KeystoreStr} from "@lodestar/api/keymanager"; | ||
import {LocalKeystoreDefinition} from "../interface.js"; | ||
|
||
export type DecryptKeystoreWorkerAPI = { | ||
decryptKeystore(args: DecryptKeystoreArgs): Promise<Uint8Array>; | ||
}; | ||
|
||
export type DecryptKeystoreArgs = LocalKeystoreDefinition | {keystoreStr: KeystoreStr; password: string}; | ||
|
||
export function isLocalKeystoreDefinition(args: DecryptKeystoreArgs): args is LocalKeystoreDefinition { | ||
return (args as LocalKeystoreDefinition).keystorePath !== undefined; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,9 +37,6 @@ export async function readPassphraseOrPrompt(args: {importKeystoresPassword?: st | |
}, | ||
]); | ||
|
||
// eslint-disable-next-line no-console | ||
console.log("Password is correct"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This console.log is incorrect and not helpful as password verification is done later in the process |
||
|
||
return answers.password; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Abort has to be pushed to graceful shutdown callbacks before invoking
getSignersFromArgs
.