install : finer graned locking for install command

This commit is contained in:
alalazo 2016-04-29 13:28:34 +02:00 committed by Todd Gamblin
parent 5988b3a222
commit 34fe51a4aa
5 changed files with 97 additions and 30 deletions

View File

@ -28,9 +28,13 @@
import time import time
import socket import socket
import llnl.util.tty as tty
__all__ = ['Lock', 'LockTransaction', 'WriteTransaction', 'ReadTransaction', __all__ = ['Lock', 'LockTransaction', 'WriteTransaction', 'ReadTransaction',
'LockError'] 'LockError']
# Default timeout in seconds, after which locks will raise exceptions. # Default timeout in seconds, after which locks will raise exceptions.
_default_timeout = 60 _default_timeout = 60
@ -120,6 +124,7 @@ def acquire_read(self, timeout=_default_timeout):
""" """
if self._reads == 0 and self._writes == 0: if self._reads == 0 and self._writes == 0:
tty.debug('READ LOCK : {0._file_path} [Acquiring]'.format(self))
self._lock(fcntl.LOCK_SH, timeout) # can raise LockError. self._lock(fcntl.LOCK_SH, timeout) # can raise LockError.
self._reads += 1 self._reads += 1
return True return True
@ -139,6 +144,7 @@ def acquire_write(self, timeout=_default_timeout):
""" """
if self._writes == 0: if self._writes == 0:
tty.debug('WRITE LOCK : {0._file_path} [Acquiring]'.format(self))
self._lock(fcntl.LOCK_EX, timeout) # can raise LockError. self._lock(fcntl.LOCK_EX, timeout) # can raise LockError.
self._writes += 1 self._writes += 1
return True return True
@ -159,6 +165,7 @@ def release_read(self):
assert self._reads > 0 assert self._reads > 0
if self._reads == 1 and self._writes == 0: if self._reads == 1 and self._writes == 0:
tty.debug('READ LOCK : {0._file_path} [Released]'.format(self))
self._unlock() # can raise LockError. self._unlock() # can raise LockError.
self._reads -= 1 self._reads -= 1
return True return True
@ -179,6 +186,7 @@ def release_write(self):
assert self._writes > 0 assert self._writes > 0
if self._writes == 1 and self._reads == 0: if self._writes == 1 and self._reads == 0:
tty.debug('WRITE LOCK : {0._file_path} [Released]'.format(self))
self._unlock() # can raise LockError. self._unlock() # can raise LockError.
self._writes -= 1 self._writes -= 1
return True return True

View File

