summaryrefslogtreecommitdiff
path: root/xmake/core/base/thread.lua
diff options
context:
space:
mode:
Diffstat (limited to 'xmake/core/base/thread.lua')
-rw-r--r--xmake/core/base/thread.lua264
1 files changed, 264 insertions, 0 deletions
diff --git a/xmake/core/base/thread.lua b/xmake/core/base/thread.lua
new file mode 100644
index 000000000..bdf1c0380
--- /dev/null
+++ b/xmake/core/base/thread.lua
@@ -0,0 +1,264 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author ruki
+-- @file thread.lua
+--
+
+-- define module
+local thread = thread or {}
+local _instance = _instance or {}
+
+-- load modules
+local io = require("base/io")
+local libc = require("base/libc")
+local bytes = require("base/bytes")
+local table = require("base/table")
+local string = require("base/string")
+local sandbox = require("sandbox/sandbox")
+
+-- the thread status
+thread.STATUS_READY = 1
+thread.STATUS_RUNNING = 2
+thread.STATUS_SUSPENDED = 3
+thread.STATUS_DEAD = 4
+
+-- new a thread
+function _instance.new(callback, opt)
+ opt = opt or {}
+ local instance = table.inherit(_instance)
+ instance._NAME = opt.name or "anonymous"
+ instance._ARGV = opt.argv
+ instance._CALLBACK = callback
+ instance._STACKSIZE = opt.stacksize or 0
+ instance._STATUS = thread.STATUS_READY
+ setmetatable(instance, _instance)
+ return instance
+end
+
+-- get thread name
+function _instance:name()
+ return self._NAME
+end
+
+-- get cdata of thread
+function _instance:cdata()
+ return self._HANLDE
+end
+
+-- get thread status
+function _instance:status()
+ return self._STATUS
+end
+
+-- is ready?
+function _instance:is_ready()
+ return self:status() == thread.STATUS_READY
+end
+
+-- is running?
+function _instance:is_running()
+ return self:status() == thread.STATUS_RUNNING
+end
+
+-- is suspended?
+function _instance:is_suspended()
+ return self:status() == thread.STATUS_SUSPENDED
+end
+
+-- is dead?
+function _instance:is_dead()
+ return self:status() == thread.STATUS_DEAD
+end
+
+-- start thread
+function _instance:start()
+ if not self:is_ready() then
+ return nil, string.format("%s: cannot start non-ready thread!", self)
+ end
+ assert(not self:cdata())
+
+ -- serialize and pass callback and arguments to this thread
+ -- we do not use string.serialize to serialize callback, because it's slower (deserialize)
+ -- and we cannot strip function debug info, we need to reserve _ENV, and other upvalue names
+ local callback = string._dump(self._CALLBACK)
+ local callinfo = {name = self:name(), argv = self._ARGV}
+ callinfo = string.serialize(callinfo, {strip = true, indent = false})
+
+ -- init and start thread
+ local handle, errors = thread.thread_init(self:name(), callback, callinfo, self._STACKSIZE)
+ if not handle then
+ return nil, errors or string.format("%s: failed to create thread!", self)
+ end
+
+ self._HANLDE = handle
+ self._STATUS = thread.STATUS_RUNNING
+ return true
+end
+
+-- suspend thread
+function _instance:suspend()
+ if not self:is_running() then
+ return nil, string.format("%s: cannot suspend non-running thread!", self)
+ end
+ assert(self:cdata())
+
+ local ok, errors = thread.thread_suspend(self:cdata())
+ if not ok then
+ return nil, errors or string.format("%s: failed to suspend thread!", self)
+ end
+
+ self._STATUS = thread.STATUS_SUSPENDED
+ return true
+end
+
+-- resume thread
+function _instance:resume()
+ if not self:is_suspended() then
+ return nil, string.format("%s: cannot suspend non-suspended thread!", self)
+ end
+ assert(self:cdata())
+
+ local ok, errors = thread.thread_resume(self:cdata())
+ if not ok then
+ return nil, errors or string.format("%s: failed to resume thread!", self)
+ end
+
+ self._STATUS = thread.STATUS_RUNNING
+ return true
+end
+
+-- wait thread
+function _instance:wait(timeout)
+ if self:is_dead() then
+ return 1
+ elseif self:is_ready() then
+ return -1, string.format("%s: cannot wait ready thread!", self)
+ end
+ assert(self:cdata())
+
+ local ok, errors = thread.thread_wait(self:cdata(), timeout)
+ if ok < 0 then
+ return -1, errors or string.format("%s: failed to resume thread!", self)
+ end
+
+ if ok > 0 then
+ self._STATUS = thread.STATUS_DEAD
+ end
+ return ok
+end
+
+-- tostring(thread)
+function _instance:__tostring()
+ local status_strs = self._STATUS_STRS
+ if not status_strs then
+ status_strs = {
+ [thread.STATUS_READY] = "ready",
+ [thread.STATUS_RUNNING] = "running",
+ [thread.STATUS_SUSPENDED] = "suspended",
+ [thread.STATUS_DEAD] = "dead"
+ }
+ self._STATUS_STRS = status_strs
+ end
+ return string.format("<thread: %s/%s>", self:name(), status_strs[self:status()])
+end
+
+-- gc(thread)
+function _instance:__gc()
+ if self:cdata() and self:is_dead() and io.thread_exit(self:cdata()) then
+ self._HANLDE = nil
+ 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"
+ end
+ return _instance.new(callback, opt)
+end
+
+-- get the running thread name
+function thread.running()
+ return thread._RUNNING
+end
+
+-- run thread
+function thread._run_thread(callback_str, callinfo_str)
+
+ -- load callback info
+ local callinfo
+ local argv
+ local threadname
+ if callinfo_str then
+ local result, errors = string.deserialize(callinfo_str)
+ if not result then
+ return false, string.format("invalid thread callinfo, %s!", errors or "unknown")
+ end
+ callinfo = result
+ if callinfo then
+ argv = callinfo.argv
+ threadname = callinfo.name
+ end
+ end
+
+ -- load callback
+ local callback
+ local fenvs = {}
+ if callback_str then
+ local script, errors = load(callback_str, "=(thread)", "b", fenvs)
+ if not script then
+ return false, string.format("cannot load thread(%s) callback, %s!", threadname or "unknown", errors or "unknown")
+ end
+ for i = 1, math.huge do
+ local upname, upvalue = debug.getupvalue(script, i)
+ if upname == nil or upname == "" then
+ break
+ end
+ if upvalue == nil then
+ return false, string.format("we cannot access upvalue(%s) in thread(%s) callback!", upname, threadname or "unknown")
+ end
+ end
+ callback = script
+ end
+ if not callback then
+ return false, "no thread callback"
+ end
+
+ -- bind sandbox
+-- local sandbox_inst, errors = sandbox.new(callback, {
+-- filter = interp:filter(), rootdir = interp:rootdir(), namespace = interp:namespace()})
+ local sandbox_inst, errors = sandbox.new(callback)
+ if not sandbox_inst then
+ return false, errors
+ end
+
+ -- save the running thread name
+ thread._RUNNING = threadname
+
+ -- do callback
+ return sandbox.load(sandbox_inst:script(), table.unpack(argv or {}))
+end
+
+-- return module
+return thread
+