8000 Switch to per-file locking. by anntzer · Pull Request #10596 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Switch to per-file locking. #10596

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 6, 2018
Merged

Switch to per-file locking. #10596

merged 1 commit into from
Mar 6, 2018

Conversation

anntzer
Copy link
Contributor
@anntzer anntzer commented Feb 25, 2018

Replace the Locked contextmanager (which locks a directory, preventing
other (cooperative) processes to access it) by a private _lock_path
contextmanager, which locks a single file (or directory).

  • The finer grained lock avoids locking out the entire tex cache when
    handling usetex, which is useful when running multiple processes at
    once.

  • Python3 implements the "x" ("exclusive") mode to open, which we can
    use instead of relying on makedirs to achieve a race-free operation
    on the filesystem.

  • The previous implementation allowed multiple threads of a single
    process to acquire the same lock, but (for the use cases here, e.g.
    running a tex subprocess) this is actually undesirable. Removing this
    behavior also simplifies the implementation.

  • As far as I can tell, the previous implementation was actually racy:
    in

      retries = 50
      sleeptime = 0.1
      while retries:
          files = glob.glob(self.pattern)
          if files and not files[0].endswith(self.end):
              time.sleep(sleeptime)
              retries -= 1
          else:
              break
      else:
          err_str = _lockstr.format(self.pattern)
          raise self.TimeoutError(err_str)
    
      # <----- HERE
    
      if not files:
          try:
              os.makedirs(self.lock_path)
          except OSError:
              pass
      else:  # PID lock already here --- someone else will remove it.
          self.remove = False
    

    multiple processes can reach "HERE" at the same time and each
    successfully create their own lock.

This should mostly mitigate out #7776.

PR Summary

PR Checklist

  • Has Pytest style unit tests
  • Code is PEP 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@anntzer anntzer added this to the v3.0 milestone Feb 25, 2018
@anntzer anntzer force-pushed the filelock branch 3 times, most recently from 5a47356 to 9034b0f Compare February 25, 2018 09:31
@tacaswell
Copy link
Member

Interesting that it has a race condition, this was adapted from the conda source iirc....

@anntzer
Copy link
Contributor Author
anntzer commented Feb 25, 2018

there's even a conda license for it (in the license subdirectory :))

Replace the Locked contextmanager (which locks a directory, preventing
other (cooperative) processes to access it) by a private _lock_path
contextmanager, which locks a single file (or directory).

- The finer grained lock avoids locking out the entire tex cache when
  handling usetex, which is useful when running multiple processes at
  once.

- Python3 implements the `"x"` ("exclusive") mode to open, which we can
  use instead of relying on `makedirs` to achieve a race-free operation
  on the filesystem.

- The previous implementation allowed multiple threads of a single
  process to acquire the same lock, but (for the use cases here, e.g.
  running a tex subprocess) this is actually undesirable.  Removing this
  behavior also simplifies the implementation.

- As far as I can tell, the previous implementation was actually racy:
  in

    retries = 50
    sleeptime = 0.1
    while retries:
        files = glob.glob(self.pattern)
        if files and not files[0].endswith(self.end):
            time.sleep(sleeptime)
            retries -= 1
        else:
            break
    else:
        err_str = _lockstr.format(self.pattern)
        raise self.TimeoutError(err_str)

    # <----- HERE

    if not files:
        try:
            os.makedirs(self.lock_path)
        except OSError:
            pass
    else:  # PID lock already here --- someone else will remove it.
        self.remove = False

  multiple processes can reach "HERE" at the same time and each
  successfully create their own lock.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0