diff --git a/docs/modules/ROOT/pages/hardhat-upgrades.adoc b/docs/modules/ROOT/pages/hardhat-upgrades.adoc index ede6c257c..cc92c71f1 100644 --- a/docs/modules/ROOT/pages/hardhat-upgrades.adoc +++ b/docs/modules/ROOT/pages/hardhat-upgrades.adoc @@ -158,4 +158,25 @@ describe("Box", function() { expect(value.toString()).to.equal('42'); }); }); ----- \ No newline at end of file +---- + +[[troubleshooting]] +== Troubleshooting + +[[error-elock]] +=== Error Code ELOCK +If used in an environment with lots of contracts being compiled, a timeout can occur related to lockfile usage for safe access to the validation file cache. + +See https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/537 for context. + +If this occurs, you can tune the following parameters to increase leniency on lockfile acquisition. +The following env vars configure the retry options, see https://github.com/tim-kos/node-retry#retrytimeoutsoptions and https://github.com/tim-kos/node-retry#retryoperationoptions for option details. + +[source, console] +---- +OPENZEPPPELIN_HARDHAT_UPGRADES_LOCK_RETRY_OPTIONS_FACTOR +OPENZEPPPELIN_HARDHAT_UPGRADES_LOCK_RETRY_OPTIONS_MAXRETRYTIME +OPENZEPPPELIN_HARDHAT_UPGRADES_LOCK_RETRY_OPTIONS_MAXTIMEOUT +OPENZEPPPELIN_HARDHAT_UPGRADES_LOCK_RETRY_OPTIONS_MINTIMEOUT +OPENZEPPPELIN_HARDHAT_UPGRADES_LOCK_RETRY_OPTIONS_RETRIES +---- diff --git a/packages/plugin-hardhat/README.md b/packages/plugin-hardhat/README.md index f1a8b07ae..7c60373cc 100644 --- a/packages/plugin-hardhat/README.md +++ b/packages/plugin-hardhat/README.md @@ -148,6 +148,23 @@ describe("Box", function() { }); }); ``` +## Troubleshooting + +### Error Code ELOCK +If used in an environment with lots of contracts being compiled, a timeout can occur related to lockfile usage for safe access to the validation file cache. + +See https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/537 for context. + +If this occurs, you can tune the following parameters to increase leniency on lockfile acquisition. +The following env vars configure the retry options, see https://github.com/tim-kos/node-retry#retrytimeoutsoptions and https://github.com/tim-kos/node-retry#retryoperationoptions for option details. + +```sh +OPENZEPPPELIN_HARDHAT_UPGRADES_LOCK_RETRY_OPTIONS_FACTOR +OPENZEPPPELIN_HARDHAT_UPGRADES_LOCK_RETRY_OPTIONS_MAXRETRYTIME +OPENZEPPPELIN_HARDHAT_UPGRADES_LOCK_RETRY_OPTIONS_MAXTIMEOUT +OPENZEPPPELIN_HARDHAT_UPGRADES_LOCK_RETRY_OPTIONS_MINTIMEOUT +OPENZEPPPELIN_HARDHAT_UPGRADES_LOCK_RETRY_OPTIONS_RETRIES +``` ## Learn more * Refer to the [API documentation](https://docs.openzeppelin.com/upgrades-plugins/api-hardhat-upgrades). diff --git a/packages/plugin-hardhat/src/utils/validations.ts b/packages/plugin-hardhat/src/utils/validations.ts index 8367d8a85..b1b825a00 100644 --- a/packages/plugin-hardhat/src/utils/validations.ts +++ b/packages/plugin-hardhat/src/utils/validations.ts @@ -1,7 +1,7 @@ import { promises as fs } from 'fs'; import path from 'path'; import lockfile from 'proper-lockfile'; - +import type { LockOptions } from 'proper-lockfile'; import type { HardhatRuntimeEnvironment } from 'hardhat/types'; import { ValidationDataCurrent, @@ -12,7 +12,17 @@ import { async function lock(file: string) { await fs.mkdir(path.dirname(file), { recursive: true }); - return lockfile.lock(file, { retries: { minTimeout: 50, factor: 1.3 }, realpath: false }); + + const lockRetryOptions = getLockfileOptionsFromEnv(); + const defaultLockRetryOptions = { + minTimeout: 50, + factor: 1.3, + }; + + return lockfile.lock(file, { + retries: lockRetryOptions ?? defaultLockRetryOptions, + realpath: false, + }); } export async function writeValidations(hre: HardhatRuntimeEnvironment, newRunData: ValidationRunData): Promise { @@ -36,6 +46,34 @@ export async function writeValidations(hre: HardhatRuntimeEnvironment, newRunDat } } +/** + * Extracts lockfile options from environment variables. + * All env variables are prefixed with + * `OPENZEPPPELIN_HARDHAT_UPGRADES_LOCK_RETRY_OPTIONS_` + * + * @note We grab options from env rather than as optional subtask arguments because subtask argument overrides are supported only in hardhat 2.13.0 and above, breaking compatibility with our peer dependency of hardhat ^2.0.2 + * @see https://github.com/NomicFoundation/hardhat/releases/tag/hardhat%402.13.0 + */ +export function getLockfileOptionsFromEnv() { + const lockRetryOptions: LockOptions['retries'] = {}; + const parentEnv = 'OPENZEPPPELIN_HARDHAT_UPGRADES_LOCK_RETRY_OPTIONS'; + const childOptions = ['factor', 'maxRetryTime', 'maxTimeout', 'minTimeout', 'retries'] as const; + + childOptions.forEach(option => { + const optionEnv = process.env[`${parentEnv}_${option.toUpperCase()}`]; + + if (optionEnv) { + lockRetryOptions[option] = Number(optionEnv); + } + }); + + if (Object.keys(lockRetryOptions).length === 0) { + return null; + } + + return lockRetryOptions; +} + export async function readValidations( hre: HardhatRuntimeEnvironment, acquireLock = true,