Skip to content

Commit

Permalink
Merge pull request #4648 from xmake-io/co
Browse files Browse the repository at this point in the history
lock and unlock coroutine #4645
  • Loading branch information
waruqi authored Jan 28, 2024
2 parents f383a6e + 2727f45 commit 1d4a3e7
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 51 deletions.
109 changes: 106 additions & 3 deletions xmake/core/base/scheduler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,29 @@ end

-- resume the given coroutine
function scheduler:co_resume(co, ...)
return coroutine.resume(co:thread(), ...)

-- do resume
local ok, errors = coroutine.resume(co:thread(), ...)

local running = self:co_running()
if running then

-- has the current directory been changed? restore it
local curdir = self._CO_CURDIR_HASH
local olddir = self._CO_CURDIRS and self._CO_CURDIRS[running] or nil
if olddir and curdir ~= olddir[1] then -- hash changed?
os.cd(olddir[2])
end

-- has the current environments been changed? restore it
local curenvs = self._CO_CURENVS_HASH
local oldenvs = self._CO_CURENVS and self._CO_CURENVS[running] or nil
if oldenvs and curenvs ~= oldenvs[1] and running:is_isolated() then -- hash changed?
os.setenvs(oldenvs[2])
end
end

return ok, errors
end

-- suspend the current coroutine
Expand All @@ -443,15 +465,15 @@ function scheduler:co_suspend(...)
-- suspend it
local results = table.pack(coroutine.yield(...))

-- Has the current directory been changed? restore it
-- has the current directory been changed? restore it
local running = assert(self:co_running())
local curdir = self._CO_CURDIR_HASH
local olddir = self._CO_CURDIRS and self._CO_CURDIRS[running] or nil
if olddir and curdir ~= olddir[1] then -- hash changed?
os.cd(olddir[2])
end

-- Has the current environments been changed? restore it
-- has the current environments been changed? restore it
local curenvs = self._CO_CURENVS_HASH
local oldenvs = self._CO_CURENVS and self._CO_CURENVS[running] or nil
if oldenvs and curenvs ~= oldenvs[1] and running:is_isolated() then -- hash changed?
Expand Down Expand Up @@ -499,6 +521,87 @@ function scheduler:co_sleep(ms)
return true
end

-- lock the current coroutine
function scheduler:co_lock(lockname)

-- get the running coroutine
local running = self:co_running()
if not running then
return false, "we must call co_lock() in coroutine with scheduler!"
end

-- is stopped?
if not self._STARTED then
return false, "the scheduler is stopped!"
end

-- do lock
local co_locked_tasks = self._CO_LOCKED_TASKS
if co_locked_tasks == nil then
co_locked_tasks = {}
self._CO_LOCKED_TASKS = co_locked_tasks
end
while true do

-- try to lock it
if co_locked_tasks[lockname] == nil then
co_locked_tasks[lockname] = running
return true
-- has been locked by the current coroutine
elseif co_locked_tasks[lockname] == running then
return true
end

-- register timeout task to timer
local function timer_callback (cancel)
if co_locked_tasks[lockname] == nil then
if running:is_suspended() then
return self:co_resume(running)
end
else
self:_timer():post(timer_callback, 500)
end
return true
end
self:_timer():post(timer_callback, 500)

-- wait
self:co_suspend()
end
return true
end

-- unlock the current coroutine
function scheduler:co_unlock(lockname)

-- get the running coroutine
local running = self:co_running()
if not running then
return false, "we must call co_unlock() in coroutine with scheduler!"
end

-- is stopped?
if not self._STARTED then
return false, "the scheduler is stopped!"
end

-- do unlock
local co_locked_tasks = self._CO_LOCKED_TASKS
if co_locked_tasks == nil then
co_locked_tasks = {}
self._CO_LOCKED_TASKS = co_locked_tasks
end
if co_locked_tasks[lockname] == nil then
return false, string.format("we need to call lock(%s) first before calling unlock(%s)", lockname, lockname)
end
if co_locked_tasks[lockname] == running then
co_locked_tasks[lockname] = nil
else
return false, string.format("unlock(%s) is called in other %s", lockname, running)
end
return true
end

