-
Notifications
You must be signed in to change notification settings - Fork 19
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
DM-44462: Try to trap case where another eups process deletes the db file #152
Changes from all commits
d3ee482
dc7e472
07da1c7
d832a92
9b91d84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,7 @@ | |
# considered a global tag. | ||
userPrefix = "user:" | ||
|
||
dotre = re.compile(r'\.') | ||
persistVersionNameNoDot = persistVersionName.replace(".", "_") | ||
who = utils.getUserName() | ||
|
||
class ProductStack: | ||
|
@@ -47,13 +47,13 @@ class ProductStack: | |
persistVersion = persistVersionName | ||
|
||
# static variable: name of file extension to use to persist data | ||
persistFileExt = "pickleDB%s" % dotre.sub('_', persistVersionName) | ||
persistFileExt = f"pickleDB{persistVersionNameNoDot}" | ||
|
||
# static variable: regexp for cache file names | ||
persistFileRe = re.compile(r'^(\w\S*)\.%s$' % persistFileExt) | ||
persistFileRe = re.compile(rf'^(\w\S*)\.{persistFileExt}$') | ||
|
||
# static variable: name of file extension to use to persist data | ||
userTagFileExt = "pickleTag%s" % dotre.sub('_', persistVersionName) | ||
userTagFileExt = f"pickleTag{persistVersionNameNoDot}" | ||
|
||
def __init__(self, dbpath, persistDir=None, autosave=True): | ||
""" | ||
|
@@ -252,8 +252,15 @@ def save(self, flavors=None, dir=None): | |
raise CacheOutOfSync(outofsync) | ||
|
||
def _cacheFileIsInSync(self, file): | ||
return (file not in self.modtimes or | ||
os.stat(file).st_mtime <= self.modtimes[file]) | ||
if file not in self.modtimes: | ||
return True | ||
try: | ||
older = os.stat(file).st_mtime <= self.modtimes[file] | ||
except FileNotFoundError: | ||
# File must have been deleted by other eups process. | ||
del self.modtimes[file] | ||
return True | ||
return older | ||
|
||
def cacheIsInSync(self, flavors=None): | ||
""" | ||
|
@@ -300,9 +307,9 @@ def persist(self, flavor, file=None): | |
self.lookup[flavor] = {} | ||
flavorData = self.lookup[flavor] | ||
|
||
fd = utils.AtomicFile(file, "wb") | ||
pickle.dump(flavorData, fd, protocol=2) | ||
fd.close() | ||
with utils.AtomicFile(file, "wb") as fd: | ||
pickle.dump(flavorData, fd, protocol=2) | ||
# This could fail if another process deleted the file immediately. | ||
self.modtimes[file] = os.stat(file).st_mtime | ||
|
||
def export(self): | ||
|
@@ -627,7 +634,10 @@ def cacheIsUpToDate(self, flavor, cacheDir=None): | |
return False | ||
|
||
# get the modification time of the cache file | ||
cache_mtime = os.stat(cache).st_mtime | ||
try: | ||
cache_mtime = os.stat(cache).st_mtime | ||
except FileNotFoundError: | ||
return False | ||
|
||
# check for user tag updates | ||
if cacheDir != self.dbpath and \ | ||
|
@@ -660,7 +670,11 @@ def clearCache(self, flavors=None, cachedir=None, verbose=0): | |
if os.path.exists(fileName): | ||
if verbose > 0: | ||
print("Deleting %s" % (fileName), file=sys.stderr) | ||
os.remove(fileName) | ||
try: | ||
os.remove(fileName) | ||
except FileNotFoundError: | ||
# Some other process deleted the file. | ||
pass | ||
|
||
def reload(self, flavors=None, persistDir=None, verbose=0): | ||
""" | ||
|
@@ -690,9 +704,8 @@ def reload(self, flavors=None, persistDir=None, verbose=0): | |
for flavor in flavors: | ||
fileName = self._persistPath(flavor,persistDir) | ||
self.modtimes[fileName] = os.stat(fileName).st_mtime | ||
fd = open(fileName, "rb") | ||
lookup = pickle.load(fd) | ||
fd.close() | ||
with open(fileName, "rb") as fd: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code is littered with the old style opens. I have only fixed the one that was in the same file as I was editing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what I'm supposed to do about the race in this code. Maybe it's a bit early in the day for me but that code gets the file name from the flavor, calls stat without checking if the cache file is there, then tries to open it. I think the code is assuming that the |
||
lookup = pickle.load(fd) | ||
|
||
self.lookup[flavor] = lookup | ||
|
||
|
@@ -886,4 +899,3 @@ def __init__(self, files=None, flavors=None, maxsave=None, msg=None): | |
self.files = files | ||
self.flavors = flavors | ||
self.maxsave = maxsave | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seem to be a number of similar
os.stat()
calls. They don't need protection as well?It's curious that the only thing that seems to remove this file is an admin command.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will take a look at the other places.
I was also wondering how the file disappears even in the case where 30 EUPS processes in parallel are trying to read the same database file. The atomic rename should not result in the file disappearing so I realize that this is papering over the cracks a little. @RobertLuptonTheGood do you know of a way that the pickle file could be deleted in normal usage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a couple more traps but
reload
is a problem.