@ -84,7 +84,6 @@ def install(parser, args):
specs = spack.cmd.parse_specs(args.packages, concretize=True) specs = spack.cmd.parse_specs(args.packages, concretize=True)
for spec in specs: for spec in specs:
package = spack.repo.get(spec) package = spack.repo.get(spec)
with spack.installed_db.write_transaction():
package.do_install( package.do_install(
keep_prefix=args.keep_prefix, keep_prefix=args.keep_prefix,
keep_stage=args.keep_stage, keep_stage=args.keep_stage,

View File

@ -33,7 +33,7 @@
2. It will allow us to track external installations as well as lost 2. It will allow us to track external installations as well as lost
packages and their dependencies. packages and their dependencies.
Prior ot the implementation of this store, a direcotry layout served Prior to the implementation of this store, a directory layout served
as the authoritative database of packages in Spack. This module as the authoritative database of packages in Spack. This module
provides a cache and a sanity checking mechanism for what is in the provides a cache and a sanity checking mechanism for what is in the
filesystem. filesystem.

View File

@ -39,8 +39,17 @@
import textwrap import textwrap
import time import time
import string import string
import contextlib
from urlparse import urlparse
from StringIO import StringIO
import llnl.util.lock
import llnl.util.tty as tty import llnl.util.tty as tty
from llnl.util.filesystem import *
from llnl.util.lang import *
from llnl.util.link_tree import LinkTree
from llnl.util.tty.log import log_output
import spack import spack
import spack.build_environment import spack.build_environment
import spack.compilers import spack.compilers
@ -53,11 +62,6 @@
import spack.url import spack.url
import spack.util.web import spack.util.web
from StringIO import StringIO
from llnl.util.filesystem import *
from llnl.util.lang import *
from llnl.util.link_tree import LinkTree
from llnl.util.tty.log import log_output
from spack.stage import Stage, ResourceStage, StageComposite from spack.stage import Stage, ResourceStage, StageComposite
from spack.util.environment import dump_environment from spack.util.environment import dump_environment
from spack.util.executable import ProcessError, which from spack.util.executable import ProcessError, which
@ -346,6 +350,9 @@ def __init__(self, spec):
# this determines how the package should be built. # this determines how the package should be built.
self.spec = spec self.spec = spec
# Lock on the prefix shared resource. Will be set in prefix property
self._prefix_lock = None
# Name of package is the name of its module, without the # Name of package is the name of its module, without the
# containing module names. # containing module names.
self.name = self.module.__name__ self.name = self.module.__name__
@ -691,6 +698,22 @@ def installed_dependents(self):
dependents.append(spec) dependents.append(spec)
return dependents return dependents
@property
def prefix_lock(self):
if self._prefix_lock is None:
dirname = join_path(os.path.dirname(self.spec.prefix), '.locks')
basename = os.path.basename(self.spec.prefix)
lock_file = join_path(dirname, basename)
if not os.path.exists(lock_file):
tty.debug('TOUCH FILE : {0}'.format(lock_file))
os.makedirs(dirname)
touch(lock_file)
self._prefix_lock = llnl.util.lock.Lock(lock_file)
return self._prefix_lock
@property @property
def prefix(self): def prefix(self):
"""Get the prefix into which this package should be installed.""" """Get the prefix into which this package should be installed."""
@ -875,6 +898,22 @@ def _resource_stage(self, resource):
resource_stage_folder = '-'.join(pieces) resource_stage_folder = '-'.join(pieces)
return resource_stage_folder return resource_stage_folder
@contextlib.contextmanager
def _prefix_read_lock(self):
try:
self.prefix_lock.acquire_read(60)
yield self
finally:
self.prefix_lock.release_read()
@contextlib.contextmanager
def _prefix_write_lock(self):
try:
self.prefix_lock.acquire_write(60)
yield self
finally:
self.prefix_lock.release_write()
install_phases = set(['configure', 'build', 'install', 'provenance']) install_phases = set(['configure', 'build', 'install', 'provenance'])
def do_install(self, def do_install(self,
@ -926,8 +965,12 @@ def do_install(self,
# Ensure package is not already installed # Ensure package is not already installed
layout = spack.install_layout layout = spack.install_layout
if 'install' in install_phases and layout.check_installed(self.spec): with self._prefix_read_lock():
tty.msg("%s is already installed in %s" % (self.name, self.prefix)) if ('install' in install_phases and
layout.check_installed(self.spec)):
tty.msg(
"%s is already installed in %s" % (self.name, self.prefix))
rec = spack.installed_db.get_record(self.spec) rec = spack.installed_db.get_record(self.spec)
if (not rec.explicit) and explicit: if (not rec.explicit) and explicit:
with spack.installed_db.write_transaction(): with spack.installed_db.write_transaction():
@ -983,7 +1026,7 @@ def build_process():
self.build_directory = join_path(self.stage.path, 'spack-build') self.build_directory = join_path(self.stage.path, 'spack-build')
self.source_directory = self.stage.source_path self.source_directory = self.stage.source_path
with self.stage: with contextlib.nested(self.stage, self._prefix_write_lock()):
# Run the pre-install hook in the child process after # Run the pre-install hook in the child process after
# the directory is created. # the directory is created.
spack.hooks.pre_install(self) spack.hooks.pre_install(self)
@ -1077,8 +1120,9 @@ def build_process():
wrap=False) wrap=False)
raise raise
# note: PARENT of the build process adds the new package to # Parent of the build process adds the new package to
# the database, so that we don't need to re-read from file. # the database, so that we don't need to re-read from file.
# NOTE: add() implicitly acquires a write-lock
spack.installed_db.add( spack.installed_db.add(
self.spec, spack.install_layout, explicit=explicit) self.spec, spack.install_layout, explicit=explicit)

View File

@ -29,6 +29,7 @@
from urlparse import urljoin from urlparse import urljoin
import llnl.util.tty as tty import llnl.util.tty as tty
import llnl.util.lock
from llnl.util.filesystem import * from llnl.util.filesystem import *
import spack.util.pattern as pattern import spack.util.pattern as pattern
@ -88,8 +89,9 @@ class Stage(object):
similar, and are intended to persist for only one run of spack. similar, and are intended to persist for only one run of spack.
""" """
def __init__(self, url_or_fetch_strategy, def __init__(
name=None, mirror_path=None, keep=False, path=None): self, url_or_fetch_strategy,
name=None, mirror_path=None, keep=False, path=None, lock=True):
"""Create a stage object. """Create a stage object.
Parameters: Parameters:
url_or_fetch_strategy url_or_fetch_strategy
@ -147,6 +149,15 @@ def __init__(self, url_or_fetch_strategy,
# Flag to decide whether to delete the stage folder on exit or not # Flag to decide whether to delete the stage folder on exit or not
self.keep = keep self.keep = keep
# File lock for the stage directory
self._lock_file = None
self._lock = None
if lock:
self._lock_file = join_path(spack.stage_path, self.name + '.lock')
if not os.path.exists(self._lock_file):
touch(self._lock_file)
self._lock = llnl.util.lock.Lock(self._lock_file)
def __enter__(self): def __enter__(self):
""" """
Entering a stage context will create the stage directory Entering a stage context will create the stage directory
@ -154,6 +165,8 @@ def __enter__(self):
Returns: Returns:
self self
""" """
if self._lock is not None:
self._lock.acquire_write(timeout=60)
self.create() self.create()
return self return self
@ -175,6 +188,9 @@ def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None and not self.keep: if exc_type is None and not self.keep:
self.destroy() self.destroy()
if self._lock is not None:
self._lock.release_write()
def _need_to_create_path(self): def _need_to_create_path(self):
"""Makes sure nothing weird has happened since the last time we """Makes sure nothing weird has happened since the last time we
looked at path. Returns True if path already exists and is ok. looked at path. Returns True if path already exists and is ok.