Skip to content

Finer-grained locking#1562

Merged
tgamblin merged 15 commits intodevelopfrom
features/db-locking
Oct 11, 2016
Merged

Finer-grained locking#1562
tgamblin merged 15 commits intodevelopfrom
features/db-locking

Conversation

@tgamblin
Copy link
Copy Markdown
Member

@tgamblin tgamblin commented Aug 19, 2016

Fixes #1266, #1966.

Rebased #866 on develop. See discussion there.

To recap, from @alalazo:

Modifications
  • removed global db locks from install, uninstall and diy
  • added lock for stage directory (an exclusive lock is taken as part of the Stage context manager)
  • added lock for prefix directory (an exclusive lock is taken during Package.do_install)
  • debug output shows locks acquire and release
  • Use single lock file instead of per-prefix locks.
Install and uninstall are not mutually exclusive

This means that if :

spack uninstall ...

is run concurrently with

spack install ...

the former command may cause the latter to fail (the same way an administrator can sudo rm ... something that another admin is about to use)

@tgamblin tgamblin mentioned this pull request Aug 19, 2016
4 tasks
@tgamblin tgamblin force-pushed the features/db-locking branch from 7fa91aa to 537e9cc Compare August 19, 2016 16:32
@trws
Copy link
Copy Markdown
Contributor

trws commented Aug 19, 2016

This looks good, I will admit though that I'm a bit wary of it without any tests.

@tgamblin
Copy link
Copy Markdown
Member Author

@alalazo @trws: I played around with this a bit and I think a few things are needed. I'm worried that the lock files:

  1. tend to pile up in the install and stage directories (there are a lot of them), and
  2. They're all in one directory.

Depending on the parallel FS, having all the locks in one directory can be good or bad, but when it's bad it can be really bad. So I would like to somehow get the locks inside the stage and prefix directories.

Given those requirements, I played around with Spack's lock file a bit. It uses readers-writer locks because it has heretofore only had to deal with locking single files (like the DB) that stick around. I think that is the right way to lock those files. However, the install and stage directories are transient, and they'll always need an exclusive lock (not reader locks).

I think the best thing to do would be to use simple exclusive file locks (open with O_EXCL) to control these directories. They have the advantage that the files only exist when in use, and I think we could implement them such that they live inside the install prefix and/or stage. I can't do that with reader-writer locks. RW locks don't play nicely with being deleted -- you get races between open and the first call to fcntl that I don't think are possible to get rid of when the lock files are deleted on release.

So, if you guys aren't opposed, I would like to take a stab at introducing a new exclusive file lock (pretty simple) and using that inside the stage and prefix directories where @alalazo currently uses the current exclusive fcntl locks.

Thoughts?

@alalazo
Copy link
Copy Markdown
Member

alalazo commented Aug 21, 2016

So, if you guys aren't opposed, I would like to take a stab at introducing a new exclusive file lock (pretty simple) and using that inside the stage and prefix directories where @alalazo currently uses the current exclusive fcntl locks.

👍 This will surely be an improvement over the current implementation

@trws
Copy link
Copy Markdown
Contributor

trws commented Aug 22, 2016

That sounds like a good idea to me, as long as we can count on an outer lock to prevent creation of multiple versions of an otherwise redundant stage directory.

@tgamblin tgamblin changed the title Finer-grained locking [WIP] Finer-grained locking Aug 29, 2016
@tgamblin tgamblin force-pushed the features/db-locking branch 2 times, most recently from 5dd56e8 to 784944b Compare October 6, 2016 08:47
@tgamblin tgamblin removed the WIP label Oct 6, 2016
@tgamblin
Copy link
Copy Markdown
Member Author

tgamblin commented Oct 6, 2016

@alalazo: this is reworked. Can you test it out?

The approach is slightly different (but better IMHO) than what I suggested above. I figured out how we can have a single lock file for all the prefixes and a single file for all the stages and ALSO get readers-writer semantics.

How it works for prefixes:

  1. The lock file is in opt/spack/.spack-db/prefix_lock and it lives alongside the install DB.
  2. Each lock is an fcntl byte-range lock on the nth byte of a file. Byte range locks can extend past the end of a file w/o taking up space, so we can have up to 2^63-1 of these on 64-bit machines.
  3. n is the sys.maxsize-bit prefix of the DAG hash.

Stages are similar -- they use a prefix of the sha1 of the stage name instead of the DAG hash prefix.

With 100 concurrent builds, the likelihood of a lock collision (false positive) is ~5.36e-16, so this scheme should retain more than sufficient parallelism (with no chance of false negatives), and get us reader-writer lock semantics with a single file, so no need to clean up lots of lock files.

@tgamblin
Copy link
Copy Markdown
Member Author

tgamblin commented Oct 6, 2016

Also this fixes #1266.

@tgamblin tgamblin changed the title [WIP] Finer-grained locking Finer-grained locking Oct 6, 2016
@tgamblin
Copy link
Copy Markdown
Member Author

tgamblin commented Oct 6, 2016

