From 29a6220b1ece0fc7132e3c6d1f2aa766296dbf5e Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Sat, 25 Jul 2026 02:53:34 -0700 Subject: [PATCH] [local storage] Remove IPC lock file after releasing the lock fasteners.InterProcessLock creates its lock file but never removes it, and LockLocalStorage did not remove it either. Because the file name is derived from a sha256 of the locked path, one file was left behind in the temporary directory for every distinct path ever locked, which can exhaust inodes on long running processes that write many objects. The lock file is now removed after the IPC lock is released. Closes #1975 --- CHANGES.rst | 6 ++++++ libcloud/storage/drivers/local.py | 8 ++++++++ libcloud/test/storage/test_local.py | 12 ++++++++++++ 3 files changed, 26 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 88b983e4bf..717ad2aded 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -37,6 +37,12 @@ Compute Storage ~~~~~~~ +- [Local] Remove the IPC lock file once ``LockLocalStorage`` releases the lock. + Previously ``fasteners`` left the file behind, so one file accumulated in the + temporary directory for every distinct path which was locked. + (GITHUB-1975) + [Sanjay Santhanam - @Sanjays2402] + - [Azure Blobs] Fix ``chunk_size`` argument being ignored by ``download_object_as_stream`` and ``download_object_range_as_stream``. The requested chunk size is now forwarded to the underlying iterator instead of diff --git a/libcloud/storage/drivers/local.py b/libcloud/storage/drivers/local.py index f47d7c0649..9f15ce4dc4 100644 --- a/libcloud/storage/drivers/local.py +++ b/libcloud/storage/drivers/local.py @@ -117,6 +117,14 @@ def __exit__(self, type, value, traceback): if self.ipc_lock.exists(): self.ipc_lock.release() + # NOTE: fasteners doesn't remove the lock file it creates, so + # without this the file accumulates in the temporary directory + # for every distinct path which is ever locked. + try: + os.remove(self.ipc_lock_path) + except OSError: + pass + if value is not None: raise value diff --git a/libcloud/test/storage/test_local.py b/libcloud/test/storage/test_local.py index 7d7f271405..0ff5f42836 100644 --- a/libcloud/test/storage/test_local.py +++ b/libcloud/test/storage/test_local.py @@ -139,6 +139,18 @@ def test_lock_local_storage(self): self.assertEqual(bool(success_1.value), True, "Check didn't pass") self.assertEqual(bool(success_2.value), True, "Second check didn't pass") + @unittest.skipIf(platform.system().lower() == "windows", "Unsupported on Windows") + def test_lock_local_storage_removes_lock_file(self): + # The IPC lock file must not be left behind once the lock is released, + # otherwise it accumulates in the temporary directory for every path + # which is ever locked. + lock = LockLocalStorage("/tmp/lock-file-cleanup") + + with lock: + self.assertTrue(os.path.exists(lock.ipc_lock_path)) + + self.assertFalse(os.path.exists(lock.ipc_lock_path)) + def test_list_containers_empty(self): containers = self.driver.list_containers() self.assertEqual(len(containers), 0)