-- get the given coroutine group
function scheduler:co_group(name)
return self._CO_GROUPS and self._CO_GROUPS[name]
Expand Down
16 changes: 16 additions & 0 deletions xmake/core/sandbox/modules/import/core/base/scheduler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@ function sandbox_core_base_scheduler.co_sleep(ms)
end
end

-- lock the current coroutine
function sandbox_core_base_scheduler.co_lock(lockname)
local ok, errors = scheduler:co_lock(lockname)
if not ok then
raise(errors)
end
end

-- unlock the current coroutine
function sandbox_core_base_scheduler.co_unlock(lockname)
local ok, errors = scheduler:co_unlock(lockname)
if not ok then
raise(errors)
end
end

-- get coroutine group with the given name
function sandbox_core_base_scheduler.co_group(name)
return scheduler:co_group(name)
Expand Down
22 changes: 7 additions & 15 deletions xmake/core/sandbox/modules/import/lib/detect/find_program.lua
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ local raise = require("sandbox/modules/raise")
local vformat = require("sandbox/modules/vformat")
local scheduler = require("sandbox/modules/import/core/base/scheduler")

-- globals
local checking = nil

-- do check
function sandbox_lib_detect_find_program._do_check(program, opt)

Expand Down Expand Up @@ -272,16 +269,6 @@ end
-- @endcode
--
function sandbox_lib_detect_find_program.main(name, opt)

-- @note avoid detect the same program in the same time leading to deadlock if running in the coroutine (e.g. ccache)
local coroutine_running = scheduler.co_running()
if coroutine_running then
while checking ~= nil and checking == name do
scheduler.co_yield()
end
end

-- init options
opt = opt or {}

-- init cachekey
Expand All @@ -290,9 +277,15 @@ function sandbox_lib_detect_find_program.main(name, opt)
cachekey = cachekey .. "_" .. opt.cachekey
end

-- @see https://github.com/xmake-io/xmake/issues/4645
-- @note avoid detect the same program in the same time leading to deadlock if running in the coroutine (e.g. ccache)
local lockname = cachekey .. name
scheduler.co_lock(lockname)

-- attempt to get result from cache first
local result = detectcache:get2(cachekey, name)
if result ~= nil and not opt.force then
scheduler.co_unlock(lockname)
return result and result or nil
end

Expand All @@ -309,11 +302,9 @@ function sandbox_lib_detect_find_program.main(name, opt)
end

-- find executable program
checking = coroutine_running and name or nil
profiler:enter("find_program", name)
result = sandbox_lib_detect_find_program._find(name, paths, opt)
profiler:leave("find_program", name)
checking = nil

-- cache result
detectcache:set2(cachekey, name, result and result or false)
Expand All @@ -327,6 +318,7 @@ function sandbox_lib_detect_find_program.main(name, opt)
utils.cprint("checking for %s ... ${color.nothing}${text.nothing}", name)
end
end
scheduler.co_unlock(lockname)
return result
end

Expand Down
22 changes: 7 additions & 15 deletions xmake/core/sandbox/modules/import/lib/detect/find_programver.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ local sandbox = require("sandbox/sandbox")
local raise = require("sandbox/modules/raise")
local scheduler = require("sandbox/modules/import/core/base/scheduler")

-- globals
local checking = nil

-- find program version
--
-- @param program the program
Expand All @@ -56,32 +53,27 @@ local checking = nil
-- @endcode
--
function sandbox_lib_detect_find_programver.main(program, opt)

-- init options
opt = opt or {}

-- @note avoid detect the same program in the same time leading to deadlock if running in the coroutine (e.g. ccache)
local coroutine_running = scheduler.co_running()
if coroutine_running then
while checking ~= nil and checking == program do
scheduler.co_yield()
end
end

-- init cachekey
local cachekey = "find_programver"
if opt.cachekey then
cachekey = cachekey .. "_" .. opt.cachekey
end

-- @see https://github.com/xmake-io/xmake/issues/4645
-- @note avoid detect the same program in the same time leading to deadlock if running in the coroutine (e.g. ccache)
local lockname = cachekey .. program
scheduler.co_lock(lockname)