Also, if anyone is interested, both of these are good reads:

TL; DR: fcntl locks (lockf in Python... sigh) work on NFS v2.something and up, and most sites deploy NFS v3 or v4, where they work now. They also work on GPFS and Lustre. I believe that covers most filesystems we care about. These locks should let us do parallel builds, and the readers-writer semantics should be useful for things like taking read locks out on dependencies while packages are being installed (preventing others from uninstalling them while that happens and before the new package that depends on them is in the DB).

@tgamblin tgamblin force-pushed the features/db-locking branch from 872e5a0 to ca5f5ee Compare October 6, 2016 10:00
@alalazo
Copy link
Copy Markdown
Member

alalazo commented Oct 6, 2016

@tgamblin Thanks for the references above!

@alalazo
Copy link
Copy Markdown
Member

alalazo commented Oct 6, 2016

Each lock is an fcntl byte-range lock on the nth byte of a file. Byte range locks can extend past the end of a file w/o taking up space, so we can have up to 2^63-1 of these on 64-bit machines.

Quite clever! I didn't know you could do something like this.

@tgamblin
Copy link
Copy Markdown
Member Author

tgamblin commented Oct 6, 2016

@alalazo: I still wish there was zero chance of taking a lock unnecessarily but sha1 collision likelihood is very low even for a prefix.

If this works for you I'll merge. Note that this also fixes a bug with the backported read only lock fix.

@tgamblin
Copy link
Copy Markdown
Member Author

tgamblin commented Oct 6, 2016

@adamjstewart @davydden: I need someone besides me to test this -- if either of you have time can you verify it fixes #1266?

@adamjstewart
Copy link
Copy Markdown
Member

Will do.

@adamjstewart
Copy link
Copy Markdown
Member

I'm seeing some problems on my end:

[01:59:45] ajstewart@blogin4:/soft/spack-0.9.1 (develop)
$ git co features/db-locking
Branch features/db-locking set up to track remote branch features/db-locking from upstream.
Switched to a new branch 'features/db-locking'
[01:59:52] ajstewart@blogin4:/soft/spack-0.9.1 (features/db-locking)
$ spack edit m4
[02:00:14] ajstewart@blogin4:/soft/spack-0.9.1 (features/db-locking *)
$ spack install gmp
==> gmp is already installed in /blues/gpfs/home/software/spack-0.9.1/opt/spack/linux-centos6-x86_64/gcc-6.1.0/gmp-6.1.1-e3x7lcgg4qizghb53izi72aep6ybswvp
Traceback (most recent call last):
  File "/soft/spack-0.9.1/bin/spack", line 192, in <module>
    main()
  File "/soft/spack-0.9.1/bin/spack", line 169, in main
    return_val = command(parser, args)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/cmd/install.py", line 97, in install
    explicit=True)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/package.py", line 981, in do_install
    rec = spack.installed_db.get_record(self.spec)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/database.py", line 79, in converter
    return function(self, spec_like, *args, **kwargs)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/database.py", line 527, in get_record
    key = self._get_matching_spec_key(spec, **kwargs)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/database.py", line 519, in _get_matching_spec_key
    match = self.query_one(spec, **kwargs)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/database.py", line 653, in query_one
    assert len(concrete_specs) <= 1
AssertionError
[02:00:24] ajstewart@blogin4:/soft/spack-0.9.1 (features/db-locking *)
$ spack reindex
[02:01:28] ajstewart@blogin4:/soft/spack-0.9.1 (features/db-locking *)
$ spack install gmp
==> gmp is already installed in /blues/gpfs/home/software/spack-0.9.1/opt/spack/linux-centos6-x86_64/gcc-6.1.0/gmp-6.1.1-e3x7lcgg4qizghb53izi72aep6ybswvp
Traceback (most recent call last):
  File "/soft/spack-0.9.1/bin/spack", line 192, in <module>
    main()
  File "/soft/spack-0.9.1/bin/spack", line 169, in main
    return_val = command(parser, args)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/cmd/install.py", line 97, in install
    explicit=True)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/package.py", line 981, in do_install
    rec = spack.installed_db.get_record(self.spec)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/database.py", line 79, in converter
    return function(self, spec_like, *args, **kwargs)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/database.py", line 527, in get_record
    key = self._get_matching_spec_key(spec, **kwargs)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/database.py", line 519, in _get_matching_spec_key
    match = self.query_one(spec, **kwargs)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/database.py", line 653, in query_one
    assert len(concrete_specs) <= 1
AssertionError

@alalazo
Copy link
Copy Markdown
Member

alalazo commented Oct 6, 2016

@tgamblin I'll have a chance to test this tomorrow...

@tgamblin
Copy link
Copy Markdown
Member Author

tgamblin commented Oct 6, 2016

@adamjstewart: can you attach output of spack debug create-db-tarball?

@adamjstewart
Copy link
Copy Markdown
Member

Um, I'm not sure if I'm doing this right...

