From 3b72d50936ca6c01b1b6612999da74715a468aa2 Mon Sep 17 00:00:00 2001 From: Travis Hobrla Date: Tue, 10 Nov 2015 13:23:19 -0800 Subject: [PATCH] Fall back to credential refresh on EDEADLK Fixes https://github.com/google/oauth2client/issues/335 This change fixes a bug where multiple threads and/or processes using multistore_file to access the same backing store could raise IOError errno.EDEADLK to the calling application. Since EDEADLK is a possibility with concurrent access, this change instead causes a fallback to read only mode and refresh credentials if necessary. --- oauth2client/multistore_file.py | 3 +++ tests/test_multistore_file.py | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 tests/test_multistore_file.py diff --git a/oauth2client/multistore_file.py b/oauth2client/multistore_file.py index f5a8593..5a12797 100644 --- a/oauth2client/multistore_file.py +++ b/oauth2client/multistore_file.py @@ -290,6 +290,9 @@ class _MultiStore(object): elif e.errno == errno.ENOLCK: logger.warn('File system is out of resources for writing the ' 'credentials file (is your disk full?).') + elif e.errno == errno.EDEADLK: + logger.warn('Lock contention on multistore file, opening ' + 'in read-only mode.') else: raise if not self._file.is_locked(): diff --git a/tests/test_multistore_file.py b/tests/test_multistore_file.py new file mode 100644 index 0000000..1e8c501 --- /dev/null +++ b/tests/test_multistore_file.py @@ -0,0 +1,43 @@ +"""Unit tests for oauth2client.multistore_file.""" + +import errno +import os +import tempfile +import unittest + +from oauth2client import multistore_file + + +class _MockLockedFile(object): + + def __init__(self, filename_str, error_code): + self.filename_str = filename_str + self.error_code = error_code + self.open_and_lock_called = False + + def open_and_lock(self): + self.open_and_lock_called = True + raise IOError(self.error_code, '') + + def is_locked(self): + return False + + def filename(self): + return self.filename_str + + +class MultistoreFileTests(unittest.TestCase): + + def test_lock_file_raises_ioerror(self): + filehandle, filename = tempfile.mkstemp() + os.close(filehandle) + + try: + for error_code in (errno.EDEADLK, errno.ENOSYS, errno.ENOLCK): + multistore = multistore_file._MultiStore(filename) + multistore._file = _MockLockedFile(filename, error_code) + # Should not raise even though the underlying file class did. + multistore._lock() + self.assertTrue(multistore._file.open_and_lock_called) + finally: + os.unlink(filename)