• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lunarmodules / copas / 30211614270

26 Jul 2026 04:59PM UTC coverage: 85.284% (+0.06%) from 85.229%
30211614270

push

github

web-flow
Merge 944934837 into 886ccdd3b

17 of 17 new or added lines in 2 files covered. (100.0%)

9 existing lines in 5 files now uncovered.

1443 of 1692 relevant lines covered (85.28%)

58072.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.12
/src/copas.lua
1
-------------------------------------------------------------------------------
2
-- Copas - Coroutine Oriented Portable Asynchronous Services
3
--
4
-- A dispatcher based on coroutines that can be used by TCP/IP servers.
5
-- Uses LuaSocket as the interface with the TCP/IP stack.
6
--
7
-- Authors: Andre Carregal, Javier Guerra, and Fabio Mascarenhas
8
-- Contributors: Diego Nehab, Mike Pall, David Burgess, Leonardo Godinho,
9
--               Thomas Harning Jr., and Gary NG
10
--
11
-- Copyright 2005-2013 - Kepler Project (www.keplerproject.org), 2015-2026 Thijs Schreijer
12
--
13
-- $Id: copas.lua,v 1.37 2009/04/07 22:09:52 carregal Exp $
14
-------------------------------------------------------------------------------
15

16
if package.loaded["socket.http"] and (_VERSION=="Lua 5.1") then     -- obsolete: only for Lua 5.1 compatibility
200✔
17
  error("you must require copas before require'ing socket.http")
×
18
end
19
if package.loaded["copas.http"] and (_VERSION=="Lua 5.1") then     -- obsolete: only for Lua 5.1 compatibility
200✔
20
  error("you must require copas before require'ing copas.http")
×
21
end
22

23
-- load either LuaSocket, or LuaSystem
24
-- note: with luasocket we don't use 'sleep' but 'select' with no sockets
25
local socket, system do
200✔
26
  if pcall(require, "socket") then
201✔
27
    -- found LuaSocket
28
    socket = require "socket"
194✔
29
  end
30

31
  -- try LuaSystem as fallback
32
  if pcall(require, "system") then
201✔
33
    system = require "system"
201✔
34
  end
35

36
  if not (socket or system) then
200✔
37
    error("Neither LuaSocket nor LuaSystem found, Copas requires at least one of them")
×
38
  end
39
end
40

41
local binaryheap = require "binaryheap"
200✔
42
local gettime = (socket or system).gettime
200✔
43
local block_sleep = (socket or system).sleep
200✔
44
local ssl -- only loaded upon demand
45

46
local core_timer_thread
47
local WATCH_DOG_TIMEOUT = 120
200✔
48
local UDP_DATAGRAM_MAX = (socket or {})._DATAGRAMSIZE or 8192
200✔
49
local TIMEOUT_PRECISION = 0.1  -- 100ms
200✔
50
local fnil = function() end
227,697✔
51

52

53
local coroutine_create = coroutine.create
200✔
54
local coroutine_running = coroutine.running
200✔
55
local coroutine_yield = coroutine.yield
200✔
56
local coroutine_resume = coroutine.resume
200✔
57
local coroutine_status = coroutine.status
200✔
58

59