$ spack debug create-db-tarball > create-db-tarball.txt
tar (child): /blues/gpfs/home/software/spack-0.9.1/spack-db.features/db-locking.ca5f5ee.2016-10-06-160940.tar.gz: Cannot open: No such file or directory
tar (child): Error is not recoverable: exiting now
/bin/tar: /blues/gpfs/home/software/spack-0.9.1/spack-db.features/db-locking.ca5f5ee.2016-10-06-160940.tar.gz: Wrote only 4096 of 10240 bytes
/bin/tar: Error is not recoverable: exiting now
==> Error: Command exited with status 2:

@alalazo
Copy link
Copy Markdown
Member

alalazo commented Oct 7, 2016

@tgamblin I just installed in parallel :

spack install -j 1 hdf5+mpi ^ mpich
spack install -j 1 cgal
spack install -j 1 [email protected]+icu+python+graph

on a Ubuntu 14.04 laptop with [email protected] : everything went well and couldn't see the error @adamjstewart reported.

@tgamblin
Copy link
Copy Markdown
Member Author

But, I tried following my steps to reproduce the bug and it didn't occur this time, so that's promising!

That is promising. I think your error might be coming from something else -- I just want to confirm with the DB test data.

@adamjstewart
Copy link
Copy Markdown
Member

@tgamblin Good catch! spack debug create-db-tarball works for me again.

Although my steps to reproduce the bug can no longer reproduce the bug on my laptop (macOS), I'm still seeing a problem on our CentOS 6 cluster. Here is the output:

$ spack install gmp %pgi
==> Installing gmp
==> Installing m4
==> libsigsegv is already installed in /blues/gpfs/home/software/spack-0.9.1/opt/spack/linux-centos6-x86_64/pgi-16.5-0/libsigsegv-2.10-3zrk2bhqrgknreumylyvff2tzpqlrt2d
Traceback (most recent call last):
  File "/soft/spack-0.9.1/bin/spack", line 192, in <module>
    main()
  File "/soft/spack-0.9.1/bin/spack", line 169, in main
    return_val = command(parser, args)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/cmd/install.py", line 97, in install
    explicit=True)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/package.py", line 1007, in do_install
    dirty=dirty)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/package.py", line 1007, in do_install
    dirty=dirty)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/package.py", line 985, in do_install
    rec = spack.installed_db.get_record(self.spec)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/database.py", line 79, in converter
    return function(self, spec_like, *args, **kwargs)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/database.py", line 527, in get_record
    key = self._get_matching_spec_key(spec, **kwargs)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/database.py", line 519, in _get_matching_spec_key
    match = self.query_one(spec, **kwargs)
  File "/blues/gpfs/home/software/spack-0.9.1/lib/spack/spack/database.py", line 653, in query_one
    assert len(concrete_specs) <= 1
AssertionError

And here is my debug tarball:
spack-db.features-db-locking.842c36c.2016-10-10-175746.tar.gz

@adamjstewart
Copy link
Copy Markdown
Member

Aside from the obvious OS difference, the biggest difference is that on my laptop, I didn't have anything installed before trying out your branch, whereas on our cluster, I had 460 packages already installed (mostly duplicates). That assert statement ought to shed some light on the problem though.

alalazo and others added 15 commits October 11, 2016 01:38
…e about

A use case where the previous approach was failing is :

 - more than one spack process running on compute nodes
 - stage directory is a link to fast LOCAL storage

 In this case the processes may try to unlink something that is "dead" for them, but actually used by other processes on storage they cannot see.
- Make sure we write, truncate, flush when setting PID and owning host in
  the file.
- Locks will now create enclosing directories and touch the lock file
  automatically.
- Closing and re-opening to upgrade to write will lose all existing read
  locks on this process.
  - If we didn't allow ranges, sleeping until no reads would work.
  - With ranges, we may never be able to take some legal write locks
    without invalidating all reads. e.g., if a write lock has distinct
    range from all reads, it should just work, but we'd have to close the
    file, reopen, and re-take reads.

- It's easier to just check whether the file is writable in the first
  place and open for writing from the start.

- Lock now only opens files read-only if we *can't* write them.
- Locks now use fcntl range locks on a single file.

How it works for prefixes:

- Each lock is a byte range lock on the nth byte of a file.

- The lock file is ``spack.installed_db.prefix_lock`` -- the DB tells us
  what to call it and it lives alongside the install DB.  n is the
  sys.maxsize-bit prefix of the DAG hash.

For stages, we take the sha1 of the stage name and use that to select a
byte to lock.

With 100 concurrent builds, the likelihood of a false lock collision is
~5.36e-16, so this scheme should retain more than sufficient paralellism
(with no chance of false negatives), and get us reader-writer lock
semantics with a single file, so no need to clean up lots of lock files.
- Fix a bug handling '/' characters in branch names.

- Make tarballs use a descriptive name for the top-level directory, not
  just `opt`.
@tgamblin
Copy link
Copy Markdown
Member Author

@adamjstewart: I looked into this. The cause for your issue is separate from what this issue is trying to fix so I made a separate issue for it (#1992) and threw it in the 0.10 release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No such spec in database error

4 participants