# Copyright 2012 the rootpy developers
# distributed under the terms of the GNU General Public License
from __future__ import absolute_import

import os
import stat
import time
import platform
from contextlib import contextmanager

from ..extern.lockfile import LockFile, LockTimeout
from . import log; log = log[__name__]

__all__ = [
    'lock',
]


@contextmanager
def lock(path, poll_interval=5, max_age=60):
    """
    Aquire a file lock in a thread-safe manner that also reaps stale locks
    possibly left behind by processes that crashed hard.
    """
    if poll_interval >= max_age:
        raise ValueError("`poll_interval` must be less than `max_age`")
    proc = '{0:d}@{1}'.format(os.getpid(), platform.node())
    lock = LockFile(path)
    log.debug("{0} attempting to lock {1}".format(proc, path))
    while not lock.i_am_locking():
        if lock.is_locked():
            # check age of the lock file
            age = time.time() - os.stat(lock.lock_file)[stat.ST_MTIME]
            # break the lock if too old (considered stale)
            if age > max_age:
                log.warning(
                    "{0} breaking exiting lock on {1} "
                    "that is {2:d} seconds old".format(
                        proc, path, int(age)))
                lock.break_lock()
        try:
            log.debug(
                "{0} waiting for {1:d} seconds "
                "for lock on {2} to be released".format(
                    proc, poll_interval, path))
            lock.acquire(timeout=poll_interval)
        except LockTimeout:
            pass
    log.debug("{0} locked {1}".format(proc, path))
    yield lock
    lock.release()
    log.debug("{0} released lock on {1}".format(proc, path))

@@ -550,24 +550,207 @@
 rd.%0A    %22%22%22%0A
+    if max_age %3C 30:%0A        raise ValueError(%22%60max_age%60 must be at least 30 seconds%22)%0A    if poll_interval %3C 1:%0A        raise ValueError(%22%60poll_interval%60 must be at least 1 second%22)%0A
     if poll_
@@ -762,32 +762,32 @@
 val %3E= max_age:%0A
-
         raise Va
@@ -1069,17 +1069,83 @@
       # 
-c
+Protect against race condition%0A            try:%0A                # C
 heck age
@@ -1162,16 +1162,20 @@
 ck file%0A
+    
         
@@ -1253,11 +1253,15 @@
     
+   
+ 
 # 
-b
+B
 reak
@@ -1312,16 +1312,20 @@
         
+    
 if age %3E
@@ -1354,62 +1354,659 @@
     
-log.warning(%0A                    %22%7B0%7D breaking exiting
+    lock.break_lock()%0A                    # What if lock was released and reacquired in the meantime?%0A                    # We don't want to break a fresh lock!%0A                    # If a lock is stale then we may have many threads%0A                    # attempting to break it here at the %22same time%22.%0A                    # Avoid the possibility of some thread trying to break the%0A                    # lock after it has already been broken and after the first%0A                    # other thread attempting to acquire the lock by sleeping%0A                    # for 0.5 seconds below.%0A                    log.warning(%0A                        %22%7B0%7D broke
  loc
@@ -2036,16 +2036,20 @@
         
+    
 %22that is
@@ -2096,24 +2096,28 @@
             
+    
 proc, path, 
@@ -2139,36 +2139,258 @@
         
-    lock.break_lock(
+except OSError:%0A                # Lock was released just now%0A                # os.path.exists(lock.lock_file) is False%0A                # OSError may be raised by os.stat() or lock.break_lock() above%0A                pass%0A        time.sleep(0.5
 )%0A      
@@ -2569,24 +2569,91 @@
 val, path))%0A
+            # Use float() here since acquire sleeps for timeout/10%0A
             
@@ -2673,16 +2673,22 @@
 timeout=
+float(
 poll_int
@@ -2693,16 +2693,17 @@
 nterval)
+)
 %0A       

fix race condition in utils.lock