diff options
| author | ruki <[email protected]> | 2025-11-04 00:50:28 +0800 |
|---|---|---|
| committer | ruki <[email protected]> | 2025-11-07 15:01:56 +0800 |
| commit | 456c2102a66ceee1fdc05fc149ce724b8aad7c8d (patch) | |
| tree | 311f3e4878c4fc229c8424920f91700f1deda8bc | |
| parent | 4db91f0a96177559363877207357c51485221546 (diff) | |
improve thread
| -rw-r--r-- | xmake/core/base/private/async_task.lua | 130 | ||||
| -rw-r--r-- | xmake/core/base/thread.lua | 137 |
2 files changed, 205 insertions, 62 deletions
diff --git a/xmake/core/base/private/async_task.lua b/xmake/core/base/private/async_task.lua index 676f2b226..9cd51926e 100644 --- a/xmake/core/base/private/async_task.lua +++ b/xmake/core/base/private/async_task.lua @@ -39,6 +39,8 @@ local task_mutex = nil -- the asynchronous task loop function async_task._loop(event, queue, mutex, is_stopped, is_diagnosis) local os = require("base/os") + local try = require("sandbox/modules/try") + local thread = require("base/thread") local function dprint(...) if is_diagnosis then @@ -46,6 +48,17 @@ function async_task._loop(event, queue, mutex, is_stopped, is_diagnosis) end end + -- restore thread objects from serialized format + local function _restore_thread_objects(cmd) + -- use thread helper to deserialize thread objects from queue data + if cmd.event_data then + cmd.event = thread._deserialize_object(cmd.event_data) + end + if cmd.result_data then + cmd.result = thread._deserialize_object(cmd.result_data) + end + end + local function _runcmd_cp(cmd) os.cp(cmd.srcpath, cmd.dstpath) end @@ -61,16 +74,39 @@ function async_task._loop(event, queue, mutex, is_stopped, is_diagnosis) rmdir = _runcmd_rmdir } local function _runcmd(cmd) + local ok = true + local errors + + -- restore thread objects if needed + _restore_thread_objects(cmd) + local runop = runops[cmd.kind] if runop then - runop(cmd) + try + { + function () + runop(cmd) + end, + catch + { + function (errs) + ok = false + errors = tostring(errs) + end + } + } + end + -- notify completion if event is provided + if cmd.event and cmd.result then + cmd.result:set({ok = ok, errors = errors}) + cmd.event:post() end end dprint("async_task: started") while not is_stopped:get() do if event:wait(-1) > 0 then - + -- fetch all tasks from queue at once local cmds = {} mutex:lock() @@ -150,25 +186,45 @@ function async_task.cp(srcpath, dstpath, opt) return false, errors end - -- TODO - assert(opt.detach) - -- post task srcpath = path.absolute(tostring(srcpath)) dstpath = path.absolute(tostring(dstpath)) + + local cmd = {kind = "cp", srcpath = srcpath, dstpath = dstpath} + local cmd_event, cmd_result + + -- create event and result for non-detach mode + if not opt.detach then + cmd_event = thread.event() + cmd_result = thread.sharedata() + + -- serialize thread objects for passing to worker thread + cmd.event_data = thread._serialize_object(cmd_event) + cmd.result_data = thread._serialize_object(cmd_result) + end + task_mutex:lock() - task_queue:push({kind = "cp", srcpath = srcpath, dstpath = dstpath}) + task_queue:push(cmd) local queue_size = task_queue:size() task_mutex:unlock() + if opt.detach then -- We cache some tasks before executing them to avoid frequent thread switching. if queue_size > 10 then task_event:post() end + return true else + -- wait for completion task_event:post() + cmd_event:wait(-1) + local result = cmd_result:get() + if result and result.ok then + return true + else + return false, result and result.errors or "unknown error" + end end - return true end -- remove files or directories @@ -179,24 +235,44 @@ function async_task.rm(filepath, opt) return false, errors end - -- TODO - assert(opt.detach) - -- post task filepath = path.absolute(tostring(filepath)) + + local cmd = {kind = "rm", filepath = filepath} + local cmd_event, cmd_result + + -- create event and result for non-detach mode + if not opt.detach then + cmd_event = thread.event() + cmd_result = thread.sharedata() + + -- serialize thread objects for passing to worker thread + cmd.event_data = thread._serialize_object(cmd_event) + cmd.result_data = thread._serialize_object(cmd_result) + end + task_mutex:lock() - task_queue:push({kind = "rm", filepath = filepath}) + task_queue:push(cmd) local queue_size = task_queue:size() task_mutex:unlock() + if opt.detach then -- We cache some tasks before executing them to avoid frequent thread switching. if queue_size > 10 then task_event:post() end + return true else + -- wait for completion task_event:post() + cmd_event:wait(-1) + local result = cmd_result:get() + if result and result.ok then + return true + else + return false, result and result.errors or "unknown error" + end end - return true end -- remove directories @@ -207,24 +283,44 @@ function async_task.rmdir(dir, opt) return false, errors end - -- TODO - assert(opt.detach) - -- post task dir = path.absolute(tostring(dir)) + + local cmd = {kind = "rmdir", dir = dir} + local cmd_event, cmd_result + + -- create event and result for non-detach mode + if not opt.detach then + cmd_event = thread.event() + cmd_result = thread.sharedata() + + -- serialize thread objects for passing to worker thread + cmd.event_data = thread._serialize_object(cmd_event) + cmd.result_data = thread._serialize_object(cmd_result) + end + task_mutex:lock() - task_queue:push({kind = "rmdir", dir = dir}) + task_queue:push(cmd) local queue_size = task_queue:size() task_mutex:unlock() + if opt.detach then -- We cache some tasks before executing them to avoid frequent thread switching. if queue_size > 10 then task_event:post() end + return true else + -- wait for completion task_event:post() + cmd_event:wait(-1) + local result = cmd_result:get() + if result and result.ok then + return true + else + return false, result and result.errors or "unknown error" + end end - return true end -- return module: async_task diff --git a/xmake/core/base/thread.lua b/xmake/core/base/thread.lua index 65e161050..91dd2a64d 100644 --- a/xmake/core/base/thread.lua +++ b/xmake/core/base/thread.lua @@ -103,26 +103,10 @@ function _thread:start() local argv = {} for _, arg in ipairs(self._ARGV) do if type(arg) == "table" then - -- is mutex? we can only pass cdata address - if arg._MUTEX and arg.cdata then - thread.mutex_incref(arg:cdata()) - arg = {mutex = true, name = arg:name(), caddr = libc.dataptr(arg:cdata(), {ffi = false})} - -- is event? we can only pass cdata address - elseif arg._EVENT and arg.cdata then - thread.event_incref(arg:cdata()) - arg = {event = true, name = arg:name(), caddr = libc.dataptr(arg:cdata(), {ffi = false})} - -- is semaphore? we can only pass cdata address - elseif arg._SEMAPHORE and arg.cdata then - thread.semaphore_incref(arg:cdata()) - arg = {semaphore = true, name = arg:name(), caddr = libc.dataptr(arg:cdata(), {ffi = false})} - -- is queue? we can only pass cdata address - elseif arg._QUEUE and arg.cdata then - thread.queue_incref(arg:cdata()) - arg = {queue = true, name = arg:name(), caddr = libc.dataptr(arg:cdata(), {ffi = false})} - -- is sharedata? we can only pass cdata address - elseif arg._SHAREDATA and arg.cdata then - thread.sharedata_incref(arg:cdata()) - arg = {sharedata = true, name = arg:name(), caddr = libc.dataptr(arg:cdata())} + -- try to serialize thread object (mutex, event, semaphore, queue, sharedata) + local serialized = thread._serialize_object(arg) + if serialized then + arg = serialized end end table.insert(argv, arg) @@ -782,23 +766,71 @@ function _sharedata:__gc() end end --- new a thread --- --- @param callback the thread callback --- @param opt the thread options, e.g. {name = "", argv = {}, stacksize = 8192} --- --- @return the thread instance --- -function thread.new(callback, opt) - if callback == nil then - return nil, "invalid thread, callback is nil" +-- serialize thread object for passing through queue or table (private helper) +-- this is used when you need to pass thread objects (mutex, event, semaphore, queue, sharedata) +-- through a queue or embed them in a table +-- returns a table with serialized caddr that can be pushed to queue +function thread._serialize_object(obj) + if not obj or type(obj) ~= "table" or not obj.cdata then + return nil end - return _thread.new(callback, opt) + + local result = {} + -- detect object type by checking internal marker + if obj._MUTEX then + thread.mutex_incref(obj:cdata()) + result.mutex = true + result.name = obj:name() + result.caddr = libc.dataptr(obj:cdata(), {ffi = false}) + elseif obj._EVENT then + thread.event_incref(obj:cdata()) + result.event = true + result.name = obj:name() + result.caddr = libc.dataptr(obj:cdata(), {ffi = false}) + elseif obj._SEMAPHORE then + thread.semaphore_incref(obj:cdata()) + result.semaphore = true + result.name = obj:name() + result.caddr = libc.dataptr(obj:cdata(), {ffi = false}) + elseif obj._QUEUE then + thread.queue_incref(obj:cdata()) + result.queue = true + result.name = obj:name() + result.caddr = libc.dataptr(obj:cdata(), {ffi = false}) + elseif obj._SHAREDATA then + thread.sharedata_incref(obj:cdata()) + result.sharedata = true + result.name = obj:name() + result.caddr = libc.dataptr(obj:cdata(), {ffi = false}) + else + return nil + end + + return result end --- get the running thread name -function thread.running() - return thread._RUNNING +-- deserialize thread object from serialized data (private helper) +-- this is used to restore thread objects (mutex, event, semaphore, queue, sharedata) +-- from serialized caddr received through queue or from table +function thread._deserialize_object(data) + if not data or type(data) ~= "table" or not data.caddr then + return nil + end + + local cdata = libc.ptraddr(data.caddr, {ffi = false}) + if data.mutex then + return _mutex.new(data.name, cdata) + elseif data.event then + return _event.new(data.name, cdata) + elseif data.semaphore then + return _semaphore.new(data.name, cdata) + elseif data.queue then + return _queue.new(data.name, cdata) + elseif data.sharedata then + return _sharedata.new(data.name, cdata) + end + + return nil end -- run thread @@ -861,20 +893,16 @@ function thread._run_thread(callback_str, callinfo_str) -- save the running thread name thread._RUNNING = threadname - -- translate arguments (mutex, ...) + -- translate arguments (mutex, event, semaphore, queue, sharedata, ...) if argv then local newargv = {} for _, arg in ipairs(argv) do - if type(arg) == "table" and arg.mutex and arg.caddr then - arg = _mutex.new(arg.name, libc.ptraddr(arg.caddr, {ffi = false})) - elseif type(arg) == "table" and arg.event and arg.caddr then - arg = _event.new(arg.name, libc.ptraddr(arg.caddr, {ffi = false})) - elseif type(arg) == "table" and arg.semaphore and arg.caddr then - arg = _semaphore.new(arg.name, libc.ptraddr(arg.caddr, {ffi = false})) - elseif type(arg) == "table" and arg.queue and arg.caddr then - arg = _queue.new(arg.name, libc.ptraddr(arg.caddr, {ffi = false})) - elseif type(arg) == "table" and arg.sharedata and arg.caddr then - arg = _sharedata.new(arg.name, libc.ptraddr(arg.caddr, {ffi = false})) + if type(arg) == "table" and arg.caddr then + -- try to deserialize thread object + local obj = thread._deserialize_object(arg) + if obj then + arg = obj + end end table.insert(newargv, arg) end @@ -895,6 +923,25 @@ function thread._run_thread(callback_str, callinfo_str) return ok, errors end +-- new a thread +-- +-- @param callback the thread callback +-- @param opt the thread options, e.g. {name = "", argv = {}, stacksize = 8192} +-- +-- @return the thread instance +-- +function thread.new(callback, opt) + if callback == nil then + return nil, "invalid thread, callback is nil" + end + return _thread.new(callback, opt) +end + +-- get the running thread name +function thread.running() + return thread._RUNNING +end + -- open a mutex function thread.mutex(name) local mutex = thread.mutex_init() |
