Skip to content

Commit

Permalink
feat(timeouts) allow math.huge to wait forever
Browse files Browse the repository at this point in the history
applies to copas.timeout, lock, semaphore, and queue.
  • Loading branch information
Tieske committed Dec 28, 2022
1 parent 35aa5d2 commit 78a37c5
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 22 deletions.
7 changes: 7 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ <h2><a name="dependencies"></a>Dependencies</h2>
<h2><a name="history"></a>History</h2>

<dl class="history">
<dt><strong>Copas 4.x.0</strong> [unreleased]</dt>
<dd><ul>
<li>Added: for timeouts in copas, lock, semaphore, and queue, allow <code>math.huge</code>
to specify no timeout/wait forever. Using <code>math.huge</code> over long timeouts will
reduce pressure on the timer-wheel.</li>
</ul></dd>

<dt><strong>Copas 4.5.0</strong> [18/Dec/2022]</dt>
<dd><ul>
<li>Added: <code>copas.status</code> got an extra parameter to track more detailed stats.</li>
Expand Down
20 changes: 11 additions & 9 deletions docs/reference.html
Original file line number Diff line number Diff line change
Expand Up @@ -418,18 +418,19 @@ <h3>Non-blocking data exchange and timer/sleep functions</h3>
<dt><strong><code>lock:get([timeout])</code></strong></dt>
<dd>Will try and acquire the lock. The optional <code>timeout</code> can be
used to override the timeout value set when the lock was created.<br />
If the the lock is not available, the coroutine will yield until either the
If the lock is not available, the coroutine will yield until either the
lock becomes available, or it times out. The one exception is when
<code>timeout</code> is 0, then it will immediately return without yielding.<br />
<code>timeout</code> is 0, then it will immediately return without yielding.
If the timeout is set to <code>math.huge</code>, then it will wait forever.<br />
Upon success, it will return the <code>wait-time</code> in seconds. Upon failure it will
return <code>nil + error + wait-time</code>. Upon a timeout the error value will
be "timeout".
</dd>

<dt><strong><code>lock.new([timeout], [not_reentrant])</code></strong></dt>
<dd>Creates and returns a new lock. The <code>timeout</code> specifies the
default timeout for the lock in
seconds, and defaults to 10. By default the lock is re-entrant,
default timeout for the lock in seconds, and defaults to 10 (set it to <code>math.huge</code>
to wait forever). By default the lock is re-entrant,
except if <code>not_reentrant</code> is set to a truthy value.
</dd>

Expand All @@ -455,7 +456,8 @@ <h3>Non-blocking data exchange and timer/sleep functions</h3>

<dt><strong><code>queue:finish([timeout], [no_destroy_on_timeout])</code></strong></dt>
<dd>Finishes a queue. Calls <code>queue:stop()</code> and then waits for the queue to run
empty (and be destroyed) before returning.
empty (and be destroyed) before returning. The <code>timeout</code> defaults to 10 seconds
(the default timeout value for a lock), <code>math.huge</code> can be used to wait forever.
Parameter <code>no_destroy_on_timeout</code> indicates if the queue is not to be forcefully
destroyed on a timeout (abandonning what ever is left in the queue).
Returns <code>true</code>, or <code>nil+"timeout"</code>, or <code>nil+"destroyed"</code>.
Expand Down Expand Up @@ -492,7 +494,7 @@ <h3>Non-blocking data exchange and timer/sleep functions</h3>
<dd>Will pop an item from the queue. If there are no items in the queue it will yield
until there are or a timeout happens (exception is when <code>timeout == 0</code>, then it will
not yield but return immediately, be careful not to create a hanging loop!). Timeout defaults
to the default time-out of a semaphore.
to the default time-out of a semaphore. If the timeout is <code>math.huge</code> then it will wait forever.
Returns an item, or <code>nil+"timeout"</code>, or <code>nil+"destroyed"</code>. Since an item
can be <code>nil</code>, make sure to check for the error message to detect error.
</dd>
Expand Down Expand Up @@ -538,7 +540,7 @@ <h3>Non-blocking data exchange and timer/sleep functions</h3>
<code>max</code> specifies the maximum number of resources the semaphore can hold.
The optional <code>start</code> parameter (default 0) specifies the number of resources upon creation.
The <code>timeout</code> specifies the default timeout for the semaphore in
seconds, and defaults to 10.
seconds, and defaults to 10 (<code>math.huge</code> can be used to wait forever).
</dd>