-- attempt to get result from cache first
local result = detectcache:get2(cachekey, program)
if result ~= nil and not opt.force then
scheduler.co_unlock(lockname)
return result and result or nil
end

-- attempt to get version output info
checking = coroutine_running and program or nil
profiler:enter("find_programver", program)
local ok = false
local outdata = nil
Expand All @@ -96,7 +88,6 @@ function sandbox_lib_detect_find_programver.main(program, opt)
else
ok, outdata = os.iorunv(program, {command or "--version"}, {envs = opt.envs})
end
checking = nil
profiler:leave("find_programver", program)

-- find version info
Expand All @@ -119,6 +110,7 @@ function sandbox_lib_detect_find_programver.main(program, opt)
-- save result
detectcache:set2(cachekey, program, result and result or false)
detectcache:save()
scheduler.co_unlock(lockname)
return result
end

Expand Down
14 changes: 5 additions & 9 deletions xmake/modules/lib/detect/features.lua
Original file line number Diff line number Diff line change
Expand Up @@ -57,29 +57,25 @@ function main(name, opt)
_g._RESULTS = _g._RESULTS or {}
local results = _g._RESULTS

-- @note avoid detect the same program in the same time if running in the coroutine (e.g. ccache)
local coroutine_running = scheduler.co_running()
if coroutine_running then
while _g._checking ~= nil and _g._checking == key do
scheduler.co_yield()
end
end
-- @see https://github.com/xmake-io/xmake/issues/4645
-- @note avoid detect the same program in the same time leading to deadlock if running in the coroutine (e.g. ccache)
scheduler.co_lock(key)

-- get result from the cache first
local result = results[key]
if result ~= nil then
scheduler.co_unlock(key)
return result
end

-- detect.tools.xxx.features(opt)?
_g._checking = coroutine_running and key or nil
local features = import("detect.tools." .. tool.name .. ".features", {try = true})
if features then
result = features(opt)
end
_g._checking = nil

result = result or {}
results[key] = result
scheduler.co_unlock(key)
return result
end
14 changes: 5 additions & 9 deletions xmake/modules/lib/detect/has_flags.lua
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,9 @@ function main(name, flags, opt)
.. (tool.version or "") .. "_" .. (opt.toolkind or "")
.. "_" .. (opt.flagkind or "") .. "_" .. table.concat(opt.sysflags, " ") .. "_" .. opt.flagskey

-- @note avoid detect the same program in the same time if running in the coroutine (e.g. ccache)
local coroutine_running = scheduler.co_running()
if coroutine_running then
while _g._checking ~= nil and _g._checking == key do
scheduler.co_yield()
end
end
-- @see https://github.com/xmake-io/xmake/issues/4645
-- @note avoid detect the same program in the same time leading to deadlock if running in the coroutine (e.g. ccache)
scheduler.co_lock(key)

-- attempt to get result from cache first
local cacheinfo = detectcache:get("lib.detect.has_flags")
Expand All @@ -98,6 +94,7 @@ function main(name, flags, opt)
end
local result = cacheinfo[key]
if result ~= nil and not opt.force then
scheduler.co_unlock(key)
return result
end

Expand All @@ -122,7 +119,6 @@ function main(name, flags, opt)
profiler.enter("has_flags", tool.name, checkflags[1])

-- detect.tools.xxx.has_flags(flags, opt)?
_g._checking = coroutine_running and key or nil
local hasflags = import("detect.tools." .. tool.name .. ".has_flags", {try = true})
local errors = nil
if hasflags then
Expand All @@ -133,7 +129,6 @@ function main(name, flags, opt)
if opt.on_check then
result, errors = opt.on_check(result, errors)
end
_g._checking = nil
result = result or false

-- stop profile
Expand All @@ -156,6 +151,7 @@ function main(name, flags, opt)
cacheinfo[key] = result
detectcache:set("lib.detect.has_flags", cacheinfo)
detectcache:save()
scheduler.co_unlock(key)
return result
end

0 comments on commit 1d4a3e7

Please sign in to comment.