Skip to content

Commit d879cda

Browse files
committed
add --wait-on-lock-limit and --wait-on-lock-interval configuration options, deprecate --wait-on-lock
1 parent 5675133 commit d879cda

File tree

4 files changed

+120
-37
lines changed

4 files changed

+120
-37
lines changed

easybuild/tools/config.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@
102102
DEFAULT_PNS = 'EasyBuildPNS'
103103
DEFAULT_PREFIX = os.path.join(os.path.expanduser('~'), ".local", "easybuild")
104104
DEFAULT_REPOSITORY = 'FileRepository'
105+
DEFAULT_WAIT_ON_LOCK_INTERVAL = 60
106+
DEFAULT_WAIT_ON_LOCK_LIMIT = 0
105107

106108
EBROOT_ENV_VAR_ACTIONS = [ERROR, IGNORE, UNSET, WARN]
107109
LOADED_MODULES_ACTIONS = [ERROR, IGNORE, PURGE, UNLOAD, WARN]
@@ -211,6 +213,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
211213
'subdir_user_modules',
212214
'test_report_env_filter',
213215
'testoutput',
216+
'wait_on_lock',
214217
'umask',
215218
'zip_logs',
216219
],
@@ -256,7 +259,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
256259
'use_f90cache',
257260
'use_existing_modules',
258261
'set_default_module',
259-
'wait_on_lock',
262+
'wait_on_lock_limit',
260263
],
261264
True: [
262265
'cleanup_builddir',
@@ -305,6 +308,9 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
305308
DEFAULT_ALLOW_LOADED_MODULES: [
306309
'allow_loaded_modules',
307310
],
311+
DEFAULT_WAIT_ON_LOCK_INTERVAL: [
312+
'wait_on_lock_interval',
313+
],
308314
}
309315
# build option that do not have a perfectly matching command line option
310316
BUILD_OPTIONS_OTHER = {

easybuild/tools/filetools.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
from easybuild.tools import run
6161
# import build_log must stay, to use of EasyBuildLog
6262
from easybuild.tools.build_log import EasyBuildError, dry_run_msg, print_msg, print_warning
63-
from easybuild.tools.config import GENERIC_EASYBLOCK_PKG, build_option, install_path
63+
from easybuild.tools.config import DEFAULT_WAIT_ON_LOCK_INTERVAL, GENERIC_EASYBLOCK_PKG, build_option, install_path
6464
from easybuild.tools.py2vs3 import std_urllib, string_type
6565
from easybuild.tools.utilities import nub, remove_unwanted_chars
6666

@@ -1531,12 +1531,40 @@ def check_lock(lock_name):
15311531
lock_path = det_lock_path(lock_name)
15321532
if os.path.exists(lock_path):
15331533
_log.info("Lock %s exists!", lock_path)
1534+
1535+
wait_interval = build_option('wait_on_lock_interval')
1536+
wait_limit = build_option('wait_on_lock_limit')
1537+
1538+
# --wait-on-lock is deprecated, should use --wait-on-lock-limit and --wait-on-lock-interval instead
15341539
wait_on_lock = build_option('wait_on_lock')
1535-
if wait_on_lock:
1536-
while os.path.exists(lock_path):
1537-
print_msg("lock %s exists, waiting %d seconds..." % (lock_path, wait_on_lock),
1540+
if wait_on_lock is not None:
1541+
depr_msg = "Use of --wait-on-lock is deprecated, use --wait-on-lock-limit and --wait-on-lock-interval"
1542+
_log.deprecated(depr_msg, '5.0')
1543+
1544+
# if --wait-on-lock-interval has default value and --wait-on-lock is specified too, the latter wins
1545+
# (required for backwards compatibility)
1546+
if wait_interval == DEFAULT_WAIT_ON_LOCK_INTERVAL and wait_on_lock > 0:
1547+
wait_interval = wait_on_lock
1548+
1549+
# if --wait-on-lock-limit is not specified we need to wait indefinitely if --wait-on-lock is specified,
1550+
# since the original semantics of --wait-on-lock was that it specified the waiting time interval (no limit)
1551+
if not wait_limit:
1552+
wait_limit = -1
1553+
1554+
# wait limit could be zero (no waiting), -1 (no waiting limit) or non-zero value (waiting limit in seconds)
1555+
if wait_limit != 0:
1556+
wait_time = 0
1557+
while os.path.exists(lock_path) and (wait_limit == -1 or wait_time < wait_limit):
1558+
print_msg("lock %s exists, waiting %d seconds..." % (lock_path, wait_interval),
15381559
silent=build_option('silent'))
1539-
time.sleep(wait_on_lock)
1560+
time.sleep(wait_interval)
1561+
wait_time += wait_interval
1562+
1563+
if wait_limit != -1 and wait_time >= wait_limit:
1564+
error_msg = "Maximum wait time for lock %s to be released reached: %s sec >= %s sec"
1565+
raise EasyBuildError(error_msg, lock_path, wait_time, wait_limit)
1566+
else:
1567+
_log.info("Lock %s was released!", lock_path)
15401568
else:
15411569
raise EasyBuildError("Lock %s already exists, aborting!", lock_path)
15421570
else:

easybuild/tools/options.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@
6464
from easybuild.tools.config import DEFAULT_JOB_BACKEND, DEFAULT_LOGFILE_FORMAT, DEFAULT_MAX_FAIL_RATIO_PERMS
6565
from easybuild.tools.config import DEFAULT_MNS, DEFAULT_MODULE_SYNTAX, DEFAULT_MODULES_TOOL, DEFAULT_MODULECLASSES
6666
from easybuild.tools.config import DEFAULT_PATH_SUBDIRS, DEFAULT_PKG_RELEASE, DEFAULT_PKG_TOOL, DEFAULT_PKG_TYPE
67-
from easybuild.tools.config import DEFAULT_PNS, DEFAULT_PREFIX, DEFAULT_REPOSITORY, EBROOT_ENV_VAR_ACTIONS, ERROR
68-
from easybuild.tools.config import FORCE_DOWNLOAD_CHOICES, GENERAL_CLASS, IGNORE, JOB_DEPS_TYPE_ABORT_ON_ERROR
69-
from easybuild.tools.config import JOB_DEPS_TYPE_ALWAYS_RUN, LOADED_MODULES_ACTIONS, WARN
70-
from easybuild.tools.config import LOCAL_VAR_NAMING_CHECK_WARN, LOCAL_VAR_NAMING_CHECKS
67+
from easybuild.tools.config import DEFAULT_PNS, DEFAULT_PREFIX, DEFAULT_REPOSITORY, DEFAULT_WAIT_ON_LOCK_INTERVAL
68+
from easybuild.tools.config import DEFAULT_WAIT_ON_LOCK_LIMIT, EBROOT_ENV_VAR_ACTIONS, ERROR, FORCE_DOWNLOAD_CHOICES
69+
from easybuild.tools.config import GENERAL_CLASS, IGNORE, JOB_DEPS_TYPE_ABORT_ON_ERROR, JOB_DEPS_TYPE_ALWAYS_RUN
70+
from easybuild.tools.config import LOADED_MODULES_ACTIONS, LOCAL_VAR_NAMING_CHECK_WARN, LOCAL_VAR_NAMING_CHECKS, WARN
7171
from easybuild.tools.config import get_pretend_installpath, init, init_build_options, mk_full_default_path
7272
from easybuild.tools.configobj import ConfigObj, ConfigObjError
7373
from easybuild.tools.docs import FORMAT_TXT, FORMAT_RST
@@ -76,9 +76,8 @@
7676
from easybuild.tools.docs import list_easyblocks, list_toolchains
7777
from easybuild.tools.environment import restore_env, unset_env_vars
7878
from easybuild.tools.filetools import CHECKSUM_TYPE_SHA256, CHECKSUM_TYPES, install_fake_vsc, move_file, which
79-
from easybuild.tools.github import GITHUB_EB_MAIN, GITHUB_EASYCONFIGS_REPO
80-
from easybuild.tools.github import GITHUB_PR_DIRECTION_DESC, GITHUB_PR_ORDER_CREATED, GITHUB_PR_STATE_OPEN
81-
from easybuild.tools.github import GITHUB_PR_STATES, GITHUB_PR_ORDERS, GITHUB_PR_DIRECTIONS
79+
from easybuild.tools.github import GITHUB_EB_MAIN, GITHUB_PR_DIRECTION_DESC, GITHUB_PR_ORDER_CREATED
80+
from easybuild.tools.github import GITHUB_PR_STATE_OPEN, GITHUB_PR_STATES, GITHUB_PR_ORDERS, GITHUB_PR_DIRECTIONS
8281
from easybuild.tools.github import HAVE_GITHUB_API, HAVE_KEYRING, VALID_CLOSE_PR_REASONS
8382
from easybuild.tools.github import fetch_easyblocks_from_pr, fetch_github_token
8483
from easybuild.tools.hooks import KNOWN_HOOKS
@@ -442,8 +441,15 @@ def override_options(self):
442441
None, 'store_true', False),
443442
'verify-easyconfig-filenames': ("Verify whether filename of specified easyconfigs matches with contents",
444443
None, 'store_true', False),
445-
'wait-on-lock': ("Wait interval (in seconds) to use when waiting for existing lock to be removed "
446-
"(0: implies no waiting, but exiting with an error)", int, 'store', 0),
444+
'wait-on-lock': ("Wait for lock to be released; 0 implies no waiting (exit with an error if the lock "
445+
"already exists), non-zero value specified waiting interval [DEPRECATED: "
446+
"use --wait-on-lock-interval and --wait-on-lock-limit instead]",
447+
int, 'store_or_None', None),
448+
'wait-on-lock-interval': ("Wait interval (in seconds) to use when waiting for existing lock to be removed",
449+
int, 'store', DEFAULT_WAIT_ON_LOCK_INTERVAL),
450+
'wait-on-lock-limit': ("Maximum amount of time (in seconds) to wait until lock is released (0 means no "
451+
"waiting at all, exit with error; -1 means no waiting limit, keep waiting)",
452+
int, 'store', DEFAULT_WAIT_ON_LOCK_LIMIT),
447453
'zip-logs': ("Zip logs that are copied to install directory, using specified command",
448454
None, 'store_or_None', 'gzip'),
449455

test/framework/toy_build.py

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2759,43 +2759,86 @@ def __enter__(self):
27592759
def __exit__(self, type, value, traceback):
27602760
pass
27612761

2762-
# wait for lock to be removed, with 1 second interval of checking
2763-
extra_args.append('--wait-on-lock=1')
2762+
# wait for lock to be removed, with 1 second interval of checking;
2763+
# check with both --wait-on-lock-interval and deprecated --wait-on-lock options
27642764

27652765
wait_regex = re.compile("^== lock .*_software_toy_0.0.lock exists, waiting 1 seconds", re.M)
27662766
ok_regex = re.compile("^== COMPLETED: Installation ended successfully", re.M)
27672767

2768-
self.assertTrue(os.path.exists(toy_lock_path))
2768+
test_cases = [
2769+
['--wait-on-lock=1'],
2770+
['--wait-on-lock=1', '--wait-on-lock-interval=60'],
2771+
['--wait-on-lock=100', '--wait-on-lock-interval=1'],
2772+
['--wait-on-lock-limit=100', '--wait-on-lock=1'],
2773+
['--wait-on-lock-limit=100', '--wait-on-lock-interval=1'],
2774+
['--wait-on-lock-limit=-1', '--wait-on-lock=1'],
2775+
['--wait-on-lock-limit=-1', '--wait-on-lock-interval=1'],
2776+
]
27692777

2770-
# use context manager to remove lock after 3 seconds
2771-
with remove_lock_after(3, toy_lock_path):
2772-
self.mock_stderr(True)
2773-
self.mock_stdout(True)
2774-
self.test_toy_build(extra_args=extra_args, verify=False, raise_error=True, testing=False)
2775-
stderr, stdout = self.get_stderr(), self.get_stdout()
2776-
self.mock_stderr(False)
2777-
self.mock_stdout(False)
2778+
for opts in test_cases:
27782779

2779-
self.assertEqual(stderr, '')
2780+
if any('--wait-on-lock=' in x for x in opts):
2781+
self.allow_deprecated_behaviour()
2782+
else:
2783+
self.disallow_deprecated_behaviour()
27802784

2781-
wait_matches = wait_regex.findall(stdout)
2782-
# we can't rely on an exact number of 'waiting' messages, so let's go with a range...
2783-
self.assertTrue(len(wait_matches) in range(2, 5))
2785+
if not os.path.exists(toy_lock_path):
2786+
mkdir(toy_lock_path)
27842787

2785-
self.assertTrue(ok_regex.search(stdout), "Pattern '%s' found in: %s" % (ok_regex.pattern, stdout))
2788+
self.assertTrue(os.path.exists(toy_lock_path))
2789+
2790+
all_args = extra_args + opts
2791+
2792+
# use context manager to remove lock after 3 seconds
2793+
with remove_lock_after(3, toy_lock_path):
2794+
self.mock_stderr(True)
2795+
self.mock_stdout(True)
2796+
self.test_toy_build(extra_args=all_args, verify=False, raise_error=True, testing=False)
2797+
stderr, stdout = self.get_stderr(), self.get_stdout()
2798+
self.mock_stderr(False)
2799+
self.mock_stdout(False)
27862800

2787-
# when there is no lock in place, --wait-on-lock has no impact
2788-
self.assertFalse(os.path.exists(toy_lock_path))
2801+
if any('--wait-on-lock=' in x for x in all_args):
2802+
self.assertTrue("Use of --wait-on-lock is deprecated" in stderr)
2803+
else:
2804+
self.assertEqual(stderr, '')
2805+
2806+
wait_matches = wait_regex.findall(stdout)
2807+
# we can't rely on an exact number of 'waiting' messages, so let's go with a range...
2808+
self.assertTrue(len(wait_matches) in range(2, 5))
2809+
2810+
self.assertTrue(ok_regex.search(stdout), "Pattern '%s' found in: %s" % (ok_regex.pattern, stdout))
2811+
2812+
# check use of --wait-on-lock-limit: if lock is never removed, we should give up when limit is reached
2813+
mkdir(toy_lock_path)
2814+
all_args = extra_args + ['--wait-on-lock-limit=3', '--wait-on-lock-interval=1']
27892815
self.mock_stderr(True)
27902816
self.mock_stdout(True)
2791-
self.test_toy_build(extra_args=extra_args, verify=False, raise_error=True, testing=False)
2817+
error_pattern = r"Maximum wait time for lock /.*toy_0.0.lock to be released reached: [0-9]+ sec >= 3 sec"
2818+
self.assertErrorRegex(EasyBuildError, error_pattern, self.test_toy_build, extra_args=all_args,
2819+
verify=False, raise_error=True, testing=False)
27922820
stderr, stdout = self.get_stderr(), self.get_stdout()
27932821
self.mock_stderr(False)
27942822
self.mock_stdout(False)
27952823

2796-
self.assertEqual(stderr, '')
2797-
self.assertTrue(ok_regex.search(stdout), "Pattern '%s' found in: %s" % (ok_regex.pattern, stdout))
2798-
self.assertFalse(wait_regex.search(stdout), "Pattern '%s' not found in: %s" % (wait_regex.pattern, stdout))
2824+
wait_matches = wait_regex.findall(stdout)
2825+
self.assertTrue(len(wait_matches) in range(2, 5))
2826+
2827+
# when there is no lock in place, --wait-on-lock* has no impact
2828+
remove_dir(toy_lock_path)
2829+
for opt in ['--wait-on-lock=1', '--wait-on-lock-limit=3', '--wait-on-lock-interval=1']:
2830+
all_args = extra_args + [opt]
2831+
self.assertFalse(os.path.exists(toy_lock_path))
2832+
self.mock_stderr(True)
2833+
self.mock_stdout(True)
2834+
self.test_toy_build(extra_args=all_args, verify=False, raise_error=True, testing=False)
2835+
stderr, stdout = self.get_stderr(), self.get_stdout()
2836+
self.mock_stderr(False)
2837+
self.mock_stdout(False)
2838+
2839+
self.assertEqual(stderr, '')
2840+
self.assertTrue(ok_regex.search(stdout), "Pattern '%s' found in: %s" % (ok_regex.pattern, stdout))
2841+
self.assertFalse(wait_regex.search(stdout), "Pattern '%s' not found in: %s" % (wait_regex.pattern, stdout))
27992842

28002843
# check for clean error on creation of lock
28012844
extra_args = ['--locks-dir=/']

0 commit comments

Comments
 (0)