Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Aliyun ACR rejects the OCI 1.1 provenance attestation manifest
# ("unknown manifest class for application/vnd.oci.empty.v1+json").
# Disabling here keeps the push working on both ghcr.io and ACR.
provenance: false
clean:
needs: build
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
Expand Down
6 changes: 3 additions & 3 deletions docs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions lualib/silly/hive.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ local pack = table.pack
local unpack = table.unpack

local task_running = task.running
local task_yield = task._yield
local task_wait = task.wait
local task_resume = task._resume

local M = {}
Expand Down Expand Up @@ -39,7 +39,7 @@ function M.invoke(worker, ...)
local t = task_running()
local id = c.push(worker, ...)
working[id] = t
local ok, dat = task_yield("HIVE")
local ok, dat = task_wait()
if not ok then
error(dat[1])
end
Expand Down
16 changes: 14 additions & 2 deletions lualib/silly/task.lua
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,19 @@ local function task_create(f)
local running = corunning()
while true do
local ret
local status = task_status[running]
if status ~= "RUN" then
--A swallowed yield error (e.g. yield across a C-call
--boundary) leaves the status dirty. Such a task may
--have leaked registrations pointing at itself, so it
--must never go back to the pool.
local info = debug.getinfo(f, "S")
log_error("[silly] task exit with stat:", tostring(status),
info.short_src, info.linedefined)
task_traceid[running] = nil
task_status[running] = nil
return
end
f = NIL
task_traceid[running] = nil
task_status[running] = nil
Expand Down Expand Up @@ -178,7 +191,7 @@ function task.wait()
local t = task_running
local status = task_status[t]
if status ~= "RUN" then
error("BUG: wait on task stat:" .. status)
error("BUG: wait on task stat:" .. tostring(status), 2)
end
task_status[t] = "WAIT"
return task_yield("WAIT")
Expand Down Expand Up @@ -223,7 +236,6 @@ end

task._create = task_create
task._resume = task_resume
task._yield = task_yield

function task._dispatch_wakeup()
while true do
Expand Down
4 changes: 2 additions & 2 deletions lualib/silly/time.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ local type = type
local task_running = task.running
local task_create = task._create
local task_resume = task._resume
local task_yield = task._yield
local task_wait = task.wait
local timeafter = c.after
local timercancel = c.cancel

Expand All @@ -23,7 +23,7 @@ function M.sleep(ms)
local t = task_running()
local session = timeafter(ms)
sleep_session_task[session] = t
task_yield("SLEEP")
task_wait()
end

---@param ms integer
Expand Down
38 changes: 31 additions & 7 deletions test/testhive.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local time = require "silly.time"
local task = require "silly.task"
local hc = require "silly.hive.c"
local hive = require "silly.hive"
local waitgroup = require "silly.sync.waitgroup"
Expand Down Expand Up @@ -68,7 +69,30 @@ do
testaux.asserteq(c, 9, "Case 4: result[3]")
end

-- Test 5: Thread Pool Expansion
-- Test 5: Task status is WAIT while invoking
--(placed before the timing-sensitive thread scaling tests,
-- so it always runs even if they fail)
do
local worker = hive.spawn([[
return function()
return true
end
]])
local invoker
task.fork(function()
invoker = task.running()
local ok = hive.invoke(worker)
testaux.asserteq(ok, true, "Case 5: invoke result")
end)
task.fork(function()
--runs while the invoker is blocked waiting for the worker
testaux.asserteq(task.status(invoker), "WAIT",
"Case 5: invoking task status is WAIT")
end)
time.sleep(100)
end

-- Test 6: Thread Pool Expansion
do
hive.limit(2, 4)
-- After previous tests, threads might be at min. Let's run one task to ensure the pool is active.
Expand All @@ -78,7 +102,7 @@ do
hive.invoke(pre_worker)

local initial_threads = hive.threads()
testaux.asserteq(initial_threads, 1, "Case 5: initial threads")
testaux.asserteq(initial_threads, 1, "Case 6: initial threads")

local wg = waitgroup.new()
for i = 1, 10 do
Expand All @@ -91,21 +115,21 @@ do
end
]])
local ok = hive.invoke(worker)
testaux.asserteq(ok, true, "Case 5: task result for i="..i)
testaux.asserteq(ok, true, "Case 6: task result for i="..i)
end)
end
wg:wait()
testaux.asserteq(hive.threads(), 4, "Case 5: threads scaled up")
testaux.asserteq(hive.threads(), 4, "Case 6: threads scaled up")
end


-- Test 6: Thread Pool Pruning
-- Test 7: Thread Pool Pruning
do
-- Ensure pool is scaled up first from previous test
testaux.asserteq(hive.threads(), 4, "Case 6: threads before prune")
testaux.asserteq(hive.threads(), 4, "Case 7: threads before prune")
print("sleep 6 seconds for idle threads")
time.sleep(6000)
prune()
local threads = hive.threads()
testaux.asserteq(threads, 2, "Case 6: threads scaled down")
testaux.asserteq(threads, 2, "Case 7: threads scaled down")
end
10 changes: 8 additions & 2 deletions test/testssl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@ local testaux = require "test.testaux"
local errno = require "silly.errno"
local ETIMEDOUT<const> = errno.TIMEDOUT
local EEOF<const> = errno.EOF
local EPIPE<const> = errno.PIPE
local ECONNRESET<const> = errno.CONNRESET

local is_iocp = silly.multiplexer == "iocp"

local function assert_eof(dat, err, msg_data, msg_err)
-- Abrupt close (no close_notify) races: clean FIN -> EOF,
-- RST -> CONNRESET, RST with undrained server writes (TLS 1.3
-- NewSessionTicket) -> EPIPE. All mean the peer is gone.
local gone = err == EEOF or err == EPIPE or err == ECONNRESET
if is_iocp then
local ok = (dat == nil) and err == EEOF
local ok = (dat == nil) and gone
testaux.asserteq(ok, true, msg_err)
else
testaux.asserteq(err, EEOF, msg_err)
testaux.asserteq(gone, true, msg_err)
testaux.asserteq(dat, nil, msg_data)
end
end
Expand Down
37 changes: 37 additions & 0 deletions test/testtask.lua
Original file line number Diff line number Diff line change
Expand Up @@ -470,4 +470,41 @@ testaux.case("Test 17: task.hook", function()
testaux.asserteq(#created_tasks, before_count, "Test 17.9: hook cleared successfully")
end)

testaux.case("Test 18: status is WAIT while sleeping", function()
local sleeper
task.fork(function()
sleeper = task.running()
time.sleep(100)
end)
task.fork(function()
--runs while the sleeper is blocked in time.sleep
testaux.asserteq(task.status(sleeper), "WAIT",
"Test 18.1: sleeping task status is WAIT")
end)
time.sleep(200)
end)

testaux.case("Test 19: task with dirty status is not recycled", function()
collectgarbage("stop")
local dump = task._dump()
local dirty
task.fork(function()
dirty = task.running()
--yield across a C-call boundary, error swallowed by pcall
local ok = pcall(string.gsub, "x", "x", function()
time.sleep(10)
end)
testaux.asserteq(ok, false, "Test 19.1: yield across C-call boundary fails")
end)
task.fork(function()
--runs after the dirty task exited
testaux.asserteq(dump.task_status[dirty], nil,
"Test 19.2: dirty task status cleaned")
local probe = task.fork(function() end)
testaux.assertneq(probe, dirty,
"Test 19.3: dirty coroutine is not recycled")
end)
time.sleep(50) --let the leaked timer fire
end)

collectgarbage("restart")
Loading