diff --git a/lua/frecency/file_lock.lua b/lua/frecency/file_lock.lua index ed4cb05..2294f4b 100644 --- a/lua/frecency/file_lock.lua +++ b/lua/frecency/file_lock.lua @@ -9,13 +9,14 @@ local FileLock = {} ---@class FrecencyFileLockConfig ---@field retry integer default: 5 +---@field unlink_retry integer default: 5 ---@field interval integer default: 500 ---@param path string ---@param opts FrecencyFileLockConfig? ---@return FrecencyFileLock FileLock.new = function(path, opts) - local config = vim.tbl_extend("force", { retry = 5, interval = 500 }, opts or {}) + local config = vim.tbl_extend("force", { retry = 5, unlink_retry = 5, interval = 500 }, opts or {}) local self = setmetatable({ config = config }, { __index = FileLock }) self.filename = path .. ".lock" return self @@ -25,6 +26,7 @@ end ---@return string? err function FileLock:get() local count = 0 + local unlink_count = 0 local err, fd while true do count = count + 1 @@ -33,9 +35,17 @@ function FileLock:get() break end async.util.sleep(self.config.interval) - if count == self.config.retry then - log.debug(("file_lock get() failed: retry count reached: %d"):format(count)) - return "failed to get lock" + if count >= self.config.retry then + log.debug(("file_lock get(): retry count reached. try to delete the lock file: %d"):format(count)) + err = async.uv.fs_unlink(self.filename) + if err then + log.debug("file_lock get() failed: " .. err) + unlink_count = unlink_count + 1 + if unlink_count >= self.config.unlink_retry then + log.error("file_lock get(): failed to unlink the lock file: " .. err) + return "failed to get lock" + end + end end log.debug(("file_lock get() retry: %d"):format(count)) end diff --git a/lua/frecency/tests/file_lock_spec.lua b/lua/frecency/tests/file_lock_spec.lua index c6eba26..34c0d24 100644 --- a/lua/frecency/tests/file_lock_spec.lua +++ b/lua/frecency/tests/file_lock_spec.lua @@ -1,3 +1,4 @@ +---@diagnostic disable: undefined-field local FileLock = require "frecency.file_lock" local util = require "frecency.tests.util" local async = require "plenary.async" --[[@as PlenaryAsync]] @@ -10,6 +11,18 @@ local function with_dir(f) close() end +local function with_unlink_fails(f) + return function() + local original = async.uv.fs_unlink + ---@diagnostic disable-next-line: duplicate-set-field + async.uv.fs_unlink = function() + return "overwritten" + end + f() + async.uv.fs_unlink = original + end +end + a.describe("file_lock", function() a.describe("get()", function() a.describe("when no lock file", function() @@ -24,9 +37,11 @@ a.describe("file_lock", function() a.describe("when with a lock file", function() with_dir(function(filename) local fl = FileLock.new(filename, { retry = 1, interval = 10 }) - a.it("fails to get", function() - assert.is.Nil(async.uv.fs_open(fl.filename, "wx", tonumber("600", 8))) - assert.are.same("failed to get lock", fl:get()) + a.it("gets successfully", function() + local err, fd = async.uv.fs_open(fl.filename, "wx", tonumber("600", 8)) + assert.is.Nil(err) + assert.is.Nil(async.uv.fs_close(fd)) + assert.is.Nil(fl:get()) end) end) end) @@ -34,12 +49,25 @@ a.describe("file_lock", function() a.describe("when getting twice", function() with_dir(function(filename) local fl = FileLock.new(filename, { retry = 1, interval = 10 }) - a.it("fails to get", function() + a.it("gets successfully", function() + assert.is.Nil(fl:get()) assert.is.Nil(fl:get()) - assert.are.same("failed to get lock", fl:get()) end) end) end) + + a.describe("when getting twice but unlink fails", function() + with_dir(function(filename) + local fl = FileLock.new(filename, { retry = 1, interval = 10 }) + a.it( + "fails to get", + with_unlink_fails(function() + assert.is.Nil(fl:get()) + assert.are.same("failed to get lock", fl:get()) + end) + ) + end) + end) end) a.describe("release()", function() @@ -78,15 +106,18 @@ a.describe("file_lock", function() a.describe("when get() fails", function() with_dir(function(filename) local fl = FileLock.new(filename, { retry = 1, interval = 10 }) - a.it("fails with a valid err", function() - assert.is.Nil(fl:get()) - assert.are.same( - "failed to get lock", - fl:with(function() - return nil - end) - ) - end) + a.it( + "fails with a valid err", + with_unlink_fails(function() + assert.is.Nil(fl:get()) + assert.are.same( + "failed to get lock", + fl:with(function() + return nil + end) + ) + end) + ) end) end)