From 3c08808f0e4acbbf89c6046cec5ef838af824676 Mon Sep 17 00:00:00 2001
From: Thijs Schreijer
Date: Tue, 26 Jul 2022 12:32:47 +0200
Subject: [PATCH 1/2] feat(naming) adds naming to sockets and threads for
debugging
also adds some debug functions to have a detailed view of
yielding and resuming of threads in the scheduler.
---
docs/index.html | 2 +-
src/copas.lua | 247 +++++++++++++++++++++++++++++++++++++-------
src/copas/queue.lua | 23 ++++-
src/copas/timer.lua | 12 ++-
4 files changed, 240 insertions(+), 44 deletions(-)
diff --git a/docs/index.html b/docs/index.html
index 13d14f9..4ab01f1 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -109,7 +109,7 @@ History
Copas 3.0.0 [12/Nov/2021]
- - [breaking] Change: copas.addserver() now uses the timeout value as a copas timeout,
+
- [breaking] Change: copas.addserver() now uses the timeout value as a copas timeout,
instead of a luasocket timeout. The client sockets for incoming connections will
inherit the timeout from the server socket.
- Added: support for SNI on TLS connections #81 (@amyspark)
diff --git a/src/copas.lua b/src/copas.lua
index 20df61b..6173a0e 100644
--- a/src/copas.lua
+++ b/src/copas.lua
@@ -61,6 +61,19 @@ function socket.newtry(finalizer)
end
end
+
+-- nil-safe versions for pack/unpack
+local _unpack = unpack or table.unpack
+local unpack = function(t, i, j) return _unpack(t, i or 1, j or t.n or #t) end
+local pack = function(...) return { n = select("#", ...), ...} end
+
+
+local coroutine_create = coroutine.create
+local coroutine_running = coroutine.running
+local coroutine_yield = coroutine.yield
+local coroutine_resume = coroutine.resume
+
+
local copas = {}
-- Meta information is public even if beginning with an "_"
@@ -75,6 +88,18 @@ copas.autoclose = true
copas.running = false
+-------------------------------------------------------------------------------
+-- Object names, to track names of thread/coroutines and sockets
+-------------------------------------------------------------------------------
+local object_names = setmetatable({}, {
+ __mode = "k",
+ __index = function(self, key)
+ local name = tostring(key)
+ rawset(self, key, name)
+ return name
+ end
+})
+
-------------------------------------------------------------------------------
-- Simple set implementation
-- adds a FIFO queue for each socket in the set
@@ -316,7 +341,7 @@ local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
-- is truthy, it is the connect timeout.
-- @return true
function sto_timeout(skt, queue, use_connect_to)
- local co = coroutine.running()
+ local co = coroutine_running()
socket_register[co] = skt
operation_register[co] = queue
timeout_flags[co] = nil
@@ -338,20 +363,20 @@ local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
-- @param queue (string) the new queue the socket is in, must be either "read" or "write"
-- @return true
function sto_change_queue(queue)
- operation_register[coroutine.running()] = queue
+ operation_register[coroutine_running()] = queue
return true
end
-- Responds with `true` if the operation timed-out.
function sto_timed_out()
- return timeout_flags[coroutine.running()]
+ return timeout_flags[coroutine_running()]
end
-- Returns the poroper timeout error
function sto_error(err)
- return useSocketTimeoutErrors[coroutine.running()] and err or "timeout"
+ return useSocketTimeoutErrors[coroutine_running()] and err or "timeout"
end
end
@@ -462,12 +487,12 @@ function copas.receive(client, pattern, part)
current_log = _writing_log
current_log[client] = gettime()
sto_change_queue("write")
- coroutine.yield(client, _writing)
+ coroutine_yield(client, _writing)
else
current_log = _reading_log
current_log[client] = gettime()
sto_change_queue("read")
- coroutine.yield(client, _reading)
+ coroutine_yield(client, _reading)
end
until false
end
@@ -498,7 +523,7 @@ function copas.receivefrom(client, size)
end
_reading_log[client] = gettime()
- coroutine.yield(client, _reading)
+ coroutine_yield(client, _reading)
until false
end
@@ -532,12 +557,12 @@ function copas.receivePartial(client, pattern, part)
current_log = _writing_log
current_log[client] = gettime()
sto_change_queue("write")
- coroutine.yield(client, _writing)
+ coroutine_yield(client, _writing)
else
current_log = _reading_log
current_log[client] = gettime()
sto_change_queue("read")
- coroutine.yield(client, _reading)
+ coroutine_yield(client, _reading)
end
until false
end
@@ -560,9 +585,9 @@ function copas.send(client, data, from, to)
if (math.random(100) > 90) then
current_log[client] = gettime() -- TODO: how to handle this??
if current_log == _writing_log then
- coroutine.yield(client, _writing)
+ coroutine_yield(client, _writing)
else
- coroutine.yield(client, _reading)
+ coroutine_yield(client, _reading)
end
end
@@ -585,12 +610,12 @@ function copas.send(client, data, from, to)
current_log = _reading_log
current_log[client] = gettime()
sto_change_queue("read")
- coroutine.yield(client, _reading)
+ coroutine_yield(client, _reading)
else
current_log = _writing_log
current_log[client] = gettime()
sto_change_queue("write")
- coroutine.yield(client, _writing)
+ coroutine_yield(client, _writing)
end
until false
end
@@ -631,7 +656,7 @@ function copas.connect(skt, host, port)
tried_more_than_once = tried_more_than_once or true
_writing_log[skt] = gettime()
- coroutine.yield(skt, _writing)
+ coroutine_yield(skt, _writing)
until false
end
@@ -752,7 +777,7 @@ function copas.dohandshake(skt, wrap_params)
error("TLS/SSL handshake failed: " .. tostring(err))
end
- coroutine.yield(nskt, queue)
+ coroutine_yield(nskt, queue)
until false
end
@@ -932,7 +957,7 @@ function copas.setErrorHandler (err, default)
if default then
_deferror = err
else
- _errhandlers[coroutine.running()] = err
+ _errhandlers[coroutine_running()] = err
end
end
@@ -940,7 +965,7 @@ end
-- `timeout, wantread, wantwrite, Operation already in progress`. If falsy, it will always
-- return `timeout`.
function copas.useSocketTimeoutErrors(bool)
- useSocketTimeoutErrors[coroutine.running()] = not not bool -- force to a boolean
+ useSocketTimeoutErrors[coroutine_running()] = not not bool -- force to a boolean
end
-------------------------------------------------------------------------------
@@ -957,7 +982,7 @@ local function _doTick (co, skt, ...)
return
end
- local ok, res, new_q = coroutine.resume(co, skt, ...)
+ local ok, res, new_q = coroutine_resume(co, skt, ...)
if ok and res and new_q then
new_q:insert (res)
@@ -972,19 +997,26 @@ local function _doTick (co, skt, ...)
end
--- accepts a connection on socket input
-local function _accept(server_skt, handler)
- local client_skt = server_skt:accept()
- if client_skt then
- client_skt:settimeout(0)
- copas.settimeouts(client_skt, user_timeouts_connect[server_skt], -- copy server socket timeout settings
- user_timeouts_send[server_skt], user_timeouts_receive[server_skt])
- local co = coroutine.create(handler)
- _doTick(co, client_skt)
+local _accept do
+ local client_counters = setmetatable({}, { __mode = "k" })
+
+ -- accepts a connection on socket input
+ function _accept(server_skt, handler)
+ local client_skt = server_skt:accept()
+ if client_skt then
+ local count = (client_counters[server_skt] or 0) + 1
+ client_counters[server_skt] = count
+ object_names[client_skt] = object_names[server_skt] .. ":client_" .. count
+
+ client_skt:settimeout(0)
+ copas.settimeouts(client_skt, user_timeouts_connect[server_skt], -- copy server socket timeout settings
+ user_timeouts_send[server_skt], user_timeouts_receive[server_skt])
+ local co = coroutine_create(handler)
+ _doTick(co, client_skt)
+ end
end
end
-
-------------------------------------------------------------------------------
-- Adds a server/handler pair to Copas dispatcher
-------------------------------------------------------------------------------
@@ -1001,7 +1033,7 @@ do
local function addUDPserver(server, handler, timeout)
server:settimeout(0)
- local co = coroutine.create(handler)
+ local co = coroutine_create(handler)
_reading:insert(server)
if timeout then
copas.settimeout(server, timeout)
@@ -1010,11 +1042,14 @@ do
end
- function copas.addserver(server, handler, timeout)
+ function copas.addserver(server, handler, timeout, name)
+ if name then
+ object_names[server] = name
+ end
if isTCP(server) then
- addTCPserver(server, handler, timeout)
+ addTCPserver(server, handler, timeout, name)
else
- addUDPserver(server, handler, timeout)
+ addUDPserver(server, handler, timeout, name)
end
end
end
@@ -1041,21 +1076,31 @@ end
-------------------------------------------------------------------------------
-- Adds an new coroutine thread to Copas dispatcher
-------------------------------------------------------------------------------
-function copas.addthread(handler, ...)
+function copas.addnamedthread(handler, name, ...)
-- create a coroutine that skips the first argument, which is always the socket
-- passed by the scheduler, but `nil` in case of a task/thread
- local thread = coroutine.create(function(_, ...)
+ local thread = coroutine_create(function(_, ...)
-- TODO: this should be added to not immediately execute the thread
-- it should only schedule and then return to the calling code
-- Enabling this breaks the "limitset".
-- copas.sleep(0)
return handler(...)
end)
+ if name then
+ object_names[thread] = name
+ end
+
_threads[thread] = true -- register this thread so it can be removed
_doTick (thread, nil, ...)
return thread
end
+
+function copas.addthread(handler, ...)
+ return copas.addnamedthread(handler, nil, ...)
+end
+
+
function copas.removethread(thread)
-- if the specified coroutine is registered, add it to the canceled table so
-- that next time it tries to resume it exits.
@@ -1071,7 +1116,7 @@ end
-- yields the current coroutine and wakes it after 'sleeptime' seconds.
-- If sleeptime < 0 then it sleeps until explicitly woken up using 'wakeup'
function copas.sleep(sleeptime)
- coroutine.yield((sleeptime or 0), _sleeping)
+ coroutine_yield((sleeptime or 0), _sleeping)
end
-- Wakes up a sleeping coroutine 'co'.
@@ -1093,12 +1138,12 @@ do
err_handler = function(...) return _deferror(...) end,
})
- copas.addthread(function()
+ copas.addnamedthread(function()
while true do
copas.sleep(TIMEOUT_PRECISION)
timerwheel:step()
end
- end)
+ end, "copas_core_timer")
-- get the number of timeouts running
function copas.gettimeouts()
@@ -1110,7 +1155,7 @@ do
-- @param callback function with signature: `function(coroutine)` where coroutine is the routine that timed-out
-- @return true
function copas.timeout(delay, callback)
- local co = coroutine.running()
+ local co = coroutine_running()
local existing_timer = timeout_register[co]
if existing_timer then
@@ -1339,7 +1384,7 @@ end
-------------------------------------------------------------------------------
function copas.loop(initializer, timeout)
if type(initializer) == "function" then
- copas.addthread(initializer)
+ copas.addnamedthread(initializer, "copas_initializer")
else
timeout = initializer or timeout
end
@@ -1349,4 +1394,128 @@ function copas.loop(initializer, timeout)
copas.running = false
end
+
+-------------------------------------------------------------------------------
+-- Naming sockets and coroutines.
+-------------------------------------------------------------------------------
+do
+ local function realsocket(skt)
+ local mt = getmetatable(skt)
+ if mt == _skt_mt_tcp or mt == _skt_mt_udp then
+ return skt.socket
+ else
+ return skt
+ end
+ end
+
+
+ function copas.setsocketname(name, skt)
+ assert(type(name) == "string", "expected arg #1 to be a string")
+ skt = assert(realsocket(skt), "expected arg #2 to be a socket")
+ object_names[skt] = name
+ end
+
+
+ function copas.getsocketname(skt)
+ skt = assert(realsocket(skt), "expected arg #1 to be a socket")
+ return object_names[skt]
+ end
+end
+
+
+function copas.setthreadname(name, coro)
+ assert(type(name) == "string", "expected arg #1 to be a string")
+ coro = coro or coroutine_running()
+ assert(type(coro) == "thread", "expected arg #2 to be a coroutine or nil")
+ object_names[coro] = name
+end
+
+
+function copas.getthreadname(coro)
+ coro = coro or coroutine_running()
+ assert(type(coro) == "thread", "expected arg #1 to be a coroutine or nil")
+ return object_names[coro]
+end
+
+-------------------------------------------------------------------------------
+-- Debug functionality.
+-------------------------------------------------------------------------------
+do
+ copas.debug = {}
+
+ local log_core -- if truthy, the core-timer will also be logged
+ local debug_log -- function used as logger
+
+
+ local debug_yield = function(skt, queue)
+ local name = object_names[coroutine.running()]
+
+ if log_core or name ~= "copas_core_timer" then
+ if queue == _sleeping then
+ debug_log("yielding '", name, "' to SLEEP for ", skt," seconds")
+
+ elseif queue == _writing then
+ debug_log("yielding '", name, "' to WRITE to '", object_names[skt], "'")
+
+ elseif queue == _reading then
+ debug_log("yielding '", name, "' to READ from '", object_names[skt], "'")
+
+ else
+ debug_log("thread '", name, "' yielding to unexpected queue; ", tostring(queue), " (", type(queue), ")", debug.traceback())
+ end
+ end
+
+ return coroutine.yield(skt, queue)
+ end
+
+
+ local debug_resume = function(coro, skt, ...)
+ local name = object_names[coro]
+
+ if skt then
+ debug_log("resuming '", name, "' for socket '", object_names[skt], "'")
+ else
+ if log_core or name ~= "copas_core_timer" then
+ debug_log("resuming '", name, "'")
+ end
+ end
+ return coroutine.resume(coro, skt, ...)
+ end
+
+
+ local debug_create = function(f)
+ local f_wrapped = function(...)
+ local results = pack(f(...))
+ debug_log("exiting '", object_names[coroutine.running()], "'")
+ return unpack(results)
+ end
+
+ return coroutine.create(f_wrapped)
+ end
+
+
+ debug_log = function() end
+
+
+ function copas.debug.start(logger, core)
+ log_core = core
+ debug_log = logger or function(...)
+ print(table.concat {...} )
+ end
+ coroutine_yield = debug_yield
+ coroutine_resume = debug_resume
+ coroutine_create = debug_create
+ end
+
+
+ function copas.debug.stop()
+ debug_log = function() end
+ coroutine_yield = coroutine.yield
+ coroutine_resume = coroutine.resume
+ coroutine_create = coroutine.create
+ end
+
+end
+
+
return copas
diff --git a/src/copas/queue.lua b/src/copas/queue.lua
index 53273a2..dbc4d7f 100644
--- a/src/copas/queue.lua
+++ b/src/copas/queue.lua
@@ -7,16 +7,29 @@ local Queue = {}
Queue.__index = Queue
+local new_name do
+ local count = 0
+
+ function new_name()
+ count = count + 1
+ return "copas_queue_" .. count
+ end
+end
+
+
-- Creates a new Queue instance
-function Queue.new()
+function Queue.new(opts)
+ opts = opts or {}
local self = {}
setmetatable(self, Queue)
+ self.name = opts.name or new_name()
self.sema = Sema.new(10^9)
self.head = 1
self.tail = 1
self.list = {}
self.workers = setmetatable({}, { __mode = "k" })
self.stopping = false
+ self.worker_id = 0
return self
end
@@ -128,7 +141,11 @@ end
function Queue:add_worker(worker)
assert(type(worker) == "function", "expected worker to be a function")
local coro
- coro = copas.addthread(function()
+
+ self.worker_id = self.worker_id + 1
+ local worker_name = self.name .. ":worker_" .. self.worker_id
+
+ coro = copas.addnamedthread(function()
copas.sleep(0) -- TODO: remove after adding into copas.addthread
while true do
local item = self:pop(10*365*24*60*60) -- wait forever (10yr)
@@ -138,7 +155,7 @@ function Queue:add_worker(worker)
worker(item)
end
self.workers[coro] = nil
- end)
+ end, worker_name)
self.workers[coro] = true
return coro
diff --git a/src/copas/timer.lua b/src/copas/timer.lua
index 1ed7948..3c26fbf 100644
--- a/src/copas/timer.lua
+++ b/src/copas/timer.lua
@@ -6,6 +6,15 @@ local timer = {}
timer.__index = timer
+local new_name do
+ local count = 0
+
+ function new_name()
+ count = count + 1
+ return "copas_timer_" .. count
+ end
+end
+
do
local function expire_func(self, initial_delay)
@@ -37,7 +46,7 @@ do
end
self.cancelled = false
- self.co = copas.addthread(expire_func, self, initial_delay or self.delay)
+ self.co = copas.addnamedthread(expire_func, self.name, self, initial_delay or self.delay)
return self
end
end
@@ -73,6 +82,7 @@ function timer.new(opts)
assert(opts.delay >= 0, "delay must be greater than or equal to 0")
assert(type(opts.callback) == "function", "expected callback to be a function")
return setmetatable({
+ name = opts.name or new_name(),
delay = opts.delay,
callback = opts.callback,
recurring = not not opts.recurring,
From ec0284149326eadef353be4c27725f62ba5e8f89 Mon Sep 17 00:00:00 2001
From: Thijs Schreijer
Date: Wed, 27 Jul 2022 08:39:55 +0200
Subject: [PATCH 2/2] add docs
---
docs/index.html | 29 +++++++++++-----
docs/reference.html | 82 ++++++++++++++++++++++++++++++++++++++++-----
src/copas.lua | 42 ++++++++++++++---------
3 files changed, 120 insertions(+), 33 deletions(-)
diff --git a/docs/index.html b/docs/index.html
index 4ab01f1..be19240 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -102,32 +102,43 @@ History
- Copas x.x.x [unreleased]
- - Added: added sempahore:destroy()
- - Added: copas.settimeouts, to set separate timeouts for connect, send, receive
+ - Added: added
sempahore:destroy()
+ - Added:
copas.settimeouts
, to set separate timeouts for connect, send, receive
- Added: queue class, see module "copas.queue"
+ - Added: names for sockets and coroutines:
+
+ copas.addserver()
has a 4th argument; name, to name the server socket
+ copas.addnamedthread()
is new and identical to copas.addthread()
,
+ but also accepts a name
+ copas.setsocketname()
, copas.getsocketname()
,
+ copas.setthreadname()
, copas.getthreadname()
added to manage names
+ copas.debug.start()
and copas.debug.end()
to enable debug
+ logging for the scheduler itself.
+
+
- Copas 3.0.0 [12/Nov/2021]
- - [breaking] Change: copas.addserver() now uses the timeout value as a copas timeout,
+
- [breaking] Change:
copas.addserver()
now uses the timeout value as a copas timeout,
instead of a luasocket timeout. The client sockets for incoming connections will
inherit the timeout from the server socket.
- Added: support for SNI on TLS connections #81 (@amyspark)
- - Added: copas.settimeout() so Copas can manage its own timeouts instead of spinning forever (Patrick Barrett )
+ - Added:
copas.settimeout()
so Copas can manage its own timeouts instead of spinning forever (Patrick Barrett )
- Added: timer class, see module "copas.timer"
- Added: lock class, see module "copas.lock"
- Added: semaphore class, see module "copas.semaphore"
- - Added: timeout interface copas.timeout()
+ - Added: timeout interface
copas.timeout()
- Added: option to override the default errorhandler, and fixes to the handler
- - Added: copas.removethread() added to be able to forcefully remove a previously added thread
- - Added: copas.loop() now takes an optional initialization function
+ - Added:
copas.removethread()
added to be able to forcefully remove a previously added thread
+ - Added:
copas.loop()
now takes an optional initialization function
- Fixed: closing sockets from another thread would make the read/write ops hang #104
- Fixed: coxpcall dependency in limit.lua #63 (Francois Perrad)
- Fixed: CI now generates the certificates for testing, on unix make can be used, on Windows generate them manually
- - Fixed: type in wrapped udp:setpeername was actually calling udp:getpeername
+ - Fixed: type in wrapped
udp:setpeername
was actually calling udp:getpeername
- Fixed: default error handler didn't print the stacktrace
- Fixed: small memory leak when sleeping until woken
- - Fixed: do not wrap udp:sendto() method, since udp send doesn't block
+ - Fixed: do not wrap
udp:sendto()
method, since udp send doesn't block
- Change: performance improvement in big limit-sets (Francisco Castro)
- Change: update deprecated tls default to tls 1.2 in (copas.http)
diff --git a/docs/reference.html b/docs/reference.html
index 4b38615..97b94c8 100644
--- a/docs/reference.html
+++ b/docs/reference.html
@@ -81,7 +81,8 @@ Getting started examples
end
-copas.addserver(server_socket, copas.handler(connection_handler, ssl_params))
+copas.addserver(server_socket, copas.handler(connection_handler,
+ ssl_params), "my_TCP_server")
copas.loop()
@@ -103,6 +104,7 @@ Getting started examples
}
local sock = copas.wrap(socket.tcp(), ssl_params)
+ copas.setsocketname("my_TCP_client", sock)
assert(sock:connect(host, port))
local data, err = sock:receive("*l")
@@ -121,17 +123,29 @@ Copas dispatcher main functions
are used to register servers and to execute the main loop of Copas:
- copas.addserver(server, handler[, timeout])
+ copas.addserver(server, handler[, timeout[, name]])
- Adds a new
server
and its handler
to the dispatcher
using an optional timeout
.
server
is a LuaSocket server socket created using
socket.bind()
.
handler
is a function that receives a LuaSocket client socket
- and handles the communication with that client.
+ and handles the communication with that client.
+ The handler will be executed in parallel with other threads and
+ registered handlers as long as it uses the Copas socket functions.
timeout
is the timeout in seconds. Upon accepting connections,
the timeout will be inherited by TCP client sockets (only applies to TCP).
- The handler will be executed in parallel with other threads and the
- registered handlers as long as it uses the Copas socket functions.
+ name
is the internal name to use for this socket. The handler threads and
+ (in case of TCP) the incoming client connections will get a name derived from the server socket.
+
+ - TCP: client-socket name:
"[server_name]:client_XX"
and the handler thread
+ "[server_name]:handler_XX"
where XX
is a sequential number matching
+ between the client-socket and handler.
+ - UDP: the handler thread will be named
"[server_name]:handler"
+
+
+
+ coro = copas.addnamedthread(func [, name [, ...]])
+ - Same as
copas.addthread
, but also names the new thread.
coro = copas.addthread(func [, ...])
@@ -158,6 +172,15 @@ Copas dispatcher main functions
See also copas.running
.
+ copas.getsocketname(skt)
+ - Returns the name for the socket.
+
+
+ copas.getthreadname([co])
+ - Returns the name for the coroutine/thread. If not given defaults to the
+ currently running coroutine.
+
+
func = copas.handler(connhandler [, sslparams])
- Wraps the
connhandler
function. Returns a new function that
wraps the client socket, and (if sslparams
is provided) performs
@@ -171,7 +194,8 @@ Copas dispatcher main functions
handlers. Every time a server accepts a connection, Copas calls the
associated handler passing the client socket returned by
socket.accept()
. The init_func
function is an
- optional initialization function that runs as a Copas thread.
+ optional initialization function that runs as a Copas thread
+ (with name "copas_initializer"
).
The timeout
parameter is optional,
and is passed to the copas.step()
function.
The loop returns when copas.finished() == true
.
@@ -206,6 +230,15 @@ Copas dispatcher main functions
on how to deal with the arguments when implementing your own.
+ copas.setsocketname(name, skt)
+ - Sets the name for the socket.
+
+
+ copas.setthreadname(name [,co])
+ - Sets the name for the coroutine/thread.
co
defaults to the
+ currently running coroutine.
+
+
copas.wrap(skt [, sslparams] )
- Wraps a LuaSocket socket and returns a Copas socket that implements LuaSocket's API
but use Copas' methods like
copas.send()
and copas.receive()
@@ -367,8 +400,19 @@ Non-blocking data exchange and timer/sleep functions
Returns list
or nil + "destroyed"
.
- queue.new()
- - Creates and returns a new queue.
+
queue.name
+ - A field set to name of the queue. See
queue.new()
.
+
+
+ queue.new([options])
+ - Creates and returns a new queue. The
options
table has the following
+ fields:
+
+ options.name
: (optional) the name for the queue, the default name will be
+ "copas_queue_XX"
. The name will be used to name any workers
+ added to the queue using queue:add_worker()
, their name will be
+ "[queue_name]:worker_XX"
+
queue:pop([timeout])
@@ -438,6 +482,8 @@ Non-blocking data exchange and timer/sleep functions
timer.new(options)
- Creates and returns an (armed) timer object. The
options
table has the following fields;
+ options.name
: (optional) the name for the timer, the default name will be
+ "copas_timer_XX"
. The name will be used to name the timer thread.
options.recurring
boolean
options.delay
expiry delay in seconds
options.initial_delay
(optional) see timer:arm()
@@ -639,6 +685,26 @@ Low level Copas functions
+Copas debugging functions
+
+These functions are mainly used for debugging Copas itself and should be considered experimental.
+
+
+
+ copas.debug.start([logger] [, core])
+ - This will internally replace coroutine handler functions to provide log output to
+ the provided
logger
function. The logger signature is function(...)
+ and the default value is the global print()
function.
+ If the core
parameter is truthy, then also the Copas core timer will be
+ logged (very noisy).
+
+
+ copas.debug.stop()
+ - Stops the debug output being generated.
+
+
+
+
diff --git a/src/copas.lua b/src/copas.lua
index 6173a0e..eeb23d3 100644
--- a/src/copas.lua
+++ b/src/copas.lua
@@ -95,7 +95,9 @@ local object_names = setmetatable({}, {
__mode = "k",
__index = function(self, key)
local name = tostring(key)
- rawset(self, key, name)
+ if key ~= nil then
+ rawset(self, key, name)
+ end
return name
end
})
@@ -672,13 +674,17 @@ local function ssl_wrap(skt, wrap_params)
end
ssl = ssl or require("ssl")
- local nskt, err = ssl.wrap(skt, wrap_params)
- if not nskt then
- return error(err) -- throw a hard error, because we do not want to silently ignore this one!!
- end
+ local nskt = assert(ssl.wrap(skt, wrap_params)) -- assert, because we do not want to silently ignore this one!!
+
nskt:settimeout(0) -- non-blocking on the ssl-socket
copas.settimeouts(nskt, user_timeouts_connect[skt],
user_timeouts_send[skt], user_timeouts_receive[skt]) -- copy copas user-timeout to newly wrapped one
+
+ local sock_name = object_names[skt]
+ if sock_name ~= tostring(skt) then
+ -- socket had a custom name, so copy it over
+ object_names[nskt] = sock_name
+ end
return nskt
end
@@ -941,7 +947,7 @@ end
local _errhandlers = setmetatable({}, { __mode = "k" }) -- error handler per coroutine
local function _deferror(msg, co, skt)
- msg = ("%s (coroutine: %s, socket: %s)"):format(tostring(msg), tostring(co), tostring(skt))
+ msg = ("%s (coroutine: %s, socket: %s)"):format(tostring(msg), object_names[co], object_names[skt])
if type(co) == "thread" then
-- regular Copas coroutine
msg = debug.traceback(co, msg)
@@ -1011,7 +1017,9 @@ local _accept do
client_skt:settimeout(0)
copas.settimeouts(client_skt, user_timeouts_connect[server_skt], -- copy server socket timeout settings
user_timeouts_send[server_skt], user_timeouts_receive[server_skt])
+
local co = coroutine_create(handler)
+ object_names[co] = object_names[server_skt] .. ":handler_" .. count
_doTick(co, client_skt)
end
end
@@ -1022,8 +1030,11 @@ end
-------------------------------------------------------------------------------
do
- local function addTCPserver(server, handler, timeout)
+ local function addTCPserver(server, handler, timeout, name)
server:settimeout(0)
+ if name then
+ object_names[server] = name
+ end
_servers[server] = handler
_reading:insert(server)
if timeout then
@@ -1031,9 +1042,13 @@ do
end
end
- local function addUDPserver(server, handler, timeout)
+ local function addUDPserver(server, handler, timeout, name)
server:settimeout(0)
local co = coroutine_create(handler)
+ if name then
+ object_names[server] = name
+ end
+ object_names[co] = object_names[server]..":handler"
_reading:insert(server)
if timeout then
copas.settimeout(server, timeout)
@@ -1043,9 +1058,6 @@ do
function copas.addserver(server, handler, timeout, name)
- if name then
- object_names[server] = name
- end
if isTCP(server) then
addTCPserver(server, handler, timeout, name)
else
@@ -1455,10 +1467,10 @@ do
debug_log("yielding '", name, "' to SLEEP for ", skt," seconds")
elseif queue == _writing then
- debug_log("yielding '", name, "' to WRITE to '", object_names[skt], "'")
+ debug_log("yielding '", name, "' to WRITE on '", object_names[skt], "'")
elseif queue == _reading then
- debug_log("yielding '", name, "' to READ from '", object_names[skt], "'")
+ debug_log("yielding '", name, "' to READ on '", object_names[skt], "'")
else
debug_log("thread '", name, "' yielding to unexpected queue; ", tostring(queue), " (", type(queue), ")", debug.traceback())
@@ -1499,9 +1511,7 @@ do
function copas.debug.start(logger, core)
log_core = core
- debug_log = logger or function(...)
- print(table.concat {...} )
- end
+ debug_log = logger or print
coroutine_yield = debug_yield
coroutine_resume = debug_resume
coroutine_create = debug_create