Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sage
Path: blob/develop/build/sage_bootstrap/flock.py
4052 views
1
#!/usr/bin/env sage-bootstrap-python
2
# vim: set filetype=python:
3
"""
4
This script runs the given command under a file lock (similar to the flock
5
command on some systems).
6
"""
7
8
# This is originally motivated by pip, but has since been generalized. We
9
# should avoid running pip while uninstalling a package because that is prone
10
# to race conditions. This script runs pip under a lock. For details, see
11
# https://github.com/sagemath/sage/issues/21672
12
13
import fcntl
14
import os
15
import sys
16
import argparse
17
18
from sage_bootstrap.compat import quote
19
20
21
class FileType(argparse.FileType):
22
"""
23
Version of argparse.FileType with the option to ensure that the full path
24
to the file exists.
25
"""
26
27
def __init__(self, mode='r', makedirs=False):
28
# Note, the base class __init__ takes other arguments too depending on
29
# the Python version but we don't care about them for this purpose
30
super(FileType, self).__init__(mode=mode)
31
self._makedirs = makedirs
32
33
def __call__(self, string):
34
if self._makedirs and string != '-':
35
dirname = os.path.dirname(string)
36
try:
37
os.makedirs(dirname)
38
except OSError as exc:
39
if not os.path.isdir(dirname):
40
raise argparse.ArgumentTypeError(
41
"can't create '{0}': {1}".format(dirname, exc))
42
43
return super(FileType, self).__call__(string)
44
45
46
class IntOrFileType(FileType):
47
"""
48
Like FileType but also accepts an int (e.g. for a file descriptor).
49
"""
50
51
def __call__(self, string):
52
try:
53
return int(string)
54
except ValueError:
55
return super(IntOrFileType, self).__call__(string)
56
57
58
def run(argv=None):
59
parser = argparse.ArgumentParser(description=__doc__)
60
group = parser.add_mutually_exclusive_group()
61
group.add_argument('-s', '--shared', action='store_true',
62
help='create a shared lock')
63
64
# Note: A exclusive lock is created by default if no other flags are given,
65
# but supplying the --exclusive flag explicitly may help clarity
66
group.add_argument('-x', '--exclusive', action='store_true',
67
help='create an exclusive lock (the default)')
68
group.add_argument('-u', '--unlock', action='store_true',
69
help='remove an existing lock')
70
parser.add_argument('lock', metavar='LOCK',
71
type=IntOrFileType('w+', makedirs=True),
72
help='filename of the lock an integer file descriptor')
73
parser.add_argument('command', metavar='COMMAND', nargs=argparse.REMAINDER,
74
help='command to run with the lock including any '
75
'arguments to that command')
76
77
args = parser.parse_args(argv)
78
79
if args.shared:
80
locktype = fcntl.LOCK_SH
81
elif args.unlock:
82
locktype = fcntl.LOCK_UN
83
else:
84
locktype = fcntl.LOCK_EX
85
86
lock = args.lock
87
command = args.command
88
89
if isinstance(lock, int) and command:
90
parser.error('sage-flock does not accept a command when passed '
91
'a file descriptor number')
92
93
# First try a non-blocking lock such that we can give an informative
94
# message while the user is waiting.
95
try:
96
fcntl.flock(lock, locktype | fcntl.LOCK_NB)
97
except IOError as exc:
98
if locktype == fcntl.LOCK_SH:
99
kind = "shared"
100
elif locktype == fcntl.LOCK_UN:
101
# This shouldn't happen
102
sys.stderr.write(
103
"Unexpected error trying to unlock fd: {0}\n".format(exc))
104
return 1
105
else:
106
kind = "exclusive"
107
108
sys.stderr.write("Waiting for {0} lock to run {1} ... ".format(
109
kind, ' '.join(quote(arg) for arg in command)))
110
fcntl.flock(lock, locktype)
111
sys.stderr.write("ok\n")
112
113
if not (args.unlock or isinstance(lock, int)):
114
os.execvp(command[0], command)
115
116
117
if __name__ == '__main__':
118
sys.exit(run())
119
120