60
-- nil-safe versions for pack/unpack
61
local _unpack = unpack or table.unpack
200✔
62
local unpack = function(t, i, j) return _unpack(t, i or 1, j or t.n or #t) end
760✔
63
local pack = function(...) return { n = select("#", ...), ...} end
1,034✔
64

65

66
local pcall = pcall
200✔
67
if _VERSION=="Lua 5.1" and not jit then     -- obsolete: only for Lua 5.1 compatibility
200✔
UNCOV
68
  pcall = require("coxpcall").pcall
32✔
UNCOV
69
  coroutine_running = require("coxpcall").running
32✔
70
end
71

72

73
if socket then
200✔
74
  -- Redefines LuaSocket functions with coroutine safe versions (pure Lua)
75
  -- (this allows the use of socket.http from within copas)
76
  local err_mt = {
194✔
77
    __tostring = function (self)
78
      return "Copas 'try' error intermediate table: '"..tostring(self[1].."'")
×
79
    end,
80
  }
81

82
  local function statusHandler(status, ...)
83
    if status then return ... end
108✔
84
    local err = (...)
54✔
85
    if type(err) == "table" and getmetatable(err) == err_mt then
54✔
86
      return nil, err[1]
54✔
87
    else
88
      error(err)
×
89
    end
90
  end
91

92
  function socket.protect(func)
194✔
93
    return function (...)
94
            return statusHandler(pcall(func, ...))
126✔
95
          end
96
  end
97

98
  function socket.newtry(finalizer)
194✔
99
    return function (...)
100
            local status = (...)
1,710✔
101
            if not status then
1,710✔
102
              pcall(finalizer or fnil, select(2, ...))
54✔
103
              error(setmetatable({ (select(2, ...)) }, err_mt), 0)
54✔
104
            end
105
            return ...
1,656✔
106
          end
107
  end
108

109
  socket.try = socket.newtry()
225✔
110
end
111

112

113
-- Setup the Copas meta table to auto-load submodules and define a default method
114
local copas do
200✔
115
  local submodules = { "ftp", "future", "http", "lock", "queue", "semaphore", "smtp", "timer" }
200✔
116
  for i, key in ipairs(submodules) do
1,800✔
117
    submodules[key] = true
1,600✔
118
    submodules[i] = nil
1,600✔
119
  end
120

121
  copas = setmetatable({},{
400✔
122
    __index = function(self, key)
123
      if submodules[key] then
266✔
124
        self[key] = require("copas."..key)
268✔
125
        submodules[key] = nil
266✔
126
        return rawget(self, key)
266✔
127
      end
128
    end,
129
    __call = function(self, ...)
130
      return self.loop(...)
6✔
131
    end,
132
  })
200✔
133
end
134

135

136
-- Meta information is public even if beginning with an "_"
137
copas._COPYRIGHT   = "Copyright (C) 2005-2013 Kepler Project, 2015-2026 Thijs Schreijer"
200✔
138
copas._DESCRIPTION = "Coroutine Oriented Portable Asynchronous Services"
200✔
139
copas._VERSION     = "Copas 4.11.0"
200✔
140

141
-- Close the socket associated with the current connection after the handler finishes
142
copas.autoclose = true
200✔
143

144
-- indicator for the loop running
145
copas.running = false
200✔
146

147
-- gettime method from either LuaSocket or LuaSystem: time in (fractional) seconds, since epoch.
148
copas.gettime = gettime
200✔
149

150
-------------------------------------------------------------------------------
151
-- Object names, to track names of thread/coroutines and sockets
152
-------------------------------------------------------------------------------
153
local object_names = setmetatable({}, {
400✔
154
  __mode = "k",
168✔
155
  __index = function(self, key)
156
    local name = tostring(key)
192✔
157
    if key ~= nil then
192✔
158
      rawset(self, key, name)
192✔
159
    end
160
    return name
192✔
161
  end
162
})
163

164
-------------------------------------------------------------------------------
165
-- Simple set implementation
166
-- adds a FIFO queue for each socket in the set
167
-------------------------------------------------------------------------------
168

169
local function newsocketset()
170
  local set = {}
600✔
171

172
  do  -- set implementation
173
    local reverse = {}
600✔
174

175
    -- Adds a socket to the set, does nothing if it exists
176
    -- @return skt if added, or nil if it existed
177
    function set:insert(skt)
600✔
178
      if not reverse[skt] then
1,320✔
179
        self[#self + 1] = skt
1,320✔
180
        reverse[skt] = #self
1,320✔
181
        return skt
1,320✔
182
      end
183
    end
184

185
    -- Removes socket from the set, does nothing if not found
186
    -- @return skt if removed, or nil if it wasn't in the set
187
    function set:remove(skt)
600✔
188
      local index = reverse[skt]
1,848✔
189
      if index then
1,848✔
190
        reverse[skt] = nil
1,314✔
191
        local top = self[#self]
1,314✔
192
        self[#self] = nil
1,314✔
193
        if top ~= skt then
1,314✔
194
          reverse[top] = index
168✔
195
          self[index] = top
168✔
196
        end
197
        return skt
1,314✔
198
      end
199
    end
200

201
  end
202

203
  do  -- queues implementation
204
    local fifo_queues = setmetatable({},{
1,200✔
205
      __mode = "k",                 -- auto collect queue if socket is gone
504✔
206
      __index = function(self, skt) -- auto create fifo queue if not found
207
        local newfifo = {}
510✔
208
        self[skt] = newfifo
510✔
209
        return newfifo
510✔
210
      end,
211
    })
212

213
    -- pushes an item in the fifo queue for the socket.
214
    function set:push(skt, itm)
600✔
215
      local queue = fifo_queues[skt]
1,224✔
216
      queue[#queue + 1] = itm
1,224✔
217
    end
218

219
    -- pops an item from the fifo queue for the socket
220
    function set:pop(skt)
600✔
221
      local queue = fifo_queues[skt]
1,146✔
222
      return table.remove(queue, 1)
1,146✔
223
    end
224

225
  end
226

227
  return set
600✔
228
end
229

230

231

232
-- Threads immediately resumable
233
local _resumable = {} do
200✔
234
  local resumelist = {}
200✔
235

236
  function _resumable:push(co)
200✔
237
    resumelist[#resumelist + 1] = co
227,275✔
238
  end
239

240
  function _resumable:clear_resumelist()
200✔
241
    local lst = resumelist
217,826✔
242
    resumelist = {}
217,826✔
243
    return lst
217,826✔
244
  end
245

246
  function _resumable:done()
200✔
247
    return resumelist[1] == nil
222,066✔
248
  end
249

250
  function _resumable:count()
200✔
251
    return #resumelist + #_resumable
×
252
  end
253

254
end
255

256

257

258
-- Similar to the socket set above, but tailored for the use of
259
-- sleeping threads
260
local _sleeping = {} do
200✔
261

262
  local heap = binaryheap.minUnique()
200✔
263
  local lethargy = setmetatable({}, { __mode = "k" }) -- list of coroutines sleeping without a wakeup time
200✔
264

265

266
  -- Required base implementation
267
  -----------------------------------------
268
  _sleeping.insert = fnil
200✔
269
  _sleeping.remove = fnil
200✔
270

271
  -- push a new timer on the heap
272
  function _sleeping:push(sleeptime, co)
200✔
273
    if sleeptime < 0 then
227,467✔
274
      lethargy[co] = true
3,378✔
275
    elseif sleeptime == 0 then
224,089✔
276
      _resumable:push(co)
279,768✔
277
    else
278
      heap:insert(gettime() + sleeptime, co)
7,938✔
279
    end
280
  end
281

282
  -- find the thread that should wake up to the time, if any
283
  function _sleeping:pop(time)
200✔
284
    if time < (heap:peekValue() or math.huge) then
290,767✔
285
      return
217,826✔
286
    end
287
    return heap:pop()
7,716✔
288
  end
289

290
  -- additional methods for time management
291
  -----------------------------------------
292
  function _sleeping:getnext()  -- returns delay until next sleep expires, or nil if there is none
200✔
293
    local t = heap:peekValue()
6,620✔
294
    if t then
6,620✔
295
      -- never report less than 0, because select() might block
296
      return math.max(t - gettime(), 0)
6,620✔
297
    end
298
  end
299

300
  function _sleeping:wakeup(co)
200✔
301
    if lethargy[co] then
3,378✔
302
      lethargy[co] = nil
3,318✔
303
      _resumable:push(co)
3,318✔
304
      return true
3,318✔
305
    end
306
    if heap:remove(co) then
70✔
307
      _resumable:push(co)
12✔
308
      return true
12✔
309
    end
310
    return nil, "not sleeping"
48✔
311
  end
312

313
  -- non-destructive check; unlike wakeup/cancel it doesn't remove 'co'
314
  function _sleeping:issleeping(co)
200✔
315
    if lethargy[co] then
606✔
316
      return true
564✔
317
    end
318
    return heap:valueByPayload(co) ~= nil
49✔
319
  end
320

321
  function _sleeping:cancel(co)
200✔
322
    lethargy[co] = nil
96✔
323
    heap:remove(co)
96✔
324
  end
325

326
  function _sleeping:cancelall()
200✔
327
    while heap:size() > 0 do heap:pop() end
×
328
    heap:insert(gettime() + TIMEOUT_PRECISION, core_timer_thread)
×
329
    -- lethargy is weak; copas's idle GC sweeps will clean it within a few steps
330
  end
331

332
  -- @param tos number of timeouts running
333
  function _sleeping:done(tos)
200✔
334
    -- return true if we have nothing more to do
335
    -- the timeout task doesn't qualify as work (fallbacks only),
336
    -- the lethargy also doesn't qualify as work ('dead' tasks),
337
    -- but the combination of a timeout + a lethargy can be work
338
    return heap:size() == 1       -- 1 means only the timeout-timer task is running
3,984✔
339
           and not (tos > 0 and next(lethargy))
3,420✔
340
  end
341

342
  -- gets number of threads in binaryheap and lethargy
343
  function _sleeping:status()
200✔
344
    local c = 0
×
345
    for _ in pairs(lethargy) do c = c + 1 end
×
346

347
    return heap:size(), c
×
348
  end
349

350
end   -- _sleeping
351

352

353

354
-------------------------------------------------------------------------------
355
-- Tracking coroutines and sockets
356
-------------------------------------------------------------------------------
357

358
local _servers = newsocketset() -- servers being handled
200✔
359
local _threads = setmetatable({}, {__mode = "k"})  -- registered threads added with addthread()
200✔
360
local _canceled = setmetatable({}, {__mode = "k"}) -- threads that are canceled and pending removal
200✔
361
local _autoclose = setmetatable({}, {__mode = "kv"}) -- sockets (value) to close when a thread (key) exits
200✔
362
local _autoclose_r = setmetatable({}, {__mode = "kv"}) -- reverse: sockets (key) to close when a thread (value) exits
200✔
363

364

365
-- for each socket we log the last read and last write times to enable the
366
-- watchdog to follow up if it takes too long.
367
-- tables contain the time, indexed by the socket
368
local _reading_log = {}
200✔
369
local _writing_log = {}
200✔
370

371
local _closed = {} -- track sockets that have been closed (list/array)
200✔
372

373
local _reading = newsocketset() -- sockets currently being read
200✔
374
local _writing = newsocketset() -- sockets currently being written
200✔
375
local _isSocketTimeout = { -- set of errors indicating a socket-timeout
200✔
376
  ["timeout"] = true,      -- default LuaSocket timeout
168✔
377
  ["wantread"] = true,     -- LuaSec specific timeout
168✔
378
  ["wantwrite"] = true,    -- LuaSec specific timeout
168✔
379
}
380

381
-------------------------------------------------------------------------------
382
-- Coroutine based socket timeouts.
383
-------------------------------------------------------------------------------
384
local user_timeouts_connect
385
local user_timeouts_send
386
local user_timeouts_receive
387
do
388
  local timeout_mt = {
200✔
389
    __mode = "k",
168✔
390
    __index = function(self, skt)
391
      -- if there is no timeout found, we insert one automatically, to block forever
392
      self[skt] = math.huge
432✔
393
      return self[skt]
432✔
394
    end,
395
  }
396

397
  user_timeouts_connect = setmetatable({}, timeout_mt)
200✔
398
  user_timeouts_send = setmetatable({}, timeout_mt)
200✔
399
  user_timeouts_receive = setmetatable({}, timeout_mt)
200✔
400
end
401

402
local useSocketTimeoutErrors = setmetatable({},{ __mode = "k" })
200✔
403

404

405
-- sto = socket-time-out
406
local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
200✔
407

408
  local socket_register = setmetatable({}, { __mode = "k" })    -- socket by coroutine
200✔
409
  local operation_register = setmetatable({}, { __mode = "k" }) -- operation "read"/"write" by coroutine
200✔
410
  local timeout_flags = setmetatable({}, { __mode = "k" })      -- true if timedout, by coroutine
200✔
411

412

413
  -- The callback called when a socket timeout occurs.
414
  local function socket_callback(co)
415
    local skt = socket_register[co]
78✔
416
    local queue = operation_register[co]
78✔
417

418
    -- flag the timeout and resume the coroutine
419
    timeout_flags[co] = true
78✔
420
    _resumable:push(co)
78✔
421

422
    -- clear the socket from the current queue
423
    if queue == "read" then
78✔
424
      _reading:remove(skt)
77✔
425
    elseif queue == "write" then
12✔
426
      _writing:remove(skt)
14✔
427
    else
428
      error("bad queue name; expected 'read'/'write', got: "..tostring(queue))
×
429
    end
430
  end
431

432

433
  -- Sets a socket timeout.
434
  -- Calling it as `sto_timeout()` will cancel the timeout.
435
  -- @param skt (socket) the socket on which to operate, use 'nil' to cancel the current timeout
436
  -- @param queue (string) the queue the socket is currently in: "read" or "write"
437
  -- @param use_connect_to (bool) if truthy, use the connect timeout instead of the
438
  --   read/write timeout implied by queue. Needed because connect also uses the "write"
439
  --   queue, so the queue value alone cannot distinguish connect from send operations.
440
  -- @return true
441
  function sto_timeout(skt, queue, use_connect_to)
168✔
442
    local co = coroutine_running()
4,052,460✔
443
    socket_register[co] = skt
4,052,460✔
444
    operation_register[co] = queue
4,052,460✔
445
    timeout_flags[co] = nil
4,052,460✔
446
    if skt then
4,052,460✔
447
      local to = (use_connect_to and user_timeouts_connect[skt]) or
2,026,241✔
448
                 (queue == "read" and user_timeouts_receive[skt]) or
2,025,914✔
449
                 user_timeouts_send[skt]
15,220✔
450
      copas.timeout(to, socket_callback)
2,633,434✔
451
    else
452
      copas.timeout(0)
2,026,230✔
453
    end
454
    return true
4,052,460✔
455
  end
456

457

458
  -- Changes the timeout to a different queue (read/write).
459
  -- Only usefull with ssl-handshakes and "wantread", "wantwrite" errors, when
460
  -- the queue has to be changed, so the timeout handler knows where to find the socket.
461
  -- @param queue (string) the new queue the socket is in, must be either "read" or "write"
462
  -- @return true
463
  function sto_change_queue(queue)
168✔
464
    operation_register[coroutine_running()] = queue
996✔
465
    return true
996✔
466
  end
467

468

469
  -- Responds with `true` if the operation timed-out.
470
  function sto_timed_out()
168✔
471
    return timeout_flags[coroutine_running()]
1,302✔
472
  end
473

474

475
  -- Returns the proper timeout error
476
  function sto_error(err)
168✔
477
    return useSocketTimeoutErrors[coroutine_running()] and err or "timeout"
78✔
478
  end
479

480
  -- only in case of testing export some internals
481
  if _G._TEST then
200✔
482
    copas._socket_register = socket_register
6✔
483
    copas._operation_register = operation_register
6✔
484
    copas._timeout_flags = timeout_flags
6✔
485
  end
486
end
487

488

489

490
-------------------------------------------------------------------------------
491
-- Coroutine based socket I/O functions.
492
-------------------------------------------------------------------------------
493

494
-- Returns "tcp"" for plain TCP and "ssl" for ssl-wrapped sockets, so truthy
495
-- for tcp based, and falsy for udp based.
496
local isTCP do
200✔
497
  local lookup = {
200✔
498
    tcp = "tcp",
168✔
499
    SSL = "ssl",
168✔
500
  }
501

502
  function isTCP(socket)
168✔
503
    return lookup[tostring(socket):sub(1,3)]
770✔
504
  end
505
end
506

507
function copas.close(skt, ...)
200✔
508
  _closed[#_closed+1] = skt
222✔
509
  return skt:close(...)
222✔
510
end
511

512

513

514
-- nil or negative is indefinitly
515
function copas.settimeout(skt, timeout)
200✔
516
  timeout = timeout or -1
216✔
517
  if type(timeout) ~= "number" then
216✔
518
    return nil, "timeout must be 'nil' or a number"
18✔
519
  end
520

521
  return copas.settimeouts(skt, timeout, timeout, timeout)
198✔
522
end
523

524
-- negative is indefinitly, nil means do not change
525
function copas.settimeouts(skt, connect, send, read)
200✔
526

527
  if connect ~= nil and type(connect) ~= "number" then
432✔
528
    return nil, "connect timeout must be 'nil' or a number"
×
529
  end
530
  if connect then
432✔
531
    if connect < 0 then
432✔
532
      connect = nil
×
533
    end
534
    user_timeouts_connect[skt] = connect
432✔
535
  end
536

537

538
  if send ~= nil and type(send) ~= "number" then
432✔
539
    return nil, "send timeout must be 'nil' or a number"
×
540
  end
541
  if send then
432✔
542
    if send < 0 then
432✔
543
      send = nil
×
544
    end
545
    user_timeouts_send[skt] = send
432✔
546
  end
547

548

549
  if read ~= nil and type(read) ~= "number" then
432✔
550
    return nil, "read timeout must be 'nil' or a number"
×
551
  end
552
  if read then
432✔
553
    if read < 0 then
432✔
554
      read = nil
×
555
    end
556
    user_timeouts_receive[skt] = read
432✔
557
  end
558

559

560
  return true
432✔
561
end
562

563
-- reads a pattern from a client and yields to the reading set on timeouts
564
-- UDP: a UDP socket expects a second argument to be a number, so it MUST
565
-- be provided as the 'pattern' below defaults to a string. Will throw a
566
-- 'bad argument' error if omitted.
567
-- SECURITY: the default pattern "*l" has no maximum length, matching LuaSocket
568
-- and Lua file-io semantics. It buffers until a newline/EOF/error, and the
569
-- per-operation timeout resets on every partial receive, so it does not bound
570
-- the accumulated size either. Do not use the default line-read directly on
571
-- untrusted/remote input without an application-enforced size limit; use a
572
-- numeric (sized) pattern or receivepartial with your own cumulative cap instead.
573
function copas.receive(client, pattern, part)
200✔
574
  local s, err
575
  pattern = pattern or "*l"
2,010,656✔
576
  local current_log = _reading_log
2,010,656✔
577
  sto_timeout(client, "read")
2,010,656✔
578

579
  repeat
580
    s, err, part = client:receive(pattern, part)
2,011,281✔
581

582
    -- guarantees that high throughput doesn't take other threads to starvation
583
    if (math.random(100) > 90) then
2,011,281✔
584
      copas.pause()
201,261✔
585
    end
586

587
    if s then
2,011,281✔
588
      current_log[client] = nil
2,010,548✔
589
      sto_timeout()
2,010,548✔
590
      return s, err, part
2,010,548✔
591

592
    elseif not _isSocketTimeout[err] then
733✔
593
      current_log[client] = nil
48✔
594
      sto_timeout()
48✔
595
      return s, err, part
48✔
596

597
    elseif sto_timed_out() then
797✔
598
      current_log[client] = nil
60✔
599
      sto_timeout()
60✔
600
      return nil, sto_error(err), part
70✔
601
    end
602

603
    if err == "wantwrite" then -- wantwrite may be returned during SSL renegotiations
625✔
604
      current_log = _writing_log
×
605
      current_log[client] = gettime()
×
606
      sto_change_queue("write")
×
607
      coroutine_yield(client, _writing)
×
608
    else
609
      current_log = _reading_log
625✔
610
      current_log[client] = gettime()
625✔
611
      sto_change_queue("read")
625✔
612
      coroutine_yield(client, _reading)
625✔
613
    end
614
  until false
625✔
615
end
616

617
-- receives data from a client over UDP. Not available for TCP.
618
-- (this is a copy of receive() method, adapted for receivefrom() use)
619
function copas.receivefrom(client, size)
200✔
620
  local s, err, port
621
  size = size or UDP_DATAGRAM_MAX
24✔
622
  sto_timeout(client, "read")
24✔
623

624
  repeat
625
    s, err, port = client:receivefrom(size) -- upon success err holds ip address
48✔
626

627
    -- garantees that high throughput doesn't take other threads to starvation
628
    if (math.random(100) > 90) then
48✔
629
      copas.pause()
9✔
630
    end
631

632
    if s then
48✔
633
      _reading_log[client] = nil
18✔
634
      sto_timeout()
18✔
635
      return s, err, port
18✔
636

637
    elseif err ~= "timeout" then
30✔
638
      _reading_log[client] = nil
×
639
      sto_timeout()
×
640
      return s, err, port
×
641

642
    elseif sto_timed_out() then
35✔
643
      _reading_log[client] = nil
6✔
644
      sto_timeout()
6✔
645
      return nil, sto_error(err), port
7✔
646
    end
647

648
    _reading_log[client] = gettime()
24✔
649
    coroutine_yield(client, _reading)
24✔
650
  until false
24✔
651
end
652

653
-- same as above but with special treatment when reading chunks,
654
-- unblocks on any data received.
655
function copas.receivepartial(client, pattern, part)
200✔
656
  local s, err
657
  pattern = pattern or "*l"
12✔
658
  local orig_size = #(part or "")
12✔
659
  local current_log = _reading_log
12✔
660
  sto_timeout(client, "read")
12✔
661

662
  repeat
663
    s, err, part = client:receive(pattern, part)
18✔
664

665
    -- guarantees that high throughput doesn't take other threads to starvation
666
    if (math.random(100) > 90) then
18✔
UNCOV
667
      copas.pause()
×
668
    end
669

670
    if s or (type(part) == "string" and #part > orig_size) then
18✔
671
      current_log[client] = nil
12✔
672
      sto_timeout()
12✔
673
      return s, err, part
12✔
674

675
    elseif not _isSocketTimeout[err] then
6✔
676
      current_log[client] = nil
×
677
      sto_timeout()
×
678
      return s, err, part
×
679

680
    elseif sto_timed_out() then
7✔
681
      current_log[client] = nil
×
682
      sto_timeout()
×
683
      return nil, sto_error(err), part
×
684
    end
685

686
    if err == "wantwrite" then
6✔
687
      current_log = _writing_log
×
688
      current_log[client] = gettime()
×
689
      sto_change_queue("write")
×
690
      coroutine_yield(client, _writing)
×
691
    else
692
      current_log = _reading_log
6✔
693
      current_log[client] = gettime()
6✔
694
      sto_change_queue("read")
6✔
695
      coroutine_yield(client, _reading)
6✔
696
    end
697
  until false
6✔
698
end
699
copas.receivePartial = copas.receivepartial  -- compat: receivePartial is deprecated
200✔
700

701
-- sends data to a client. The operation is buffered and
702
-- yields to the writing set on timeouts
703
-- Note: from and to parameters will be ignored by/for UDP sockets
704
function copas.send(client, data, from, to)
200✔
705
  local s, err
706
  from = from or 1
15,220✔
707
  local lastIndex = from - 1
15,220✔
708
  local current_log = _writing_log
15,220✔
709
  sto_timeout(client, "write")
15,220✔
710

711
  repeat
712
    s, err, lastIndex = client:send(data, lastIndex + 1, to)
15,399✔
713

714
    -- guarantees that high throughput doesn't take other threads to starvation
715
    if (math.random(100) > 90) then
15,399✔
716
      copas.pause()
1,490✔
717
    end
718

719
    if s then
15,399✔
720
      current_log[client] = nil
15,196✔
721
      sto_timeout()
15,196✔
722
      return s, err, lastIndex
15,196✔
723

724
    elseif not _isSocketTimeout[err] then
203✔
725
      current_log[client] = nil
24✔
726
      sto_timeout()
24✔
727
      return s, err, lastIndex
24✔
728

729
    elseif sto_timed_out() then
207✔
730
      current_log[client] = nil
×
731
      sto_timeout()
×
732
      return nil, sto_error(err), lastIndex
×
733
    end
734

735
    if err == "wantread" then
179✔
736
      current_log = _reading_log
×
737
      current_log[client] = gettime()
×
738
      sto_change_queue("read")
×
739
      coroutine_yield(client, _reading)
×
740
    else
741
      current_log = _writing_log
179✔
742
      current_log[client] = gettime()
179✔
743
      sto_change_queue("write")
179✔
744
      coroutine_yield(client, _writing)
179✔
745
    end
746
  until false
179✔
747
end
748

749
function copas.sendto(client, data, ip, port)
200✔
750
  -- deprecated; for backward compatibility only, since UDP doesn't block on sending
751
  return client:sendto(data, ip, port)
×
752
end
753

754
-- waits until connection is completed
755
function copas.connect(skt, host, port)
200✔
756
  skt:settimeout(0)
212✔
757
  local ret, err, tried_more_than_once
758
  sto_timeout(skt, "write", true)
210✔
759

760
  repeat
761
    ret, err = skt:connect(host, port)
422✔
762

763
    -- non-blocking connect on Windows results in error "Operation already
764
    -- in progress" to indicate that it is completing the request async. So essentially
765
    -- it is the same as "timeout"
766
    if ret or (err ~= "timeout" and err ~= "Operation already in progress") then
414✔
767
      _writing_log[skt] = nil
198✔
768
      sto_timeout()
198✔
769
      -- Once the async connect completes, Windows returns the error "already connected"
770
      -- to indicate it is done, so that error should be ignored. Except when it is the
771
      -- first call to connect, then it was already connected to something else and the
772
      -- error should be returned
773
      if (not ret) and (err == "already connected" and tried_more_than_once) then
198✔
774
        return 1
×
775
      end
776
      return ret, err
198✔
777

778
    elseif sto_timed_out() then
252✔
779
      _writing_log[skt] = nil
12✔
780
      sto_timeout()
12✔
781
      return nil, sto_error(err)
14✔
782
    end
783

784
    tried_more_than_once = tried_more_than_once or true
204✔
785
    _writing_log[skt] = gettime()
204✔
786
    coroutine_yield(skt, _writing)
204✔
787
  until false
204✔
788
end
789

790

791
-- Wraps a tcp socket in an ssl socket and configures it. If the socket was
792
-- already wrapped, it does nothing and returns the socket.
793
-- @param wrap_params the parameters for the ssl-context
794
-- @return wrapped socket, or throws an error
795
local function ssl_wrap(skt, wrap_params)
796
  if isTCP(skt) == "ssl" then return skt end -- was already wrapped
224✔
797
  if not wrap_params then
108✔
798
    error("cannot wrap socket into a secure socket (using 'ssl.wrap()') without parameters/context")
×
799
  end
800

801
  ssl = ssl or require("ssl")
108✔
802
  local nskt = assert(ssl.wrap(skt, wrap_params)) -- assert, because we do not want to silently ignore this one!!
126✔
803

804
  nskt:settimeout(0)  -- non-blocking on the ssl-socket
108✔
805
  copas.settimeouts(nskt, user_timeouts_connect[skt],
216✔
806
    user_timeouts_send[skt], user_timeouts_receive[skt]) -- copy copas user-timeout to newly wrapped one
112✔
807

808
  local co = _autoclose_r[skt]
108✔
809
  if co then
108✔
810
    -- socket registered for autoclose, move registration to wrapped one
811
    _autoclose[co] = nskt
24✔
812
    _autoclose_r[skt] = nil
24✔
813
    _autoclose_r[nskt] = co
24✔
814
  end
815

816
  local sock_name = object_names[skt]
108✔
817
  if sock_name ~= tostring(skt) then
108✔
818
    -- socket had a custom name, so copy it over
819
    object_names[nskt] = sock_name
36✔
820
  end
821
  return nskt
108✔
822
end
823

824

825
-- For each luasec method we have a subtable, allows for future extension.
826
-- Required structure:
827
-- {
828
--   wrap = ... -- parameter to 'wrap()'; the ssl parameter table, or the context object
829
--   sni = {                  -- parameters to 'sni()'
830
--     names = string | table -- 1st parameter
831
--     strict = bool          -- 2nd parameter
832
--   }
833
-- }
834
local function normalize_sslt(sslt)
835
  local t = type(sslt)
348✔
836
  local r = setmetatable({}, {
696✔
837
    __index = function(self, key)
838
      -- a bug if this happens, here as a sanity check, just being careful since
839
      -- this is security stuff
840
      error("accessing unknown 'ssl_params' table key: "..tostring(key))
×
841
    end,
842
  })
843
  if t == "nil" then
348✔
844
    r.wrap = false
234✔
845
    r.sni = false
234✔
846

847
  elseif t == "table" then
114✔
848
    if sslt.mode or sslt.protocol then
114✔
849
      -- has the mandatory fields for the ssl-params table for handshake
850
      -- backward compatibility
851
      r.wrap = sslt
24✔
852
      r.sni = false
24✔
853
    else
854
      -- has the target definition, copy our known keys
855
      r.wrap = sslt.wrap or false -- 'or false' because we do not want nils
90✔
856
      r.sni = sslt.sni or false -- 'or false' because we do not want nils
90✔
857
    end
858

859
  elseif t == "userdata" then
×
860
    -- it's an ssl-context object for the handshake
861
    -- backward compatibility
862
    r.wrap = sslt
×
863
    r.sni = false
×
864

865
  else
866
    error("ssl parameters; did not expect type "..tostring(sslt))
×
867
  end
868

869
  return r
348✔
870
end
871

872

873
---
874
-- Peforms an (async) ssl handshake on a connected TCP client socket.
875
-- NOTE: if not ssl-wrapped already, then replace all previous socket references, with the returned new ssl wrapped socket
876
-- Throws error and does not return nil+error, as that might silently fail
877
-- in code like this;
878
--   copas.addserver(s1, function(skt)
879
--       skt = copas.wrap(skt, sparams)
880
--       skt:dohandshake()   --> without explicit error checking, this fails silently and
881
--       skt:send(body)      --> continues unencrypted
882
-- @param skt Regular LuaSocket CLIENT socket object
883
-- @param wrap_params Table with ssl parameters
884
-- @return wrapped ssl socket, or throws an error
885
function copas.dohandshake(skt, wrap_params)
200✔
886
  ssl = ssl or require("ssl")
108✔
887

888
  local nskt = ssl_wrap(skt, wrap_params)
108✔
889

890
  sto_timeout(nskt, "write", true)
108✔
891
  local queue
892

893
  repeat
894
    local success, err = nskt:dohandshake()
294✔
895

896
    if success then
294✔
897
      sto_timeout()
96✔
898
      return nskt
96✔
899

900
    elseif not _isSocketTimeout[err] then
198✔
901
      sto_timeout()
12✔
902
      error("TLS/SSL handshake failed: " .. tostring(err))
12✔
903

904
    elseif sto_timed_out() then
217✔
905
      sto_timeout()
×
906
      return nil, sto_error(err)
×
907

908
    elseif err == "wantwrite" then
186✔
909
      sto_change_queue("write")
×
910
      queue = _writing
×
911

912
    elseif err == "wantread" then
186✔
913
      sto_change_queue("read")
186✔
914
      queue = _reading
186✔
915

916
    else
917
      error("TLS/SSL handshake failed: " .. tostring(err))
×
918
    end
919

920
    coroutine_yield(nskt, queue)
186✔
921
  until false
186✔
922
end
923

924
-- flushes a client write buffer (deprecated)
925
function copas.flush()
200✔
926
end
927

928
-- wraps a TCP socket to use Copas methods (send, receive, flush and settimeout)
929
local _skt_mt_tcp = {
200✔
930
      __tostring = function(self)
931
        return tostring(self.socket).." (copas wrapped)"
18✔
932
      end,
933

934
      __index = {
200✔
935
        send = function (self, data, from, to)
936
          return copas.send (self.socket, data, from, to)
15,214✔
937
        end,
938

939
        receive = function (self, pattern, prefix)
940
          if user_timeouts_receive[self.socket] == 0 then
2,010,651✔
941
            return copas.receivepartial(self.socket, pattern, prefix)
12✔
942
          end
943
          return copas.receive(self.socket, pattern, prefix)
2,010,638✔
944
        end,
945

946
        receivepartial = function (self, pattern, prefix)
947
          return copas.receivepartial(self.socket, pattern, prefix)
×
948
        end,
949

950
        flush = function (self)
951
          return copas.flush(self.socket)
×
952
        end,
953

954
        settimeout = function (self, time)
955
          return copas.settimeout(self.socket, time)
198✔
956
        end,
957

958
        settimeouts = function (self, connect, send, receive)
959
          return copas.settimeouts(self.socket, connect, send, receive)
×
960
        end,
961

962
        -- TODO: socket.connect is a shortcut, and must be provided with an alternative
963
        -- if ssl parameters are available, it will also include a handshake
964
        connect = function(self, ...)
965
          local res, err = copas.connect(self.socket, ...)
210✔
966
          if res then
210✔
967
            if self.ssl_params.sni then self:sni() end
192✔
968
            if self.ssl_params.wrap then res, err = self:dohandshake() end
205✔
969
          end
970
          return res, err
204✔
971
        end,
972

973
        close = function(self, ...)
974
          return copas.close(self.socket, ...)
222✔
975
        end,
976

977
        -- TODO: socket.bind is a shortcut, and must be provided with an alternative
978
        bind = function(self, ...) return self.socket:bind(...) end,
200✔
979

980
        -- TODO: is this DNS related? hence blocking?
981
        getsockname = function(self, ...)
982
          local ok, ip, port, family = pcall(self.socket.getsockname, self.socket, ...)
×
983
          if ok then
×
984
            return ip, port, family
×
985
          else
986
            return nil, "not implemented by LuaSec"
×
987
          end
988
        end,
989

990
        getstats = function(self, ...) return self.socket:getstats(...) end,
200✔
991

992
        setstats = function(self, ...) return self.socket:setstats(...) end,
200✔
993

994
        listen = function(self, ...) return self.socket:listen(...) end,
200✔
995

996
        accept = function(self, ...) return self.socket:accept(...) end,
200✔
997

998
        setoption = function(self, ...)
999
          local ok, res, err = pcall(self.socket.setoption, self.socket, ...)
×
1000
          if ok then
×
1001
            return res, err
×
1002
          else
1003
            return nil, "not implemented by LuaSec"
×
1004
          end
1005
        end,
1006

1007
        getoption = function(self, ...)
1008
          local ok, val, err = pcall(self.socket.getoption, self.socket, ...)
×
1009
          if ok then
×
1010
            return val, err
×
1011
          else
1012
            return nil, "not implemented by LuaSec"
×
1013
          end
1014
        end,
1015

1016
        -- TODO: is this DNS related? hence blocking?
1017
        getpeername = function(self, ...)
1018
          local ok, ip, port, family = pcall(self.socket.getpeername, self.socket, ...)
×
1019
          if ok then
×
1020
            return ip, port, family
×
1021
          else
1022
            return nil, "not implemented by LuaSec"
×
1023
          end
1024
        end,
1025

1026
        shutdown = function(self, ...) return self.socket:shutdown(...) end,
200✔
1027

1028
        sni = function(self, names, strict)
1029
          local sslp = self.ssl_params
84✔
1030
          self.socket = ssl_wrap(self.socket, sslp.wrap)
98✔
1031
          if names == nil then
84✔
1032
            names = sslp.sni.names
72✔
1033
            strict = sslp.sni.strict
72✔
1034
          end
1035
          return self.socket:sni(names, strict)
84✔
1036
        end,
1037

1038
        dohandshake = function(self, wrap_params)
1039
          local nskt, err = copas.dohandshake(self.socket, wrap_params or self.ssl_params.wrap)
108✔
1040
          if not nskt then return nskt, err end
96✔
1041
          self.socket = nskt  -- replace internal socket with the newly wrapped ssl one
96✔
1042
          return self
96✔
1043
        end,
1044

1045
        getalpn = function(self, ...)
1046
          local ok, proto, err = pcall(self.socket.getalpn, self.socket, ...)
×
1047
          if ok then
×
1048
            return proto, err
×
1049
          else
1050
            return nil, "not a tls socket"
×
1051
          end
1052
        end,
1053

1054
        getsniname = function(self, ...)
1055
          local ok, name, err = pcall(self.socket.getsniname, self.socket, ...)
×
1056
          if ok then
×
1057
            return name, err
×
1058
          else
1059
            return nil, "not a tls socket"
×
1060
          end
1061
        end,
1062
      }
200✔
1063
}
1064

1065
-- wraps a UDP socket, copy of TCP one adapted for UDP.
1066
local _skt_mt_udp = {__index = { }}
200✔
1067
for k,v in pairs(_skt_mt_tcp) do _skt_mt_udp[k] = _skt_mt_udp[k] or v end
600✔
1068
for k,v in pairs(_skt_mt_tcp.__index) do _skt_mt_udp.__index[k] = v end
4,600✔
1069

1070
_skt_mt_udp.__index.send        = function(self, ...) return self.socket:send(...) end
206✔
1071

1072
_skt_mt_udp.__index.sendto      = function(self, ...) return self.socket:sendto(...) end
218✔
1073

1074

1075
_skt_mt_udp.__index.receive =     function (self, size)
200✔
1076
                                    return copas.receive (self.socket, (size or UDP_DATAGRAM_MAX))
12✔
1077
                                  end
1078

1079
_skt_mt_udp.__index.receivefrom = function (self, size)
200✔
1080
                                    return copas.receivefrom (self.socket, (size or UDP_DATAGRAM_MAX))
24✔
1081
                                  end
1082

1083
                                  -- TODO: is this DNS related? hence blocking?
1084
_skt_mt_udp.__index.setpeername = function(self, ...) return self.socket:setpeername(...) end
206✔
1085

1086
_skt_mt_udp.__index.setsockname = function(self, ...) return self.socket:setsockname(...) end
200✔
1087

1088
                                    -- do not close client, as it is also the server for udp.
1089
_skt_mt_udp.__index.close       = function(self, ...) return true end
212✔
1090

1091
_skt_mt_udp.__index.settimeouts = function (self, connect, send, receive)
200✔
1092
                                    return copas.settimeouts(self.socket, connect, send, receive)
×
1093
                                  end
1094

1095

1096

1097
---
1098
-- Wraps a LuaSocket socket object in an async Copas based socket object.
1099
-- @param skt The socket to wrap
1100
-- @sslt (optional) Table with ssl parameters, use an empty table to use ssl with defaults
1101
-- @return wrapped socket object
1102
function copas.wrap (skt, sslt)
200✔
1103
  if (getmetatable(skt) == _skt_mt_tcp) or (getmetatable(skt) == _skt_mt_udp) then
372✔
1104
    return skt -- already wrapped
×
1105
  end
1106

1107
  skt:settimeout(0)
374✔
1108

1109
  if isTCP(skt) then
434✔
1110
    return setmetatable ({socket = skt, ssl_params = normalize_sslt(sslt)}, _skt_mt_tcp)
406✔
1111
  else
1112
    return setmetatable ({socket = skt}, _skt_mt_udp)
24✔
1113
  end
1114
end
1115

1116
--- Wraps a handler in a function that deals with wrapping the socket and doing the
1117
-- optional ssl handshake.
1118
function copas.handler(handler, sslparams)
200✔
1119
  -- TODO: pass a timeout value to set, and use during handshake
1120
  return function (skt, ...)
1121
    skt = copas.wrap(skt, sslparams) -- this call will normalize the sslparams table
112✔
1122
    local sslp = skt.ssl_params
96✔
1123
    if sslp.sni then skt:sni(sslp.sni.names, sslp.sni.strict) end
96✔
1124
    if sslp.wrap then skt:dohandshake(sslp.wrap) end
96✔
1125
    return handler(skt, ...)
90✔
1126
  end
1127
end
1128

1129

1130
--------------------------------------------------
1131
-- Error handling
1132
--------------------------------------------------
1133

1134
local _errhandlers = setmetatable({}, { __mode = "k" })   -- error handler per coroutine
200✔
1135

1136

1137
function copas.gettraceback(msg, co, skt)
200✔
1138
  local co_str = co == nil and "nil" or copas.getthreadname(co)
38✔
1139
  local skt_str = skt == nil and "nil" or copas.getsocketname(skt)
38✔
1140
  local msg_str = msg == nil and "" or tostring(msg)
38✔
1141
  if msg_str == "" then
38✔
1142
    msg_str = ("(coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str)
×
1143
  else
1144
    msg_str = ("%s (coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str)
38✔
1145
  end
1146

1147
  if type(co) == "thread" then
38✔
1148
    -- regular Copas coroutine
1149
    return debug.traceback(co, msg_str)
38✔
1150
  end
1151
  -- not a coroutine, but the main thread, this happens if a timeout callback
1152
  -- (see `copas.timeout` causes an error (those callbacks run on the main thread).
1153
  return debug.traceback(msg_str, 2)
×
1154
end
1155

1156

1157
local function _deferror(msg, co, skt)
1158
  print(copas.gettraceback(msg, co, skt))
29✔
1159
end
1160

1161

1162
function copas.seterrorhandler(err, default)
200✔
1163
  assert(err == nil or type(err) == "function", "Expected the handler to be a function, or nil")
60✔
1164
  if default then
60✔
1165
    assert(err ~= nil, "Expected the handler to be a function when setting the default")
42✔
1166
    _deferror = err
42✔
1167
  else
1168
    _errhandlers[coroutine_running()] = err
18✔
1169
  end
1170
end
1171
copas.setErrorHandler = copas.seterrorhandler  -- deprecated; old casing
200✔
1172

1173

1174
function copas.geterrorhandler(co)
200✔
1175
  co = co or coroutine_running()
12✔
1176
  return _errhandlers[co] or _deferror
12✔
1177
end
1178

1179

1180
-- if `bool` is truthy, then the original socket errors will be returned in case of timeouts;
1181
-- `timeout, wantread, wantwrite, Operation already in progress`. If falsy, it will always
1182
-- return `timeout`.
1183
function copas.useSocketTimeoutErrors(bool)
200✔
1184
  useSocketTimeoutErrors[coroutine_running()] = not not bool -- force to a boolean
6✔
1185
end
1186

1187
-------------------------------------------------------------------------------
1188
-- Thread handling
1189
-------------------------------------------------------------------------------
1190

1191
local function _doTick (co, skt, ...)
1192
  if not co then return end
234,129✔
1193

1194
  -- if a coroutine was canceled/removed, don't resume it
1195
  if _canceled[co] then
234,129✔
1196
    _canceled[co] = nil -- also clean up the registry
30✔
1197
    _threads[co] = nil
30✔
1198
    return
30✔
1199
  end
1200

1201
  -- res: the socket (being read/write on) or the time to sleep
1202
  -- new_q: either _writing, _reading, or _sleeping
1203
  -- local time_before = gettime()
1204
  local ok, res, new_q = coroutine_resume(co, skt, ...)
234,099✔
1205
  -- local duration = gettime() - time_before
1206
  -- if duration > 1 then
1207
  --   duration = math.floor(duration * 1000)
1208
  --   pcall(_errhandlers[co] or _deferror, "task ran for "..tostring(duration).." milliseconds.", co, skt)
1209
  -- end
1210

1211
  if new_q == _reading or new_q == _writing or new_q == _sleeping then
234,087✔
1212
    -- we're yielding to a new queue
1213
    new_q:insert (res)
228,691✔
1214
    new_q:push (res, co)
228,691✔
1215
    return
228,691✔
1216
  end
1217

1218
  -- coroutine is terminating
1219

1220
  if ok and coroutine_status(co) ~= "dead" then
5,396✔
1221
    -- it called coroutine.yield from a non-Copas function which is unexpected
1222
    ok = false
6✔
1223
    res = "coroutine.yield was called without a resume first, user-code cannot yield to Copas"
6✔
1224
  end
1225

1226
  if not ok then
5,396✔
1227
    local k, e = pcall(_errhandlers[co] or _deferror, res, co, skt)
46✔
1228
    if not k then
46✔
1229
      print("Failed executing error handler: " .. tostring(e))
×
1230
    end
1231
  end
1232

1233
  local skt_to_close = _autoclose[co]
5,396✔
1234
  if skt_to_close then
5,396✔
1235
    skt_to_close:close()
120✔
1236
    _autoclose[co] = nil
120✔
1237
    _autoclose_r[skt_to_close] = nil
120✔
1238
  end
1239

1240
  _errhandlers[co] = nil
5,396✔
1241
end
1242

1243

1244
local _accept do
200✔
1245
  local client_counters = setmetatable({}, { __mode = "k" })
200✔
1246

1247
  -- accepts a connection on socket input
1248
  function _accept(server_skt, handler)
168✔
1249
    local client_skt = server_skt:accept()
126✔
1250
    if client_skt then
126✔
1251
      local count = (client_counters[server_skt] or 0) + 1
126✔
1252
      client_counters[server_skt] = count
126✔
1253
      object_names[client_skt] = object_names[server_skt] .. ":client_" .. count
140✔
1254

1255
      client_skt:settimeout(0)
126✔
1256
      copas.settimeouts(client_skt, user_timeouts_connect[server_skt],  -- copy server socket timeout settings
252✔
1257
        user_timeouts_send[server_skt], user_timeouts_receive[server_skt])
140✔
1258

1259
      local co = coroutine_create(handler)
126✔
1260
      object_names[co] = object_names[server_skt] .. ":handler_" .. count
126✔
1261

1262
      if copas.autoclose then
126✔
1263
        _autoclose[co] = client_skt
126✔
1264
        _autoclose_r[client_skt] = co
126✔
1265
      end
1266

1267
      _doTick(co, client_skt)
126✔
1268
    end
1269
  end
1270
end
1271

1272
-------------------------------------------------------------------------------
1273
-- Adds a server/handler pair to Copas dispatcher
1274
-------------------------------------------------------------------------------
1275

1276
do
1277
  local function addTCPserver(server, handler, timeout, name)
1278
    server:settimeout(0)
96✔
1279
    if name then
96✔
1280
      object_names[server] = name
×
1281
    end
1282
    _servers[server] = handler
96✔
1283
    _reading:insert(server)
96✔
1284
    if timeout then
96✔
1285
      copas.settimeout(server, timeout)
18✔
1286
    end
1287
  end
1288

1289
  local function addUDPserver(server, handler, timeout, name)
1290
    server:settimeout(0)
×
1291
    local co = coroutine_create(handler)
×
1292
    if name then
×
1293
      object_names[server] = name
×
1294
    end
1295
    object_names[co] = object_names[server]..":handler"
×
1296
    _reading:insert(server)
×
1297
    if timeout then
×
1298
      copas.settimeout(server, timeout)
×
1299
    end
1300
    _doTick(co, server)
×
1301
  end
1302

1303

1304
  function copas.addserver(server, handler, timeout, name)
200✔
1305
    if isTCP(server) then
112✔
1306
      addTCPserver(server, handler, timeout, name)
112✔
1307
    else
1308
      addUDPserver(server, handler, timeout, name)
×
1309
    end
1310
  end
1311
end
1312

1313

1314
function copas.removeserver(server, keep_open)
200✔
1315
  local skt = server
90✔
1316
  local mt = getmetatable(server)
90✔
1317
  if mt == _skt_mt_tcp or mt == _skt_mt_udp then
90✔
1318
    skt = server.socket
×
1319
  end
1320

1321
  _servers:remove(skt)
90✔
1322
  _reading:remove(skt)
90✔
1323

1324
  if keep_open then
90✔
1325
    return true
18✔
1326
  end
1327
  return server:close()
72✔
1328
end
1329

1330

1331

1332
-------------------------------------------------------------------------------
1333
-- Adds an new coroutine thread to Copas dispatcher
1334
-------------------------------------------------------------------------------
1335
function copas.addnamedthread(name, handler, ...)
200✔
1336
  if type(name) == "function" and type(handler) == "string" then
5,590✔
1337
    -- old call, flip args for compatibility
1338
    name, handler = handler, name
×
1339
  end
1340

1341
  -- create a coroutine that skips the first argument, which is always the socket
1342
  -- passed by the scheduler, but `nil` in case of a task/thread
1343
  local thread = coroutine_create(function(_, ...)
11,180✔
1344
    copas.pause()
5,590✔
1345
    return handler(...)
5,566✔
1346
  end)
1347
  if name then
5,590✔
1348
    object_names[thread] = name
526✔
1349
  end
1350

1351
  _threads[thread] = true -- register this thread so it can be removed
5,590✔
1352
  _doTick (thread, nil, ...)
5,590✔
1353
  return thread
5,590✔
1354
end
1355

1356

1357
function copas.addthread(handler, ...)
200✔
1358
  return copas.addnamedthread(nil, handler, ...)
4,998✔
1359
end
1360

1361

1362
function copas.removethread(thread)
200✔
1363
  -- if the specified coroutine is registered, add it to the canceled table so
1364
  -- that next time it tries to resume it exits.
1365
  _canceled[thread] = _threads[thread or 0]
96✔
1366
  _sleeping:cancel(thread)
96✔
1367
end
1368

1369

1370

1371
-------------------------------------------------------------------------------
1372
-- Sleep/pause management functions
1373
-------------------------------------------------------------------------------
1374

1375
-- yields the current coroutine and wakes it after 'sleeptime' seconds.
1376
-- If sleeptime < 0 then it sleeps until explicitly woken up using 'wakeup'
1377
-- TODO: deprecated, remove in next major
1378
function copas.sleep(sleeptime)
200✔
1379
  coroutine_yield((sleeptime or 0), _sleeping)
×
1380
end
1381

1382

1383
-- yields the current coroutine and wakes it after 'sleeptime' seconds.
1384
-- if sleeptime < 0 then it sleeps 0 seconds.
1385
function copas.pause(sleeptime)
200✔
1386
  local s = gettime()
224,089✔
1387
  if sleeptime and sleeptime > 0 then
224,089✔
1388
    coroutine_yield(sleeptime, _sleeping)
9,244✔
1389
  else
1390
    coroutine_yield(0, _sleeping)
216,151✔
1391
  end
1392
  return gettime() - s
223,841✔
1393
end
1394

1395

1396
-- yields the current coroutine until explicitly woken up using 'wakeup'
1397
function copas.pauseforever()
200✔
1398
  local s = gettime()
3,378✔
1399
  coroutine_yield(-1, _sleeping)
3,378✔
1400
  return gettime() - s
3,318✔
1401
end
1402

1403

1404
-- Wakes up a sleeping coroutine 'co'.
1405
-- @return true on success, or nil+"not sleeping" if 'co' wasn't sleeping
1406
-- (eg. it was already woken up, finished, or canceled through `copas.removethread`).
1407
function copas.wakeup(co)
200✔
1408
  return _sleeping:wakeup(co)
3,378✔
1409
end
1410

1411

1412
-- Checks whether a coroutine 'co' is currently sleeping (paused or
1413
-- paused-forever), without waking it up. Useful to detect a coroutine
1414
-- that was canceled (eg. through `copas.removethread`) while it was
1415
-- expected to still be waiting.
1416
function copas.issleeping(co)
200✔
1417
  return _sleeping:issleeping(co)
606✔
1418
end
1419

1420

1421

1422
-------------------------------------------------------------------------------
1423
-- Timeout management
1424
-------------------------------------------------------------------------------
1425

1426
do
1427
  local timeout_register = setmetatable({}, { __mode = "k" })
200✔
1428
  local timerwheel = require("timerwheel").new({
401✔
1429
      now = gettime,
200✔
1430
      precision = TIMEOUT_PRECISION,
200✔
1431
      ringsize = math.floor(60*60*24/TIMEOUT_PRECISION),  -- ring size 1 day
200✔
1432
      err_handler = function(err)
1433
        return _deferror(err, core_timer_thread)
16✔
1434
      end,
1435
    })
1436

1437
  core_timer_thread = copas.addnamedthread("copas_core_timer", function()
400✔
1438
    while true do
1439
      copas.pause(TIMEOUT_PRECISION)
6,903✔
1440
      timerwheel:step()
7,846✔
1441
    end
1442
  end)
1443

1444
  -- get the number of timeouts running
1445
  function copas.gettimeouts()
200✔
1446
    return timerwheel:count()
3,420✔
1447
  end
1448

1449
  --- Sets the timeout for the current coroutine.
1450
  -- @param delay delay (seconds), use 0 (or math.huge) to cancel the timerout
1451
  -- @param callback function with signature: `function(coroutine)` where coroutine is the routine that timed-out
1452
  -- @return true
1453
  function copas.timeout(delay, callback)
200✔
1454
    local co = coroutine_running()
4,057,600✔
1455
    local existing_timer = timeout_register[co]
4,057,600✔
1456

1457
    if existing_timer then
4,057,600✔
1458
      timerwheel:cancel(existing_timer)
4,491✔
1459
    end
1460

1461
    if delay > 0 and delay ~= math.huge then
4,057,600✔
1462
      timeout_register[co] = timerwheel:set(delay, callback, co)
7,094✔
1463
    elseif delay == 0 or delay == math.huge then
4,051,521✔
1464
      timeout_register[co] = nil
4,051,521✔
1465
    else
1466
      error("timout value must be greater than or equal to 0, got: "..tostring(delay))
×
1467
    end
1468

1469
    return true
4,057,600✔
1470
  end
1471

1472
end
1473

1474

1475
-------------------------------------------------------------------------------
1476
-- main tasks: manage readable and writable socket sets
1477
-------------------------------------------------------------------------------
1478
-- a task is an object with a required method `step()` that deals with a
1479
-- single step for that task.
1480

1481
local _tasks = {} do
200✔
1482
  function _tasks:add(tsk)
200✔
1483
    _tasks[#_tasks + 1] = tsk
800✔
1484
  end
1485
end
1486

1487

1488
-- a task to check ready to read events
1489
local _readable_task = {} do
200✔
1490

1491
  _readable_task._events = {}
200✔
1492

1493
  local function tick(skt)
1494
    local handler = _servers[skt]
901✔
1495
    if handler then
901✔
1496
      _accept(skt, handler)
147✔
1497
    else
1498
      _reading:remove(skt)
775✔
1499
      _doTick(_reading:pop(skt), skt)
902✔
1500
    end
1501
  end
1502

1503
  function _readable_task:step()
200✔
1504
    for _, skt in ipairs(self._events) do
218,731✔
1505
      tick(skt)
901✔
1506
    end
1507
  end
1508

1509
  _tasks:add(_readable_task)
232✔
1510
end
1511

1512

1513
-- a task to check ready to write events
1514
local _writable_task = {} do
200✔
1515

1516
  _writable_task._events = {}
200✔
1517

1518
  local function tick(skt)
1519
    _writing:remove(skt)
371✔
1520
    _doTick(_writing:pop(skt), skt)
431✔
1521
  end
1522

1523
  function _writable_task:step()
200✔
1524
    for _, skt in ipairs(self._events) do
218,197✔
1525
      tick(skt)
371✔
1526
    end
1527
  end
1528

1529
  _tasks:add(_writable_task)
232✔
1530
end
1531

1532

1533

1534
-- sleeping threads task
1535
local _sleeping_task = {} do
200✔
1536

1537
  function _sleeping_task:step()
200✔
1538
    local now = gettime()
217,826✔
1539

1540
    local co = _sleeping:pop(now)
217,826✔
1541
    while co do
225,542✔
1542
      -- we're pushing them to _resumable, since that list will be replaced before
1543
      -- executing. This prevents tasks running twice in a row with pause(0) for example.
1544
      -- So here we won't execute, but at _resumable step which is next
1545
      _resumable:push(co)
7,716✔
1546
      co = _sleeping:pop(now)
9,023✔
1547
    end
1548
  end
1549

1550
  _tasks:add(_sleeping_task)
200✔
1551
end
1552

1553

1554

1555
-- resumable threads task
1556
local _resumable_task = {} do
200✔
1557

1558
  function _resumable_task:step()
200✔
1559
    -- replace the resume list before iterating, so items placed in there
1560
    -- will indeed end up in the next copas step, not in this one, and not
1561
    -- create a loop
1562
    local resumelist = _resumable:clear_resumelist()
217,826✔
1563

1564
    for _, co in ipairs(resumelist) do
445,085✔
1565
      _doTick(co)
227,267✔
1566
    end
1567
  end
1568

1569
  _tasks:add(_resumable_task)
200✔
1570
end
1571

1572

1573
-------------------------------------------------------------------------------
1574
-- Checks for reads and writes on sockets
1575
-------------------------------------------------------------------------------
1576
local _select_plain do
200✔
1577

1578
  local last_cleansing = 0
200✔
1579
  local duration = function(t2, t1) return t2-t1 end
217,940✔
1580

1581
  if not socket then
200✔
1582
    -- socket module unavailable, switch to luasystem sleep
1583
    _select_plain = block_sleep
6✔
1584
  else
1585
    -- use socket.select to handle socket-io
1586
    _select_plain = function(timeout)
1587
      local err
1588
      local now = gettime()
217,740✔
1589

1590
      -- remove any closed sockets to prevent select from hanging on them
1591
      if _closed[1] then
217,740✔
1592
        for i, skt in ipairs(_closed) do
441✔
1593
          _closed[i] = { _reading:remove(skt), _writing:remove(skt) }
296✔
1594
        end
1595
      end
1596

1597
      _readable_task._events, _writable_task._events, err = socket.select(_reading, _writing, timeout)
217,740✔
1598
      local r_events, w_events = _readable_task._events, _writable_task._events
217,740✔
1599

1600
      -- inject closed sockets in readable/writeable task so they can error out properly
1601
      if _closed[1] then
217,740✔
1602
        for i, skts in ipairs(_closed) do
441✔
1603
          _closed[i] = nil
222✔
1604
          r_events[#r_events+1] = skts[1]
222✔
1605
          w_events[#w_events+1] = skts[2]
222✔
1606
        end
1607
      end
1608

1609
      if duration(now, last_cleansing) > WATCH_DOG_TIMEOUT then
281,643✔
1610
        last_cleansing = now
188✔
1611

1612
        -- Check all sockets selected for reading, and check how long they have been waiting
1613
        -- for data already, without select returning them as readable
1614
        for skt,time in pairs(_reading_log) do
188✔
1615
          if not r_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
×
1616
            -- This one timedout while waiting to become readable, so move
1617
            -- it in the readable list and try and read anyway, despite not
1618
            -- having been returned by select
1619
            _reading_log[skt] = nil
×
1620
            r_events[#r_events + 1] = skt
×
1621
            r_events[skt] = #r_events
×
1622
          end
1623
        end
1624

1625
        -- Do the same for writing
1626
        for skt,time in pairs(_writing_log) do
188✔
1627
          if not w_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
×
1628
            _writing_log[skt] = nil
×
1629
            w_events[#w_events + 1] = skt
×
1630
            w_events[skt] = #w_events
×
1631
          end
1632
        end
1633
      end
1634

1635
      if err == "timeout" and #r_events + #w_events > 0 then
217,740✔
1636
        return nil
6✔
1637
      else
1638
        return err
217,734✔
1639
      end
1640
    end
1641
  end
1642
end
1643

1644

1645

1646
-------------------------------------------------------------------------------
1647
-- Dispatcher loop step.
1648
-- Listen to client requests and handles them
1649
-- Returns false if no socket-data was handled, or true if there was data
1650
-- handled (or nil + error message)
1651
-------------------------------------------------------------------------------
1652

1653
local copas_stats
1654
local min_ever, max_ever
1655

1656
local _select = _select_plain
200✔
1657

1658
-- instrumented version of _select() to collect stats
1659
local _select_instrumented = function(timeout)
1660
  if copas_stats then
×
1661
    local step_duration = gettime() - copas_stats.step_start
×
1662
    copas_stats.duration_max = math.max(copas_stats.duration_max, step_duration)
×
1663
    copas_stats.duration_min = math.min(copas_stats.duration_min, step_duration)
×
1664
    copas_stats.duration_tot = copas_stats.duration_tot + step_duration
×
1665
    copas_stats.steps = copas_stats.steps + 1
×
1666
  else
1667
    copas_stats = {
×
1668
      duration_max = -1,
1669
      duration_min = 999999,
1670
      duration_tot = 0,
1671
      steps = 0,
1672
    }
1673
  end
1674

1675
  local err = _select_plain(timeout)
×
1676

1677
  local now = gettime()
×
1678
  copas_stats.time_start = copas_stats.time_start or now
×
1679
  copas_stats.step_start = now
×
1680

1681
  return err
×
1682
end
1683

1684

1685
function copas.step(timeout)
200✔
1686
  -- Need to wake up the select call in time for the next sleeping event
1687
  if not _resumable:done() then
281,748✔
1688
    timeout = 0
211,210✔
1689
  else
1690
    timeout = math.min(_sleeping:getnext(), timeout or math.huge)
7,739✔
1691
  end
1692

1693
  local err = _select(timeout)
217,830✔
1694

1695
  for _, tsk in ipairs(_tasks) do
1,089,130✔
1696
    tsk:step()
871,312✔
1697
  end
1698

1699
  if err then
217,818✔
1700
    if err == "timeout" then
216,708✔
1701
      if timeout + 0.01 > TIMEOUT_PRECISION and math.random(100) > 90 then
216,618✔
1702
        -- we were idle, so occasionally do a GC sweep to ensure lingering
1703
        -- sockets are closed, and we don't accidentally block the loop from
1704
        -- exiting
1705
        collectgarbage()
474✔
1706
      end
1707
      return false
216,618✔
1708
    end
1709
    return nil, err
90✔
1710
  end
1711

1712
  return true
1,110✔
1713
end
1714

1715

1716
-------------------------------------------------------------------------------
1717
-- Check whether there is something to do.
1718
-- returns false if there are no sockets for read/write nor tasks scheduled
1719
-- (which means Copas is in an empty spin)
1720
-------------------------------------------------------------------------------
1721
function copas.finished()
200✔
1722
  return #_reading == 0 and #_writing == 0 and _resumable:done() and _sleeping:done(copas.gettimeouts())
219,643✔
1723
end
1724

1725

1726
local resetexit do
200✔
1727
  local exit_semaphore, exiting
1728

1729
  function resetexit()
168✔
1730
    exit_semaphore = copas.semaphore.new(1, 0, math.huge)
457✔
1731
    exiting = false
368✔
1732
  end
1733

1734
  -- Signals tasks to exit. But only if they check for it. By calling `copas.exiting`
1735
  -- they can check if they should exit. Or by calling `copas.waitforexit` they can
1736
  -- wait until the exit signal is given.
1737
  function copas.exit()
200✔
1738
    if exiting then return end
356✔
1739
    exiting = true
356✔
1740
    exit_semaphore:destroy()
356✔
1741
  end
1742

1743
  -- returns whether Copas is in the process of exiting. Exit can be started by
1744
  -- calling `copas.exit()`.
1745
  function copas.exiting()
200✔
1746
    return exiting
706✔
1747
  end
1748

1749
  -- Pauses the current coroutine until Copas is exiting. To be used as an exit
1750
  -- signal for tasks that need to clean up before exiting.
1751
  function copas.waitforexit()
232✔
1752
    exit_semaphore:take(1)
12✔
1753
  end
1754
end
1755

1756

1757
--- Forcibly cancels all pending work and signals exit.
1758
-- Intended for test teardown only. Abandons all registered threads and sockets
1759
-- without giving them a chance to clean up. After this call copas.finished()
1760
-- will return true and the loop will exit. The module is left in a clean state
1761
-- ready for the next copas.loop() call.
1762
function copas.cancelall()
200✔
1763
  -- 1. clear resumable queue
1764
  _resumable:clear_resumelist()
×
1765

1766
  -- 2. drain sleeping heap
1767
  _sleeping:cancelall()
×
1768

1769
  -- 3. close and drain reading sockets
1770
  while _reading[1] do
×
1771
    copas.close(_reading[1])
×
1772
    _reading:remove(_reading[1])
×
1773
  end
1774

1775
  -- 4. close and drain writing sockets
1776
  while _writing[1] do
×
1777
    copas.close(_writing[1])
×
1778
    _writing:remove(_writing[1])
×
1779
  end
1780

1781
  -- 5. remove all servers
1782
  while _servers[1] do
×
1783
    copas.removeserver(_servers[1])
×
1784
  end
1785

1786
  -- 6. clear non-weak ancillary tables
1787
  _closed = {}
×
1788
  _reading_log = {}
×
1789
  _writing_log = {}
×
1790

1791
  -- 7. signal exit
1792
  copas.exit()
×
1793
end
1794

1795

1796
local _getstats do
200✔
1797
  local _getstats_instrumented, _getstats_plain
1798

1799

1800
  function _getstats_plain(enable)
168✔
1801
    -- this function gets hit if turned off, so turn on if true
1802
    if enable == true then
×
1803
      _select = _select_instrumented
×
1804
      _getstats = _getstats_instrumented
×
1805
      -- reset stats
1806
      min_ever = nil
×
1807
      max_ever = nil
×
1808
      copas_stats = nil
×
1809
    end
1810
    return {}
×
1811
  end
1812

1813

1814
  -- convert from seconds to millisecs, with microsec precision
1815
  local function useconds(t)
1816
    return math.floor((t * 1000000) + 0.5) / 1000
×
1817
  end
1818
  -- convert from seconds to seconds, with millisec precision
1819
  local function mseconds(t)
1820
    return math.floor((t * 1000) + 0.5) / 1000
×
1821
  end
1822

1823

1824
  function _getstats_instrumented(enable)
168✔
1825
    if enable == false then
×
1826
      _select = _select_plain
×
1827
      _getstats = _getstats_plain
×
1828
      -- instrumentation disabled, so switch to the plain implementation
1829
      return _getstats(enable)
×
1830
    end
1831
    if (not copas_stats) or (copas_stats.step == 0) then
×
1832
      return {}
×
1833
    end
1834
    local stats = copas_stats
×
1835
    copas_stats = nil
×
1836
    min_ever = math.min(min_ever or 9999999, stats.duration_min)
×
1837
    max_ever = math.max(max_ever or 0, stats.duration_max)
×
1838
    stats.duration_min_ever = min_ever
×
1839
    stats.duration_max_ever = max_ever
×
1840
    stats.duration_avg = stats.duration_tot / stats.steps
×
1841
    stats.step_start = nil
×
1842
    stats.time_end = gettime()
×
1843
    stats.time_tot = stats.time_end - stats.time_start
×
1844
    stats.time_avg = stats.time_tot / stats.steps
×
1845

1846
    stats.duration_avg = useconds(stats.duration_avg)
×
1847
    stats.duration_max = useconds(stats.duration_max)
×
1848
    stats.duration_max_ever = useconds(stats.duration_max_ever)
×
1849
    stats.duration_min = useconds(stats.duration_min)
×
1850
    stats.duration_min_ever = useconds(stats.duration_min_ever)
×
1851
    stats.duration_tot = useconds(stats.duration_tot)
×
1852
    stats.time_avg = useconds(stats.time_avg)
×
1853
    stats.time_start = mseconds(stats.time_start)
×
1854
    stats.time_end = mseconds(stats.time_end)
×
1855
    stats.time_tot = mseconds(stats.time_tot)
×
1856
    return stats
×
1857
  end
1858

1859
  _getstats = _getstats_plain
200✔
1860
end
1861

1862

1863
function copas.status(enable_stats)
200✔
1864
  local res = _getstats(enable_stats)
×
1865
  res.running = not not copas.running
×
1866
  res.timeout = copas.gettimeouts()
×
1867
  res.timer, res.inactive = _sleeping:status()
×
1868
  res.read = #_reading
×
1869
  res.write = #_writing
×
1870
  res.active = _resumable:count()
×
1871
  return res
×
1872
end
1873

1874

1875
-------------------------------------------------------------------------------
1876
-- Dispatcher endless loop.
1877
-- Listen to client requests and handles them forever
1878
-------------------------------------------------------------------------------
1879
function copas.loop(initializer, timeout)
232✔
1880
  if type(initializer) == "function" then
368✔
1881
    copas.addnamedthread("copas_initializer", initializer)
204✔
1882
  else
1883
    timeout = initializer or timeout
192✔
1884
  end
1885

1886
  resetexit()
368✔
1887
  copas.running = true
368✔
1888
  while true do
1889
    copas.step(timeout)
217,830✔
1890
    if copas.finished() then
281,734✔
1891
      if copas.exiting() then
821✔
1892
        break
176✔
1893
      end
1894
      copas.exit()
350✔
1895
    end
1896
  end
1897
  copas.running = false
356✔
1898
end
1899

1900

1901
-------------------------------------------------------------------------------
1902
-- Naming sockets and coroutines.
1903
-------------------------------------------------------------------------------
1904
do
1905
  local function realsocket(skt)
1906
    local mt = getmetatable(skt)
90✔
1907
    if mt == _skt_mt_tcp or mt == _skt_mt_udp then
90✔
1908
      return skt.socket
90✔
1909
    else
1910
      return skt
×
1911
    end
1912
  end
1913

1914

1915
  function copas.setsocketname(name, skt)
232✔
1916
    assert(type(name) == "string", "expected arg #1 to be a string")
90✔
1917
    skt = assert(realsocket(skt), "expected arg #2 to be a socket")
105✔
1918
    object_names[skt] = name
90✔
1919
  end
1920

1921

1922
  function copas.getsocketname(skt)
232✔
1923
    skt = assert(realsocket(skt), "expected arg #1 to be a socket")
×
1924
    return object_names[skt]
×
1925
  end
1926
end
1927

1928

1929
function copas.setthreadname(name, coro)
232✔
1930
  assert(type(name) == "string", "expected arg #1 to be a string")
60✔
1931
  coro = coro or coroutine_running()
60✔
1932
  assert(type(coro) == "thread", "expected arg #2 to be a coroutine or nil")
60✔
1933
  object_names[coro] = name
60✔
1934
end
1935

1936

1937
function copas.getthreadname(coro)
232✔
1938
  coro = coro or coroutine_running()
38✔
1939
  assert(type(coro) == "thread", "expected arg #1 to be a coroutine or nil")
38✔
1940
  return object_names[coro]
40✔
1941
end
1942

1943
-------------------------------------------------------------------------------
1944
-- Debug functionality.
1945
-------------------------------------------------------------------------------
1946
do
1947
  copas.debug = {}
200✔
1948

1949
  local log_core    -- if truthy, the core-timer will also be logged
1950
  local debug_log   -- function used as logger
1951

1952

1953
  local debug_yield = function(skt, queue)
1954
    local name = object_names[coroutine_running()]
4,767✔
1955

1956
    if log_core or name ~= "copas_core_timer" then
4,767✔
1957
      if queue == _sleeping then
4,746✔
1958
        debug_log("yielding '", name, "' to SLEEP for ", skt," seconds")
4,666✔
1959

1960
      elseif queue == _writing then
80✔
1961
        debug_log("yielding '", name, "' to WRITE on '", object_names[skt], "'")
14✔
1962

1963
      elseif queue == _reading then
68✔
1964
        debug_log("yielding '", name, "' to READ on '", object_names[skt], "'")
70✔
1965

1966
      else
1967
        debug_log("thread '", name, "' yielding to unexpected queue; ", tostring(queue), " (", type(queue), ")", debug.traceback())
×
1968
      end
1969
    end
1970

1971
    return coroutine.yield(skt, queue)
4,767✔
1972
  end
1973

1974

1975
  local debug_resume = function(coro, skt, ...)
1976
    local name = object_names[coro]
4,779✔
1977

1978
    if skt then
4,779✔
1979
      debug_log("resuming '", name, "' for socket '", object_names[skt], "'")
80✔
1980
    else
1981
      if log_core or name ~= "copas_core_timer" then
4,699✔
1982
        debug_log("resuming '", name, "'")
4,678✔
1983
      end
1984
    end
1985
    return coroutine.resume(coro, skt, ...)
4,779✔
1986
  end
1987

1988

1989
  local debug_create = function(f)
1990
    local f_wrapped = function(...)
1991
      local results = pack(f(...))
14✔
1992
      debug_log("exiting '", object_names[coroutine_running()], "'")
12✔
1993
      return unpack(results)
12✔
1994
    end
1995

1996
    return coroutine.create(f_wrapped)
12✔
1997
  end
1998

1999

2000
  debug_log = fnil
200✔
2001

2002

2003
  -- enables debug output for all coroutine operations.
2004
  function copas.debug.start(logger, core)
400✔
2005
    log_core = core
6✔
2006
    debug_log = logger or print
6✔
2007
    coroutine_yield = debug_yield
6✔
2008
    coroutine_resume = debug_resume
6✔
2009
    coroutine_create = debug_create
6✔
2010
  end
2011

2012

2013
  -- disables debug output for coroutine operations.
2014
  function copas.debug.stop()
400✔
2015
    debug_log = fnil
×
2016
    coroutine_yield = coroutine.yield
×
2017
    coroutine_resume = coroutine.resume
×
2018
    coroutine_create = coroutine.create
×
2019
  end
2020

2021
  do
2022
    local call_id = 0
200✔
2023

2024
    -- Description table of socket functions for debug output.
2025
    -- each socket function name has TWO entries;
2026
    -- 'name_in' and 'name_out', each being an array of names/descriptions of respectively
2027
    -- input parameters and return values.
2028
    -- If either table has a 'callback' key, then that is a function that will be called
2029
    -- with the parameters/return-values for further inspection.
2030
    local args = {
200✔
2031
      settimeout_in = {
200✔
2032
        "socket ",
168✔
2033
        "seconds",
168✔
2034
        "mode   ",
2035
      },
200✔
2036
      settimeout_out = {
200✔
2037
        "success",
168✔
2038
        "error  ",
2039
      },
200✔
2040
      connect_in = {
200✔
2041
        "socket ",
168✔
2042
        "address",
168✔
2043
        "port   ",
2044
      },
200✔
2045
      connect_out = {
200✔
2046
        "success",
168✔
2047
        "error  ",
2048
      },
200✔
2049
      getfd_in = {
200✔
2050
        "socket ",
2051
        -- callback = function(...)
2052
        --   print(debug.traceback("called from:", 4))
2053
        -- end,
2054
      },
200✔
2055
      getfd_out = {
200✔
2056
        "fd",
2057
      },
200✔
2058
      send_in = {
200✔
2059
        "socket   ",
168✔
2060
        "data     ",
168✔
2061
        "idx-start",
168✔
2062
        "idx-end  ",
2063
      },
200✔
2064
      send_out = {
200✔
2065
        "last-idx-send    ",
168✔
2066
        "error            ",
168✔
2067
        "err-last-idx-send",
2068
      },
200✔
2069
      receive_in = {
200✔
2070
        "socket ",
168✔
2071
        "pattern",
168✔
2072
        "prefix ",
2073
      },
200✔
2074
      receive_out = {
200✔
2075
        "received    ",
168✔
2076
        "error       ",
168✔
2077
        "partial data",
2078
      },
200✔
2079
      dirty_in = {
200✔
2080
        "socket",
2081
        -- callback = function(...)
2082
        --   print(debug.traceback("called from:", 4))
2083
        -- end,
2084
      },
200✔
2085
      dirty_out = {
200✔
2086
        "data in read-buffer",
2087
      },
200✔
2088
      close_in = {
200✔
2089
        "socket",
2090
        -- callback = function(...)
2091
        --   print(debug.traceback("called from:", 4))
2092
        -- end,
2093
      },
200✔
2094
      close_out = {
200✔
2095
        "success",
168✔
2096
        "error",
2097
      },
200✔
2098
    }
2099
    local function print_call(func, msg, ...)
2100
      print(msg)
548✔
2101
      local arg = pack(...)
548✔
2102
      local desc = args[func] or {}
548✔
2103
      for i = 1, math.max(arg.n, #desc) do
1,240✔
2104
        local value = arg[i]
692✔
2105
        if type(value) == "string" then
692✔
2106
          local xvalue = value:sub(1,30)
36✔
2107
          if xvalue ~= value then
36✔
2108
            xvalue = xvalue .."(...truncated)"
×
2109
          end
2110
          print("\t"..(desc[i] or i)..": '"..tostring(xvalue).."' ("..type(value).." #"..#value..")")
36✔
2111
        else
2112
          print("\t"..(desc[i] or i)..": '"..tostring(value).."' ("..type(value)..")")
656✔
2113
        end
2114
      end
2115
      if desc.callback then
548✔
2116
        desc.callback(...)
×
2117
      end
2118
    end
2119

2120
    local debug_mt = {
200✔
2121
      __index = function(self, key)
2122
        local value = self.__original_socket[key]
274✔
2123
        if type(value) ~= "function" then
274✔
2124
          return value
×
2125
        end
2126
        return function(self2, ...)
2127
            local my_id = call_id + 1
274✔
2128
            call_id = my_id
274✔
2129
            local results
2130

2131
            if self2 ~= self then
274✔
2132
              -- there is no self
2133
              print_call(tostring(key).."_in", my_id .. "-calling '"..tostring(key) .. "' with; ", self, ...)
×
2134
              results = pack(value(self, ...))
×
2135
            else
2136
              print_call(tostring(key).."_in", my_id .. "-calling '" .. tostring(key) .. "' with; ", self.__original_socket, ...)
274✔
2137
              results = pack(value(self.__original_socket, ...))
312✔
2138
            end
2139
            print_call(tostring(key).."_out", my_id .. "-results '"..tostring(key) .. "' returned; ", unpack(results))
312✔
2140
            return unpack(results)
274✔
2141
          end
2142
      end,
2143
      __tostring = function(self)
2144
        return tostring(self.__original_socket)
48✔
2145
      end
2146
    }
2147

2148

2149
    -- wraps a socket (copas or luasocket) in a debug version printing all calls
2150
    -- and their parameters/return values. Extremely noisy!
2151
    -- returns the wrapped socket.
2152
    -- NOTE: only for plain sockets, will not support TLS
2153
    function copas.debug.socket(original_skt)
400✔
2154
      if (getmetatable(original_skt) == _skt_mt_tcp) or (getmetatable(original_skt) == _skt_mt_udp) then
12✔
2155
        -- already wrapped as Copas socket, so recurse with the original luasocket one
2156
        original_skt.socket = copas.debug.socket(original_skt.socket)
×
2157
        return original_skt
×
2158
      end
2159

2160
      local proxy = setmetatable({
24✔
2161
        __original_socket = original_skt
12✔
2162
      }, debug_mt)
12✔
2163

2164
      return proxy
12✔
2165
    end
2166
  end
2167
end
2168

2169

2170
return copas
200✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc