Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(exit): add exit signalling #172

Merged
merged 3 commits into from
Aug 29, 2024
Merged
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
2 changes: 2 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ <h2><a name="history"></a>History</h2>
<li>Change: Copas no longer requires LuaSocket, if no sockets are needed, LuaSystem will be enough as a fallback.</li>
<li>Feat: added <code>copas.gettime()</code>, which transparently maps to either LuaSockets or
LuaSystems implementation, ensuring independence of the availability of either one of those.</li>
<li>Feat: Controlled exit of the Copas loop. Adding <code>copas.exit()</code>, <code>copas.exiting()</code>, and
<code>copas.waitforexit()</code>.</li>
</ul></dd>

<dt><strong>Copas 4.7.1</strong> [9/Mar/2024]</dt>
Expand Down
40 changes: 38 additions & 2 deletions docs/reference.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,12 @@ <h3>Getting started examples</h3>

end

copas.addserver(server_socket, copas.handler(connection_handler,
ssl_params), "my_TCP_server")
copas.addthread(function()
copas.addserver(server_socket, copas.handler(connection_handler,
ssl_params), "my_TCP_server")
copas.waitforexit()
copas.removeserver(server_socket)
end)

copas()
</pre>
Expand Down Expand Up @@ -180,6 +184,29 @@ <h3>Copas dispatcher main functions</h3>
truthy.</p>
</dd>

<dt><strong><code>copas.exit()</code></strong></dt>
<dd>
<p>Sets a flag that the application is intending to exit. After calling
this function <code>copas.exiting()</code> will be returning <code>true</code>, and
all threads blocked on <code>copas.waitforexit()</code> will be released.</p>

<p>Copas itself will call this function when <code>copas.finished()</code> returns
<code>true</code>.</p>
</dd>

<dt><strong><code>bool = copas.exiting()</code></strong></dt>
<dd>
<p>Returns a flag indicating whether the application is supposed to exit.
Returns <code>false</code> until after <code>copas.exit()</code> has been called,
after which it will start returning <code>true</code>.</p>

<p>Clients should check whether they are to cease their operation and exit. They
can do this by checking this flag, or by registering a task waiting on
<code>copas.waitforexit()</code>. Clients should cancel pending work and close sockets
when an exit is announced, otherwise Copas will not exit.
</p>
</dd>

<dt><strong><code>bool = copas.finished()</code></strong></dt>
<dd>
<p>Checks whether anything remains to be done.</p>
Expand Down Expand Up @@ -304,6 +331,15 @@ <h3>Copas dispatcher main functions</h3>
currently running coroutine.</p>
</dd>

<dt><strong><code>copas.waitforexit()</code></strong></dt>
<dd>
<p>This will block the calling coroutine until the <code>copas.exit()</code> function
is called. Clients should check whether they are to cease their operation and exit. They
can do this by waiting on this call, or by checking the <code>copas.exiting()</code> flag.
Clients should cancel pending work and close sockets when an exit is announced, otherwise
Copas will not exit.</p>
</dd>

<dt><strong><code>skt = copas.wrap(skt [, sslparams] )</code></strong></dt>
<dd>
<p>Wraps a LuaSocket socket and returns a Copas socket that implements LuaSocket's API
Expand Down
43 changes: 42 additions & 1 deletion src/copas.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1666,6 +1666,38 @@ function copas.finished()
return #_reading == 0 and #_writing == 0 and _resumable:done() and _sleeping:done(copas.gettimeouts())
end


local resetexit do
local exit_semaphore, exiting

function resetexit()
exit_semaphore = copas.semaphore.new(1, 0, math.huge)
exiting = false
end

-- Signals tasks to exit. But only if they check for it. By calling `copas.exiting`
-- they can check if they should exit. Or by calling `copas.waitforexit` they can
-- wait until the exit signal is given.
function copas.exit()
if exiting then return end
exiting = true
exit_semaphore:destroy()
end

-- returns whether Copas is in the process of exiting. Exit can be started by
-- calling `copas.exit()`.
function copas.exiting()
return exiting
end

-- Pauses the current coroutine until Copas is exiting. To be used as an exit
-- signal for tasks that need to clean up before exiting.
function copas.waitforexit()
exit_semaphore:take(1)
end
end


local _getstats do
local _getstats_instrumented, _getstats_plain

Expand Down Expand Up @@ -1756,8 +1788,17 @@ function copas.loop(initializer, timeout)
timeout = initializer or timeout
end

resetexit()
copas.running = true
while not copas.finished() do copas.step(timeout) end
while true do
copas.step(timeout)
if copas.finished() then
if copas.exiting() then
break
end
copas.exit()
end
end
copas.running = false
end

Expand Down
43 changes: 43 additions & 0 deletions tests/exittest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,46 @@ copas.loop()
assert(testran == 6, "Test 6 was not executed!")
print("6) success")

print("7) Testing exiting releasing the exitsemaphore (implicit, no call to copas.exit)")
copas.addthread(function()
print("","7 running...")
copas.addthread(function()
copas.waitforexit()
testran = 7
end)
end)
copas.loop()
assert(testran == 7, "Test 7 was not executed!")
print("7) success")

print("8) Testing schduling new tasks while exiting (explicit exit by calling copas.exit)")
testran = 0
copas.addthread(function()
print("","8 running...")
copas.addthread(function()
while true do
copas.pause(0.1)
testran = testran + 1
print("count...")
if testran == 3 then -- testran == 3
print("initiating exit...")
copas.exit()
break
end
end
end)
copas.addthread(function()
copas.waitforexit()
print("exit signal received...")
testran = testran + 1 -- testran == 4
copas.addthread(function()
print("running new task from exit handler...")
copas.pause(1)
testran = testran + 1 -- testran == 5
print("new task from exit handler done!")
end)
end)
end)
copas.loop()
assert(testran == 5, "Test 8 was not executed!")
print("8) success")
Loading