<dt><strong><code>semaphore:take([requested], [timeout])</code></strong></dt>
Expand All @@ -547,7 +549,7 @@ <h3>Non-blocking data exchange and timer/sleep functions</h3>
If not enough resources are available it
will yield and wait until enough resources are available, or a timeout occurs. The exception is when
<code>timeout</code> is set to 0, in that case it will immediately return without yielding if there are
not enough resources available.<br />
not enough resources available. If the timeout is <code>math.huge</code> then it will wait forever.<br />
The optional <code>timeout</code> parameter can be used to override the default timeout as set upon
semaphore creation.
Returns <code>true</code> upon success or <code>nil + "timeout"</code> on a timeout. In case more
Expand Down Expand Up @@ -778,7 +780,7 @@ <h3>Low level Copas functions</h3>
<dd>Creates a timeout timer for the current coroutine. The <code>delay</code>
is the timeout in seconds, and the <code>callback</code> will
be called upon an actual timeout occuring.<br />
Calling it with <code>delay = 0</code> will cancel the timeout.<br />
Calling it with <code>delay = 0</code> (or <code>math.huge</code>) will cancel the timeout.<br />
Calling it repeatedly will simply replace the timeout on the current
coroutine and any previous callback set will no longer be called.<br />
<br />
Expand Down
13 changes: 6 additions & 7 deletions src/copas.lua
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,8 @@ do
local timeout_mt = {
__mode = "k",
__index = function(self, skt)
-- if there is no timeout found, we insert one automatically,
-- a 10 year timeout as substitute for the default "blocking" should do
self[skt] = 10*365*24*60*60
-- if there is no timeout found, we insert one automatically, to block forever
self[skt] = math.huge
return self[skt]
end,
}
Expand Down Expand Up @@ -413,7 +412,7 @@ local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
end


-- Returns the poroper timeout error
-- Returns the proper timeout error
function sto_error(err)
return useSocketTimeoutErrors[coroutine_running()] and err or "timeout"
end
Expand Down Expand Up @@ -1306,7 +1305,7 @@ do
end

--- Sets the timeout for the current coroutine.
-- @param delay delay (seconds), use 0 to cancel the timerout
-- @param delay delay (seconds), use 0 (or math.huge) to cancel the timerout
-- @param callback function with signature: `function(coroutine)` where coroutine is the routine that timed-out
-- @return true
function copas.timeout(delay, callback)
Expand All @@ -1317,9 +1316,9 @@ do
timerwheel:cancel(existing_timer)
end

if delay > 0 then
if delay > 0 and delay ~= math.huge then
timeout_register[co] = timerwheel:set(delay, callback, co)
elseif delay == 0 then
elseif delay == 0 or delay == math.huge then
timeout_register[co] = nil
else
error("timout value must be greater than or equal to 0, got: "..tostring(delay))
Expand Down
5 changes: 3 additions & 2 deletions src/copas/lock.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ local registry = setmetatable({}, { __mode="kv" })


--- Creates a new lock.
-- @param seconds (optional) default timeout in seconds when acquiring the lock (defaults to 10)
-- @param seconds (optional) default timeout in seconds when acquiring the lock (defaults to 10),
-- set to `math.huge` to have no timeout.
-- @param not_reentrant (optional) if truthy the lock will not allow a coroutine to grab the same lock multiple times
-- @return the lock object
function lock.new(seconds, not_reentrant)
Expand Down Expand Up @@ -100,7 +101,7 @@ end
-- If the lock is owned by another thread, this will yield control, until the
-- lock becomes available, or it times out.
-- If `timeout == 0` then it will immediately return (without yielding).
-- @param timeout (optional) timeout in seconds, if given overrides the timeout passed to `new`.
-- @param timeout (optional) timeout in seconds, defaults to the timeout passed to `new` (use `math.huge` to have no timeout).
-- @return wait-time on success, or nil+error+wait_time on failure. Errors can be "timeout", "destroyed", or "lock is not re-entrant"
function lock:get(timeout)
local co = coroutine.running()
Expand Down
4 changes: 2 additions & 2 deletions src/copas/queue.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ end

-- Pops and item from the queue. If there are no items in the queue it will yield
-- until there are or a timeout happens (exception is when `timeout == 0`, then it will
-- not yield but return immediately)
-- not yield but return immediately). If the timeout is `math.huge` it will wait forever.
-- Returns item, or nil+err ("timeout", or "destroyed")
function Queue:pop(timeout)
local ok, err = self.sema:take(1, timeout)
Expand Down Expand Up @@ -163,7 +163,7 @@ function Queue:add_worker(worker)

coro = copas.addnamedthread(worker_name, function()
while true do
local item, err = self:pop(10*365*24*60*60) -- wait forever (10yr)
local item, err = self:pop(math.huge) -- wait forever
if err then
break -- queue destroyed, exit
end
Expand Down
5 changes: 3 additions & 2 deletions src/copas/semaphore.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ local registry = setmetatable({}, { __mode="kv" })
-- create a new semaphore
-- @param max maximum number of resources the semaphore can hold (this maximum does NOT include resources that have been given but not yet returned).
-- @param start (optional, default 0) the initial resources available
-- @param seconds (optional, default 10) default semaphore timeout in seconds
-- @param seconds (optional, default 10) default semaphore timeout in seconds, or `math.huge` to have no timeout.
function semaphore.new(max, start, seconds)
local timeout = tonumber(seconds or DEFAULT_TIMEOUT) or -1
if timeout < 0 then
Expand Down Expand Up @@ -133,7 +133,8 @@ end
-- Waits if there are not enough resources available before returning.
-- @param requested (optional, default 1) the number of resources requested
-- @param timeout (optional, defaults to semaphore timeout) timeout in
-- seconds. If 0 it will either succeed or return immediately with error "timeout"
-- seconds. If 0 it will either succeed or return immediately with error "timeout".
-- If `math.huge` it will wait forever.
-- @return true, or nil+"destroyed"
function semaphore:take(requested, timeout)
requested = requested or 1
Expand Down

0 comments on commit 78a37c5

Please sign in to comment.