|
43 | 43 | import errno
|
44 | 44 | import ctypes
|
45 | 45 | from itertools import chain, repeat
|
| 46 | +import time |
46 | 47 | import zipfile
|
47 | 48 | import argparse
|
| 49 | +from random import randint |
| 50 | +from contextlib import contextmanager |
48 | 51 |
|
49 | 52 |
|
50 | 53 | # Application version
|
@@ -155,7 +158,10 @@ def log(msg, is_error=False):
|
155 | 158 | sys.stderr.write(msg) if is_error else sys.stdout.write(msg)
|
156 | 159 |
|
157 | 160 | def message(msg):
|
158 |
| - return "[mbed] %s\n" % msg |
| 161 | + if very_verbose: |
| 162 | + return "[mbed-%s] %s\n" % (os.getpid(), msg) |
| 163 | + else: |
| 164 | + return "[mbed] %s\n" % msg |
159 | 165 |
|
160 | 166 | def info(msg, level=1):
|
161 | 167 | if level <= 0 or verbose:
|
@@ -1223,7 +1229,8 @@ def clone(self, url, path, rev=None, depth=None, protocol=None, offline=False, *
|
1223 | 1229 | os.makedirs(os.path.split(path)[0])
|
1224 | 1230 |
|
1225 | 1231 | info("Carbon copy from \"%s\" to \"%s\"" % (cache, path))
|
1226 |
| - shutil.copytree(cache, path) |
| 1232 | + with self.cache_lock_held(url): |
| 1233 | + shutil.copytree(cache, path) |
1227 | 1234 |
|
1228 | 1235 | with cd(path):
|
1229 | 1236 | scm.seturl(formaturl(url, protocol))
|
@@ -1253,7 +1260,8 @@ def clone(self, url, path, rev=None, depth=None, protocol=None, offline=False, *
|
1253 | 1260 | self.url = url
|
1254 | 1261 | self.path = os.path.abspath(path)
|
1255 | 1262 | self.ignores()
|
1256 |
| - self.set_cache(url) |
| 1263 | + with self.cache_lock_held(url): |
| 1264 | + self.set_cache(url) |
1257 | 1265 | return True
|
1258 | 1266 |
|
1259 | 1267 | if offline:
|
@@ -1336,6 +1344,113 @@ def set_cache(self, url):
|
1336 | 1344 | warning("Unable to cache \"%s\" to \"%s\"" % (self.path, cpath))
|
1337 | 1345 | return False
|
1338 | 1346 |
|
| 1347 | + def cache_lock(self, url): |
| 1348 | + cpath = self.url2cachedir(url) |
| 1349 | + if not cpath: |
| 1350 | + return False |
| 1351 | + |
| 1352 | + if not os.path.isdir(cpath): |
| 1353 | + os.makedirs(cpath) |
| 1354 | + |
| 1355 | + lock_dir = os.path.join(cpath, '.lock') |
| 1356 | + lock_file = os.path.join(lock_dir, 'pid') |
| 1357 | + timeout = 300 |
| 1358 | + |
| 1359 | + for i in range(timeout): |
| 1360 | + if i: |
| 1361 | + time.sleep(1) |
| 1362 | + |
| 1363 | + if os.path.exists(lock_dir): |
| 1364 | + try: |
| 1365 | + if os.path.isfile(lock_file): |
| 1366 | + with open(lock_file, 'r') as f: |
| 1367 | + pid = f.read(8) |
| 1368 | + if not pid: |
| 1369 | + if int(os.path.getmtime(lock_file)) + timeout < int(time.time()): |
| 1370 | + info("Cache lock file exists, but is empty. Cleaning up") |
| 1371 | + os.remove(lock_file) |
| 1372 | + os.rmdir(lock_dir) |
| 1373 | + elif int(pid) != os.getpid() and self.pid_exists(pid): |
| 1374 | + info("Cache lock file exists and process %s is alive." % pid) |
| 1375 | + else: |
| 1376 | + info("Cache lock file exists, but %s is dead. Cleaning up" % pid) |
| 1377 | + os.remove(lock_file) |
| 1378 | + os.rmdir(lock_dir) |
| 1379 | + else: |
| 1380 | + os.rmdir(lock_dir) |
| 1381 | + continue |
| 1382 | + except (OSError) as e: |
| 1383 | + continue |
| 1384 | + else: |
| 1385 | + try: |
| 1386 | + os.mkdir(lock_dir) |
| 1387 | + with open(lock_file, 'w') as f: |
| 1388 | + info("Writing cache lock file %s for pid %s" % (lock_file, os.getpid())) |
| 1389 | + f.write(str(os.getpid())) |
| 1390 | + f.flush() |
| 1391 | + os.fsync(f) |
| 1392 | + break |
| 1393 | + except (OSError) as e: |
| 1394 | + ## Windows: |
| 1395 | + ## <type 'exceptions.WindowsError'> 17 [Error 183] Cannot create a file when that file already exists: 'testing' |
| 1396 | + ## or when concurrent: 13 WindowsError(5, 'Access is denied') |
| 1397 | + ## Linux: <type 'exceptions.OSError'> 17 [Errno 17] File exists: 'testing' |
| 1398 | + ## or when concurrent & virtualbox 71, OSError(71, 'Protocol error') |
| 1399 | + ## or when full: 28, OSError(28, 'No space left on device') |
| 1400 | + if e.errno in (17,13,71,28): |
| 1401 | + continue |
| 1402 | + else: |
| 1403 | + raise e |
| 1404 | + else: |
| 1405 | + error("Exceeded 5 minutes limit while waiting for other process to finish caching") |
| 1406 | + return True |
| 1407 | + |
| 1408 | + def cache_unlock(self, url): |
| 1409 | + cpath = self.url2cachedir(url) |
| 1410 | + if not cpath: |
| 1411 | + return False |
| 1412 | + |
| 1413 | + lock_dir = os.path.join(cpath, '.lock') |
| 1414 | + lock_file = os.path.join(lock_dir, 'pid') |
| 1415 | + try: |
| 1416 | + if os.path.exists(lock_dir): |
| 1417 | + if os.path.isfile(lock_file): |
| 1418 | + try: |
| 1419 | + with open(lock_file, 'r') as f: |
| 1420 | + pid = f.read(8) |
| 1421 | + if int(pid) != os.getpid(): |
| 1422 | + error("Cache lock file exists with a different pid (\"%s\" vs \"%s\")" % (pid, os.getpid())) |
| 1423 | + else: |
| 1424 | + info("Cache lock file exists with my pid (\"%s\"). Cleaning up." % (pid)) |
| 1425 | + except OSError: |
| 1426 | + error("Unable to unlock cache dir \"%s\"" % (cpath)) |
| 1427 | + os.remove(lock_file) |
| 1428 | + os.rmdir(lock_dir) |
| 1429 | + except (OSError) as e: |
| 1430 | + pass |
| 1431 | + return True |
| 1432 | + |
| 1433 | + @contextmanager |
| 1434 | + def cache_lock_held(self, url): |
| 1435 | + self.cache_lock(url) |
| 1436 | + try: |
| 1437 | + yield |
| 1438 | + finally: |
| 1439 | + self.cache_unlock(url) |
| 1440 | + |
| 1441 | + def pid_exists(self, pid): |
| 1442 | + try: |
| 1443 | + os.kill(int(pid), 0) |
| 1444 | + except OSError as err: |
| 1445 | + if err.errno == errno.ESRCH: |
| 1446 | + return False |
| 1447 | + elif err.errno == errno.EPERM: |
| 1448 | + return True |
| 1449 | + else: |
| 1450 | + raise err |
| 1451 | + else: |
| 1452 | + return True |
| 1453 | + |
1339 | 1454 | def can_update(self, clean, clean_deps):
|
1340 | 1455 | err = None
|
1341 | 1456 | if (self.is_local or self.url is None) and not clean_deps:
|
|
0 commit comments