diff --git a/changes.md b/changes.md index d5e01d5..cddb8a8 100644 --- a/changes.md +++ b/changes.md @@ -5,17 +5,82 @@ Update 13.1.0 Bug fixes and some new features (Will upgrade version to 14.0.0 if Added: - Connections:Lock() -- Prevents a connection object form being fired - Connections:Unlock() -- Removes the restriction imposed by conn:Lock() -- - +- new fucntion added to the thread namespace +-- thread.request(THREAD handle,STRING cmd,VARARGS args) -- allows you to push thread requests from outside the running thread! Extremely powerful. +-- thread.exec(FUNCTION func) -- Allows you to push code to run within the thread execution block! +- handle = multi:newThread() now returns a thread handle to interact with the object +-- handle:Pause() +-- handle:Resume() +-- handle:Kill() +- Basic Thread error checking. +When creating a coroutine based thread with multi:newThread(), the library will attempt to scan through the butecode and check for while loops and the use of thread.* This is only done at thread creation. It isn't perfect, but the goal is to print a warning message that the thread may hang the multi library. This also has some flaws, if a thread function calls another function that has code that may hang the program, this quick check does not see that! Perhaps this is something I can tackle in the future. +```lua +package.path="?/init.lua;?.lua;"..package.path +multi = require("multi") +a=0 +multi:newThread("Test",function() + while true do + a=a+1 + end +end) +multi:mainloop() +-- Output: Warning! The thread created: contains a while loop which may not be confugured properly with thread.* If your code seems to hang this may be the reason! +``` Fixed: - Minor bug with multi:newThread() in how names and functions were managed -- Major bug with the system thread handler. Saw healthy threads as dead ones -- +- Major bug with the system thread handler. Saw healthy threads as dead ones +- Major bug the thread scheduler was seen creating a massive amount of 'event' causing memory leaks and hard crashes! This has been fixed by changing how the scheduler opperates. Changed: -- getTasksDetails("t"), the table varaiant, formats threads, and system threads in the same way that tasks are formatted -- +- getTasksDetails("t"), the table varaiant, formats threads, and system threads in the same way that tasks are formatted. Please see below for the format of the task details +- TID has been added to multi objects. They count up from 0 and no 2 objects will have the same number +- thread.hold() -- As part of the memory leaks that I had to fix thread.hold() is slightly different. This change shouldn't impact previous code at all, but thread.hold() can not only return at most 7 arguments! +- You should notice some faster code execution from threads, the changes improve preformance of threads greatly. They are now much faster than before! +# Tasks Details Table format +``` +{ + ["Tasks"] = { + { + ["TID"] = 0, + ["Type"] = scheduler, + ["Name"] = multi.thread, + ["Priority"] = Core, + ["Uptime"] = 6.752 + ["Link"] = tableRef + }, + ... + } , + + ["Systemthreads"] = { + { + ["Uptime"] = 6.752 + ["Link"] = tableRef + ["Name"] = threadname + ["ThreadID"] = 0 + }, + ... + }, + + ["Threads"] = { + { + ["Uptime"] = 6.752 + ["Link"] = tableRef + ["Name"] = threadname + ["ThreadID"] = 0 + }, + ... + }, + + ["ProcessName"] = multi.root, + ["CyclesPerSecondPerTask"] = 3560300, + ["MemoryUsage"] = 1846, in KB returned as a number + ["ThreadCount"] = 1, + ["SystemLoad"] = 0, as a % 100 is max 0 is min + ["PriorityScheme"] = Round-Robin + ["SystemThreadCount"] = 1 +} +``` Update 13.0.0 Added some documentation, and some new features too check it out! ------------- **Quick note** on the 13.0.0 update: diff --git a/multi/init.lua b/multi/init.lua index ad03e8a..abfb7af 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -342,7 +342,17 @@ function multi:getTasksDetails(t) name = " <"..name..">" end count = count + 1 - table.insert(str,{v.Type:sub(1,1):upper()..v.Type:sub(2,-1)..name,multi.Round(os.clock()-v.creationTime,3),self.PriorityResolve[v.Priority],i}) + table.insert(str,{v.Type:sub(1,1):upper()..v.Type:sub(2,-1)..name,multi.Round(os.clock()-v.creationTime,3),self.PriorityResolve[v.Priority],v.TID}) + end + for v,i in pairs(multi.PausedObjects) do + if v.Type~="event" then + local name = v.Name or "" + if name~="" then + name = " <"..name..">" + end + count = count + 1 + table.insert(str,{v.Type:sub(1,1):upper()..v.Type:sub(2,-1)..name,multi.Round(os.clock()-v.creationTime,3),self.PriorityResolve[v.Priority],v.TID}) + end end if count == 0 then table.insert(str,{"Currently no processes running!","","",""}) @@ -369,22 +379,31 @@ function multi:getTasksDetails(t) str = { ProcessName = (self.Name or "Unnamed"), ThreadCount = #multi.scheduler.Threads, - MemoryUsage = math.ceil(collectgarbage("count")).." KB", + MemoryUsage = math.ceil(collectgarbage("count")), PriorityScheme = priorityTable[multi.defaultSettings.priority or 0], SystemLoad = multi.Round(load,2), CyclesPerSecondPerTask = steps, - SystemThreadCount = #multi.SystemThreads + SystemThreadCount = multi.SystemThreads and #multi.SystemThreads or 0 } + str.Tasks = {} + str.PausedTasks = {} str.Threads = {} str.Systemthreads = {} for i,v in pairs(self.Mainloop) do - str[#str+1]={Link = v, Type=v.Type,Name=v.Name,Uptime=os.clock()-v.creationTime,Priority=self.PriorityResolve[v.Priority],TID = i} + table.insert(str.Tasks,{Link = v, Type=v.Type,Name=v.Name,Uptime=os.clock()-v.creationTime,Priority=self.PriorityResolve[v.Priority],TID = v.TID}) + end + for v,i in pairs(multi.PausedObjects) do + if v.Type~="event" then + table.insert(str.Tasks,{Link = v, Type=v.Type,Name=v.Name,Uptime=os.clock()-v.creationTime,Priority=self.PriorityResolve[v.Priority],TID = v.TID}) + end end for i=1,#multi.scheduler.Threads do - table.insert(str.Threads,{Uptime = os.clock()-multi.scheduler.Threads[i].creationTime,Name = multi.scheduler.Threads[i].Name}) + table.insert(str.Threads,{Uptime = os.clock()-multi.scheduler.Threads[i].creationTime,Name = multi.scheduler.Threads[i].Name,Link = multi.scheduler.Threads[i],TID = multi.scheduler.Threads[i].TID}) end - for i=1,#multi.SystemThreads do - table.insert(str.Systemthreads,{Uptime = os.clock()-multi.SystemThreads[i].creationTime,Name = multi.SystemThreads[i].Name}) + if multi.SystemThreads then + for i=1,#multi.SystemThreads do + table.insert(str.Systemthreads,{Uptime = os.clock()-multi.SystemThreads[i].creationTime,Name = multi.SystemThreads[i].Name,Link = multi.SystemThreads[i],TID = multi.SystemThreads[i].count}) + end end return str end @@ -527,6 +546,7 @@ function multi:OnTimerResolved(func) return self end -- Timer stuff done +multi.PausedObjects = {} function multi:Pause() if self.Type=='mainprocess' then multi.print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") @@ -535,6 +555,7 @@ function multi:Pause() local loop = self.Parent.Mainloop for i=1,#loop do if loop[i] == self then + multi.PausedObjects[self] = true table.remove(loop,i) break end @@ -552,6 +573,7 @@ function multi:Resume() else if self.Active==false then table.insert(self.Parent.Mainloop,self) + multi.PausedObjects[self] = nil self.Active=true end end @@ -569,6 +591,7 @@ function multi:Destroy() if self.Parent.Mainloop[i]==self then self.Parent.OnObjectDestroyed:Fire(self) table.remove(self.Parent.Mainloop,i) + self.Destroyed = true break end end @@ -593,6 +616,7 @@ function multi:setName(name) end multi.SetName = multi.setName --Constructors [CORE] +local _tid = 0 function multi:newBase(ins) if not(self.Type=='mainprocess' or self.Type=='process' or self.Type=='queue') then error('Can only create an object on multi or an interface obj') return false end local c = {} @@ -606,6 +630,7 @@ function multi:newBase(ins) c.funcTM={} c.funcTMR={} c.ender={} + c.TID = _tid c.important={} c.Act=function() end c.Parent=self @@ -616,6 +641,7 @@ function multi:newBase(ins) else table.insert(self.Mainloop,c) end + _tid = _tid + 1 return c end function multi:newProcessor(file) @@ -1444,19 +1470,38 @@ if os.getOS()=="windows" then else thread.__CORES=tonumber(io.popen("nproc --all"):read("*n")) end +thread.requests = {} +function thread.request(t,cmd,...) + thread.requests[t.thread] = {cmd,{...}} +end +function thread._Requests() + local t = thread.requests[coroutine.running()] + thread.requests[coroutine.running()] = nil + if t then + local cmd,args = t[1],t[2] + thread[cmd](unpack(args)) + end +end +function thread.exec(func) + func() +end function thread.sleep(n) + thread._Requests() coroutine.yield({"_sleep_",n or 0}) end function thread.hold(n) + thread._Requests() return coroutine.yield({"_hold_",n or function() return true end}) end function thread.skip(n) + thread._Requests() coroutine.yield({"_skip_",n or 0}) end function thread.kill() coroutine.yield({"_kill_",":)"}) end function thread.yeild() + thread._Requests() coroutine.yield({"_sleep_",0}) end function thread.isThread() @@ -1504,20 +1549,52 @@ multi:setDomainName("Threads") multi:setDomainName("Globals") local initT = false local threadCount = 0 +local threadid = 0 function multi:newThread(name,func) local func = func or name if type(name) == "function" then name = "Thread#"..threadCount end + local g=string.dump(func) + if g:find("K") and not g:find(" thread") then + print("Warning! The thread created: <"..name.."> contains a while loop which may not be confugured properly with thread.* If your code seems to hang this may be the reason!") + else + multi.print("Should be safe") + end local c={} c.ref={} c.Name=name c.thread=coroutine.create(func) c.sleep=1 c.Type="thread" + c.TID = threadid c.firstRunDone=false c.timer=multi:newTimer() c.ref.Globals=self:linkDomain("Globals") + c._isPaused = false + function c:isPaused() + return self._isPaused + end + local resumed = false + function c:Pause() + if not self._isPaused then + thread.request(self,"exec",function() + thread.hold(function() + return resumed + end) + resumed = false + self._isPaused = false + end) + self._isPaused = true + end + end + function c:Resume() + resumed = true + end + function c:Kill() + thread.request(self,"kill") + end + c.Destroy = c.Kill function c.ref:send(name,val) ret=coroutine.yield({Name=name,Value=val}) self:syncGlobals(ret) @@ -1551,6 +1628,8 @@ function multi:newThread(name,func) multi.initThreads() end c.creationTime = os.clock() + threadid = threadid + 1 + return c end function multi.initThreads() initT = true @@ -1560,13 +1639,31 @@ function multi.initThreads() self.skip=tonumber(n) or 24 end multi.scheduler.skip=0 - multi.scheduler.counter=0 multi.scheduler.Threads=multi:linkDomain("Threads") multi.scheduler.Globals=multi:linkDomain("Globals") + local holds = {} + local skips = {} + local t0,t1,t2,t3,t4,t5,t6 + local ret multi.scheduler:OnLoop(function(self) - self.counter=self.counter+1 + for i=#skips,1,-1 do + skips[i].pos = skips[i].pos + 1 + if skips[i].count==skips[i].pos then + skips[i].Link.sleep=0 + skips[i]=nil + end + end + for i=#holds,1,-1 do + if holds[i] then + t0,t1,t2,t3,t4,t5,t6 = holds[i].func() + if t0 then + holds[i].Link.sleep = 0 + holds[i].Link.returns = {t0,t1,t2,t3,t4,t5,t6} + holds[i]=nil + end + end + end for i=#self.Threads,1,-1 do - ret={} if coroutine.status(self.Threads[i].thread)=="dead" then table.remove(self.Threads,i) else @@ -1591,7 +1688,7 @@ function multi.initThreads() end if ret==true or ret==false then multi.print("Thread Ended!!!") - ret={} + ret=nil end end if ret then @@ -1603,26 +1700,18 @@ function multi.initThreads() elseif ret[1]=="_skip_" then self.Threads[i].timer:Reset() self.Threads[i].sleep=math.huge - local event=multi:newEvent(function(evnt) return multi.scheduler.counter>=evnt.counter end) - event.link=self.Threads[i] - event.counter=self.counter+ret[2] - event:OnEvent(function(evnt) - evnt.link.sleep=0 - evnt:Destroy() - end):setName("multi.thread.skip") + table.insert(skips,{ + Link = self.Threads[i], + count = ret[2], + pos = 0, + }) elseif ret[1]=="_hold_" then self.Threads[i].timer:Reset() self.Threads[i].sleep=math.huge - local event=multi:newEvent(ret[2]) - event.returns = nil - event.link=self.Threads[i] - event:OnEvent(function(evnt) - evnt.link.sleep=0 - evnt.link.returns = evnt.returns - multi.nextStep(function() - evnt:Destroy() - end) - end):setName("multi.thread.hold") + table.insert(holds,{ + Link = self.Threads[i], + func = ret[2] + }) elseif ret.Name then self.Globals[ret.Name]=ret.Value end diff --git a/test.lua b/test.lua index 6e47353..f2df0fe 100644 --- a/test.lua +++ b/test.lua @@ -1,44 +1,99 @@ package.path="?/init.lua;?.lua;"..package.path multi = require("multi") -local GLOBAL,THREAD = require("multi.integration.lanesManager").init() -nGLOBAL = require("multi.integration.networkManager").init() -function table.print(tbl, indent) - if type(tbl)~="table" then return end - if not indent then indent = 0 end - for k, v in pairs(tbl) do - formatting = string.rep(' ', indent) .. k .. ': ' - if type(v) == 'table' then - print(formatting) - table.print(v, indent+1) - else - print(formatting .. tostring(v)) - end - end -end -print(#multi.SystemThreads) -multi:newThread("Detail Updater",function() +--~ local GLOBAL,THREAD = require("multi.integration.lanesManager").init() +--~ nGLOBAL = require("multi.integration.networkManager").init() +--~ function table.print(tbl, indent) +--~ if type(tbl)~="table" then return end +--~ if not indent then indent = 0 end +--~ for k, v in pairs(tbl) do +--~ formatting = string.rep(' ', indent) .. k .. ': ' +--~ if type(v) == 'table' then +--~ print(formatting) +--~ table.print(v, indent+1) +--~ else +--~ print(formatting .. tostring(v)) +--~ end +--~ end +--~ end +--~ print(#multi.SystemThreads) +--~ multi:newThread("Detail Updater",function() +--~ while true do +--~ thread.sleep(1) +--~ print(multi:getTasksDetails()) +--~ print("-----") +--~ table.print(multi:getTasksDetails("t")) +--~ io.read() +--~ end +--~ end) +--~ multi.OnSystemThreadDied(function(...) +--~ print("why you say dead?",...) +--~ end) +--~ multi.OnError(function(...) +--~ print(...) +--~ end) +--~ multi:newSystemThread("TestSystem",function() +--~ while true do +--~ THREAD.sleep(1) +--~ print("I'm alive") +--~ end +--~ end) +--~ print(#multi.SystemThreads) +--~ multi:mainloop{ +--~ protect = false, +--~ print = true +--~ } +--~ function tprint (tbl, indent) +--~ if not indent then indent = 0 end +--~ for k, v in pairs(tbl) do +--~ formatting = string.rep(" ", indent) .. k .. ": " +--~ if type(v) == "table" then +--~ print(formatting) +--~ tprint(v, indent+1) +--~ elseif type(v) == 'boolean' then +--~ print(formatting .. tostring(v)) +--~ else +--~ print(formatting .. tostring(v)) +--~ end +--~ end +--~ end + +--~ t = multi:newThread("test",function() +--~ while true do +--~ thread.sleep(.5) +--~ print("A test!") +--~ end +--~ end) +--~ multi:newAlarm(3):OnRing(function() +--~ multi:newAlarm(3):OnRing(function() +--~ t:Resume() +--~ end) +--~ t:Pause() +--~ end) +--~ multi.OnError(function(...) +--~ print(...) +--~ end) + +--~ function test() +--~ while true do +--~ a=a+1 +--~ end +--~ end +--~ g=string.dump(test) +--~ print(g) +--~ if g:find(" thread") then +--~ print("Valid Thread!") +--~ elseif (g:find("K")) and not g:find(" thread") then +--~ print("Invalid Thread!") +--~ else +--~ print("Should be safe") +--~ end +a=0 +multi:newTLoop(function() + a=a+1 +end,1) +multi:newThread("Test",function() while true do - thread.sleep(1) - print(multi:getTasksDetails()) - print("-----") - table.print(multi:getTasksDetails("t")) - io.read() + -- end end) -multi.OnSystemThreadDied(function(...) - print("why you say dead?",...) -end) -multi.OnError(function(...) - print(...) -end) -multi:newSystemThread("TestSystem",function() - while true do - THREAD.sleep(1) - print("I'm alive") - end -end) -print(#multi.SystemThreads) -multi:mainloop{ - protect = false, - print = true -} +multi:mainloop()