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

lunarmodules / copas / 30196085089

26 Jul 2026 09:14AM UTC coverage: 85.072%. Remained the same
30196085089

push

github

web-flow
docs(receive): add security note on l-pattern (#196)

1419 of 1668 relevant lines covered (85.07%)

63196.54 hits per line

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

80.06
/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
194✔
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
194✔
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
194✔
26
  if pcall(require, "socket") then
195✔
27
    -- found LuaSocket
28
    socket = require "socket"
188✔
29
  end
30

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

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

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

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

52

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

59

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

65

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

72

73
if socket then
194✔
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 = {
188✔
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)
188✔
93
    return function (...)
94
            return statusHandler(pcall(func, ...))
126✔
95
          end
96
  end
97

98
  function socket.newtry(finalizer)
188✔
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()
218✔
110
end
111

112

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

121
  copas = setmetatable({},{
388✔
122
    __index = function(self, key)
123
      if submodules[key] then
260✔
124
        self[key] = require("copas."..key)
262✔
125
        submodules[key] = nil
260✔
126
        return rawget(self, key)
260✔
127
      end
128
    end,
129
    __call = function(self, ...)
130
      return self.loop(...)
6✔
131
    end,
132
  })
194✔
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"
194✔
138
copas._DESCRIPTION = "Coroutine Oriented Portable Asynchronous Services"
194✔
139
copas._VERSION     = "Copas 4.11.0"
194✔
140

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

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

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

150
-------------------------------------------------------------------------------
151
-- Object names, to track names of thread/coroutines and sockets
152
-------------------------------------------------------------------------------
153
local object_names = setmetatable({}, {
388✔
154
  __mode = "k",
163✔
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 = {}
582✔
171

172
  do  -- set implementation
173
    local reverse = {}
582✔
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)
582✔
178
      if not reverse[skt] then
1,302✔
179
        self[#self + 1] = skt
1,302✔
180
        reverse[skt] = #self
1,302✔
181
        return skt
1,302✔
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)
582✔
188
      local index = reverse[skt]
1,830✔
189
      if index then
1,830✔
190
        reverse[skt] = nil
1,296✔
191
        local top = self[#self]
1,296✔
192
        self[#self] = nil
1,296✔
193
        if top ~= skt then
1,296✔
194
          reverse[top] = index
173✔
195
          self[index] = top
173✔
196
        end
197
        return skt
1,296✔
198
      end
199
    end
200

201
  end
202

203
  do  -- queues implementation
204
    local fifo_queues = setmetatable({},{
1,164✔
205
      __mode = "k",                 -- auto collect queue if socket is gone
489✔
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)
582✔
215
      local queue = fifo_queues[skt]
1,206✔
216
      queue[#queue + 1] = itm
1,206✔
217
    end
218

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

225
  end
226

227
  return set
582✔
228
end
229

230

231

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

236
  function _resumable:push(co)
194✔
237
    resumelist[#resumelist + 1] = co
242,821✔
238
  end
239

240
  function _resumable:clear_resumelist()
194✔
241
    local lst = resumelist
233,553✔
242
    resumelist = {}
233,553✔
243
    return lst
233,553✔
244
  end
245

246
  function _resumable:done()
194✔
247
    return resumelist[1] == nil
236,958✔
248
  end
249

250
  function _resumable:count()
194✔
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
194✔
261

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

265

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

271
  -- push a new timer on the heap
272
  function _sleeping:push(sleeptime, co)
194✔
273
    if sleeptime < 0 then
242,963✔
274
      lethargy[co] = true
3,222✔
275
    elseif sleeptime == 0 then
239,741✔
276
      _resumable:push(co)
294,246✔
277
    else
278
      heap:insert(gettime() + sleeptime, co)
7,286✔
279
    end
280
  end
281

282
  -- find the thread that should wake up to the time, if any
283
  function _sleeping:pop(time)
194✔
284
    if time < (heap:peekValue() or math.huge) then
303,804✔
285
      return
233,553✔
286
    end
287
    return heap:pop()
7,072✔
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
194✔
293
    local t = heap:peekValue()
5,977✔
294
    if t then
5,977✔
295
      -- never report less than 0, because select() might block
296
      return math.max(t - gettime(), 0)
5,977✔
297
    end
298
  end
299

300
  function _sleeping:wakeup(co)
194✔
301
    if lethargy[co] then
3,234✔
302
      lethargy[co] = nil
3,204✔
303
      _resumable:push(co)
3,204✔
304
      return
3,204✔
305
    end
306
    if heap:remove(co) then
35✔
307
      _resumable:push(co)
12✔
308
    end
309
  end
310

311
  function _sleeping:cancel(co)
194✔
312
    lethargy[co] = nil
54✔
313
    heap:remove(co)
54✔
314
  end
315

316
  function _sleeping:cancelall()
194✔
317
    while heap:size() > 0 do heap:pop() end
×
318
    heap:insert(gettime() + TIMEOUT_PRECISION, core_timer_thread)
×
319
    -- lethargy is weak; copas's idle GC sweeps will clean it within a few steps
320
  end
321

322
  -- @param tos number of timeouts running
323
  function _sleeping:done(tos)
194✔
324
    -- return true if we have nothing more to do
325
    -- the timeout task doesn't qualify as work (fallbacks only),
326
    -- the lethargy also doesn't qualify as work ('dead' tasks),
327
    -- but the combination of a timeout + a lethargy can be work
328
    return heap:size() == 1       -- 1 means only the timeout-timer task is running
3,147✔
329
           and not (tos > 0 and next(lethargy))
2,701✔
330
  end
331

332
  -- gets number of threads in binaryheap and lethargy
333
  function _sleeping:status()
194✔
334
    local c = 0
×
335
    for _ in pairs(lethargy) do c = c + 1 end
×
336

337
    return heap:size(), c
×
338
  end
339

340
end   -- _sleeping
341

342

343

344
-------------------------------------------------------------------------------
345
-- Tracking coroutines and sockets
346
-------------------------------------------------------------------------------
347

348
local _servers = newsocketset() -- servers being handled
194✔
349
local _threads = setmetatable({}, {__mode = "k"})  -- registered threads added with addthread()
194✔
350
local _canceled = setmetatable({}, {__mode = "k"}) -- threads that are canceled and pending removal
194✔
351
local _autoclose = setmetatable({}, {__mode = "kv"}) -- sockets (value) to close when a thread (key) exits
194✔
352
local _autoclose_r = setmetatable({}, {__mode = "kv"}) -- reverse: sockets (key) to close when a thread (value) exits
194✔
353

354

355
-- for each socket we log the last read and last write times to enable the
356
-- watchdog to follow up if it takes too long.
357
-- tables contain the time, indexed by the socket
358
local _reading_log = {}
194✔
359
local _writing_log = {}
194✔
360

361
local _closed = {} -- track sockets that have been closed (list/array)
194✔
362

363
local _reading = newsocketset() -- sockets currently being read
194✔
364
local _writing = newsocketset() -- sockets currently being written
194✔
365
local _isSocketTimeout = { -- set of errors indicating a socket-timeout
194✔
366
  ["timeout"] = true,      -- default LuaSocket timeout
163✔
367
  ["wantread"] = true,     -- LuaSec specific timeout
163✔
368
  ["wantwrite"] = true,    -- LuaSec specific timeout
163✔
369
}
370

371
-------------------------------------------------------------------------------
372
-- Coroutine based socket timeouts.
373
-------------------------------------------------------------------------------
374
local user_timeouts_connect
375
local user_timeouts_send
376
local user_timeouts_receive
377
do
378
  local timeout_mt = {
194✔
379
    __mode = "k",
163✔
380
    __index = function(self, skt)
381
      -- if there is no timeout found, we insert one automatically, to block forever
382
      self[skt] = math.huge
432✔
383
      return self[skt]
432✔
384
    end,
385
  }
386

387
  user_timeouts_connect = setmetatable({}, timeout_mt)
194✔
388
  user_timeouts_send = setmetatable({}, timeout_mt)
194✔
389
  user_timeouts_receive = setmetatable({}, timeout_mt)
194✔
390
end
391

392
local useSocketTimeoutErrors = setmetatable({},{ __mode = "k" })
194✔
393

394

395
-- sto = socket-time-out
396
local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
194✔
397

398
  local socket_register = setmetatable({}, { __mode = "k" })    -- socket by coroutine
194✔
399
  local operation_register = setmetatable({}, { __mode = "k" }) -- operation "read"/"write" by coroutine
194✔
400
  local timeout_flags = setmetatable({}, { __mode = "k" })      -- true if timedout, by coroutine
194✔
401

402

403
  -- The callback called when a socket timeout occurs.
404
  local function socket_callback(co)
405
    local skt = socket_register[co]
78✔
406
    local queue = operation_register[co]
78✔
407

408
    -- flag the timeout and resume the coroutine
409
    timeout_flags[co] = true
78✔
410
    _resumable:push(co)
78✔
411

412
    -- clear the socket from the current queue
413
    if queue == "read" then
78✔
414
      _reading:remove(skt)
77✔
415
    elseif queue == "write" then
12✔
416
      _writing:remove(skt)
14✔
417
    else
418
      error("bad queue name; expected 'read'/'write', got: "..tostring(queue))
×
419
    end
420
  end
421

422

423
  -- Sets a socket timeout.
424
  -- Calling it as `sto_timeout()` will cancel the timeout.
425
  -- @param skt (socket) the socket on which to operate, use 'nil' to cancel the current timeout
426
  -- @param queue (string) the queue the socket is currently in: "read" or "write"
427
  -- @param use_connect_to (bool) if truthy, use the connect timeout instead of the
428
  --   read/write timeout implied by queue. Needed because connect also uses the "write"
429
  --   queue, so the queue value alone cannot distinguish connect from send operations.
430
  -- @return true
431
  function sto_timeout(skt, queue, use_connect_to)
163✔
432
    local co = coroutine_running()
4,357,856✔
433
    socket_register[co] = skt
4,357,856✔
434
    operation_register[co] = queue
4,357,856✔
435
    timeout_flags[co] = nil
4,357,856✔
436
    if skt then
4,357,856✔
437
      local to = (use_connect_to and user_timeouts_connect[skt]) or
2,178,939✔
438
                 (queue == "read" and user_timeouts_receive[skt]) or
2,178,612✔
439
                 user_timeouts_send[skt]
15,219✔
440
      copas.timeout(to, socket_callback)
2,767,095✔
441
    else
442
      copas.timeout(0)
2,178,928✔
443
    end
444
    return true
4,357,856✔
445
  end
446

447

448
  -- Changes the timeout to a different queue (read/write).
449
  -- Only usefull with ssl-handshakes and "wantread", "wantwrite" errors, when
450
  -- the queue has to be changed, so the timeout handler knows where to find the socket.
451
  -- @param queue (string) the new queue the socket is in, must be either "read" or "write"
452
  -- @return true
453
  function sto_change_queue(queue)
163✔
454
    operation_register[coroutine_running()] = queue
978✔
455
    return true
978✔
456
  end
457

458

459
  -- Responds with `true` if the operation timed-out.
460
  function sto_timed_out()
163✔
461
    return timeout_flags[coroutine_running()]
1,284✔
462
  end
463

464

465
  -- Returns the proper timeout error
466
  function sto_error(err)
163✔
467
    return useSocketTimeoutErrors[coroutine_running()] and err or "timeout"
78✔
468
  end
469

470
  -- only in case of testing export some internals
471
  if _G._TEST then
194✔
472
    copas._socket_register = socket_register
6✔
473
    copas._operation_register = operation_register
6✔
474
    copas._timeout_flags = timeout_flags
6✔
475
  end
476
end
477

478

479

480
-------------------------------------------------------------------------------
481
-- Coroutine based socket I/O functions.
482
-------------------------------------------------------------------------------
483

484
-- Returns "tcp"" for plain TCP and "ssl" for ssl-wrapped sockets, so truthy
485
-- for tcp based, and falsy for udp based.
486
local isTCP do
194✔
487
  local lookup = {
194✔
488
    tcp = "tcp",
163✔
489
    SSL = "ssl",
163✔
490
  }
491

492
  function isTCP(socket)
163✔
493
    return lookup[tostring(socket):sub(1,3)]
770✔
494
  end
495
end
496

497
function copas.close(skt, ...)
194✔
498
  _closed[#_closed+1] = skt
222✔
499
  return skt:close(...)
222✔
500
end
501

502

503

504
-- nil or negative is indefinitly
505
function copas.settimeout(skt, timeout)
194✔
506
  timeout = timeout or -1
216✔
507
  if type(timeout) ~= "number" then
216✔
508
    return nil, "timeout must be 'nil' or a number"
18✔
509
  end
510

511
  return copas.settimeouts(skt, timeout, timeout, timeout)
198✔
512
end
513

514
-- negative is indefinitly, nil means do not change
515
function copas.settimeouts(skt, connect, send, read)
194✔
516

517
  if connect ~= nil and type(connect) ~= "number" then
432✔
518
    return nil, "connect timeout must be 'nil' or a number"
×
519
  end
520
  if connect then
432✔
521
    if connect < 0 then
432✔
522
      connect = nil
×
523
    end
524
    user_timeouts_connect[skt] = connect
432✔
525
  end
526

527

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

538

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

549

550
  return true
432✔
551
end
552

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

569
  repeat
570
    s, err, part = client:receive(pattern, part)
2,163,962✔
571

572
    -- guarantees that high throughput doesn't take other threads to starvation
573
    if (math.random(100) > 90) then
2,163,962✔
574
      copas.pause()
217,070✔
575
    end
576

577
    if s then
2,163,962✔
578
      current_log[client] = nil
2,163,247✔
579
      sto_timeout()
2,163,247✔
580
      return s, err, part
2,163,247✔
581

582
    elseif not _isSocketTimeout[err] then
715✔
583
      current_log[client] = nil
48✔
584
      sto_timeout()
48✔
585
      return s, err, part
48✔
586

587
    elseif sto_timed_out() then
785✔
588
      current_log[client] = nil
60✔
589
      sto_timeout()
60✔
590
      return nil, sto_error(err), part
70✔
591
    end
592

593
    if err == "wantwrite" then -- wantwrite may be returned during SSL renegotiations
607✔
594
      current_log = _writing_log
×
595
      current_log[client] = gettime()
×
596
      sto_change_queue("write")
×
597
      coroutine_yield(client, _writing)
×
598
    else
599
      current_log = _reading_log
607✔
600
      current_log[client] = gettime()
607✔
601
      sto_change_queue("read")
607✔
602
      coroutine_yield(client, _reading)
607✔
603
    end
604
  until false
607✔
605
end
606

607
-- receives data from a client over UDP. Not available for TCP.
608
-- (this is a copy of receive() method, adapted for receivefrom() use)
609
function copas.receivefrom(client, size)
194✔
610
  local s, err, port
611
  size = size or UDP_DATAGRAM_MAX
24✔
612
  sto_timeout(client, "read")
24✔
613

614
  repeat
615
    s, err, port = client:receivefrom(size) -- upon success err holds ip address
48✔
616

617
    -- garantees that high throughput doesn't take other threads to starvation
618
    if (math.random(100) > 90) then
48✔
619
      copas.pause()
4✔
620
    end
621

622
    if s then
48✔
623
      _reading_log[client] = nil
18✔
624
      sto_timeout()
18✔
625
      return s, err, port
18✔
626

627
    elseif err ~= "timeout" then
30✔
628
      _reading_log[client] = nil
×
629
      sto_timeout()
×
630
      return s, err, port
×
631

632
    elseif sto_timed_out() then
35✔
633
      _reading_log[client] = nil
6✔
634
      sto_timeout()
6✔
635
      return nil, sto_error(err), port
7✔
636
    end
637

638
    _reading_log[client] = gettime()
24✔
639
    coroutine_yield(client, _reading)
24✔
640
  until false
24✔
641
end
642

643
-- same as above but with special treatment when reading chunks,
644
-- unblocks on any data received.
645
function copas.receivepartial(client, pattern, part)
194✔
646
  local s, err
647
  pattern = pattern or "*l"
12✔
648
  local orig_size = #(part or "")
12✔
649
  local current_log = _reading_log
12✔
650
  sto_timeout(client, "read")
12✔
651

652
  repeat
653
    s, err, part = client:receive(pattern, part)
18✔
654

655
    -- guarantees that high throughput doesn't take other threads to starvation
656
    if (math.random(100) > 90) then
18✔
657
      copas.pause()
1✔
658
    end
659

660
    if s or (type(part) == "string" and #part > orig_size) then
18✔
661
      current_log[client] = nil
12✔
662
      sto_timeout()
12✔
663
      return s, err, part
12✔
664

665
    elseif not _isSocketTimeout[err] then
6✔
666
      current_log[client] = nil
×
667
      sto_timeout()
×
668
      return s, err, part
×
669

670
    elseif sto_timed_out() then
7✔
671
      current_log[client] = nil
×
672
      sto_timeout()
×
673
      return nil, sto_error(err), part
×
674
    end
675

676
    if err == "wantwrite" then
6✔
677
      current_log = _writing_log
×
678
      current_log[client] = gettime()
×
679
      sto_change_queue("write")
×
680
      coroutine_yield(client, _writing)
×
681
    else
682
      current_log = _reading_log
6✔
683
      current_log[client] = gettime()
6✔
684
      sto_change_queue("read")
6✔
685
      coroutine_yield(client, _reading)
6✔
686
    end
687
  until false
6✔
688
end
689
copas.receivePartial = copas.receivepartial  -- compat: receivePartial is deprecated
194✔
690

691
-- sends data to a client. The operation is buffered and
692
-- yields to the writing set on timeouts
693
-- Note: from and to parameters will be ignored by/for UDP sockets
694
function copas.send(client, data, from, to)
194✔
695
  local s, err
696
  from = from or 1
15,219✔
697
  local lastIndex = from - 1
15,219✔
698
  local current_log = _writing_log
15,219✔
699
  sto_timeout(client, "write")
15,219✔
700

701
  repeat
702
    s, err, lastIndex = client:send(data, lastIndex + 1, to)
15,398✔
703

704
    -- guarantees that high throughput doesn't take other threads to starvation
705
    if (math.random(100) > 90) then
15,398✔
706
      copas.pause()
1,494✔
707
    end
708

709
    if s then
15,398✔
710
      current_log[client] = nil
15,195✔
711
      sto_timeout()
15,195✔
712
      return s, err, lastIndex
15,195✔
713

714
    elseif not _isSocketTimeout[err] then
203✔
715
      current_log[client] = nil
24✔
716
      sto_timeout()
24✔
717
      return s, err, lastIndex
24✔
718

719
    elseif sto_timed_out() then
207✔
720
      current_log[client] = nil
×
721
      sto_timeout()
×
722
      return nil, sto_error(err), lastIndex
×
723
    end
724

725
    if err == "wantread" then
179✔
726
      current_log = _reading_log
×
727
      current_log[client] = gettime()
×
728
      sto_change_queue("read")
×
729
      coroutine_yield(client, _reading)
×
730
    else
731
      current_log = _writing_log
179✔
732
      current_log[client] = gettime()
179✔
733
      sto_change_queue("write")
179✔
734
      coroutine_yield(client, _writing)
179✔
735
    end
736
  until false
179✔
737
end
738

739
function copas.sendto(client, data, ip, port)
194✔
740
  -- deprecated; for backward compatibility only, since UDP doesn't block on sending
741
  return client:sendto(data, ip, port)
×
742
end
743

744
-- waits until connection is completed
745
function copas.connect(skt, host, port)
194✔
746
  skt:settimeout(0)
212✔
747
  local ret, err, tried_more_than_once
748
  sto_timeout(skt, "write", true)
210✔
749

750
  repeat
751
    ret, err = skt:connect(host, port)
422✔
752

753
    -- non-blocking connect on Windows results in error "Operation already
754
    -- in progress" to indicate that it is completing the request async. So essentially
755
    -- it is the same as "timeout"
756
    if ret or (err ~= "timeout" and err ~= "Operation already in progress") then
414✔
757
      _writing_log[skt] = nil
198✔
758
      sto_timeout()
198✔
759
      -- Once the async connect completes, Windows returns the error "already connected"
760
      -- to indicate it is done, so that error should be ignored. Except when it is the
761
      -- first call to connect, then it was already connected to something else and the
762
      -- error should be returned
763
      if (not ret) and (err == "already connected" and tried_more_than_once) then
198✔
764
        return 1
×
765
      end
766
      return ret, err
198✔
767

768
    elseif sto_timed_out() then
252✔
769
      _writing_log[skt] = nil
12✔
770
      sto_timeout()
12✔
771
      return nil, sto_error(err)
14✔
772
    end
773

774
    tried_more_than_once = tried_more_than_once or true
204✔
775
    _writing_log[skt] = gettime()
204✔
776
    coroutine_yield(skt, _writing)
204✔
777
  until false
204✔
778
end
779

780

781
-- Wraps a tcp socket in an ssl socket and configures it. If the socket was
782
-- already wrapped, it does nothing and returns the socket.
783
-- @param wrap_params the parameters for the ssl-context
784
-- @return wrapped socket, or throws an error
785
local function ssl_wrap(skt, wrap_params)
786
  if isTCP(skt) == "ssl" then return skt end -- was already wrapped
224✔
787
  if not wrap_params then
108✔
788
    error("cannot wrap socket into a secure socket (using 'ssl.wrap()') without parameters/context")
×
789
  end
790

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

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

798
  local co = _autoclose_r[skt]
108✔
799
  if co then
108✔
800
    -- socket registered for autoclose, move registration to wrapped one
801
    _autoclose[co] = nskt
24✔
802
    _autoclose_r[skt] = nil
24✔
803
    _autoclose_r[nskt] = co
24✔
804
  end
805

806
  local sock_name = object_names[skt]
108✔
807
  if sock_name ~= tostring(skt) then
108✔
808
    -- socket had a custom name, so copy it over
809
    object_names[nskt] = sock_name
36✔
810
  end
811
  return nskt
108✔
812
end
813

814

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

837
  elseif t == "table" then
114✔
838
    if sslt.mode or sslt.protocol then
114✔
839
      -- has the mandatory fields for the ssl-params table for handshake
840
      -- backward compatibility
841
      r.wrap = sslt
24✔
842
      r.sni = false
24✔
843
    else
844
      -- has the target definition, copy our known keys
845
      r.wrap = sslt.wrap or false -- 'or false' because we do not want nils
90✔
846
      r.sni = sslt.sni or false -- 'or false' because we do not want nils
90✔
847
    end
848

849
  elseif t == "userdata" then
×
850
    -- it's an ssl-context object for the handshake
851
    -- backward compatibility
852
    r.wrap = sslt
×
853
    r.sni = false
×
854

855
  else
856
    error("ssl parameters; did not expect type "..tostring(sslt))
×
857
  end
858

859
  return r
348✔
860
end
861

862

863
---
864
-- Peforms an (async) ssl handshake on a connected TCP client socket.
865
-- NOTE: if not ssl-wrapped already, then replace all previous socket references, with the returned new ssl wrapped socket
866
-- Throws error and does not return nil+error, as that might silently fail
867
-- in code like this;
868
--   copas.addserver(s1, function(skt)
869
--       skt = copas.wrap(skt, sparams)
870
--       skt:dohandshake()   --> without explicit error checking, this fails silently and
871
--       skt:send(body)      --> continues unencrypted
872
-- @param skt Regular LuaSocket CLIENT socket object
873
-- @param wrap_params Table with ssl parameters
874
-- @return wrapped ssl socket, or throws an error
875
function copas.dohandshake(skt, wrap_params)
194✔
876
  ssl = ssl or require("ssl")
108✔
877

878
  local nskt = ssl_wrap(skt, wrap_params)
108✔
879

880
  sto_timeout(nskt, "write", true)
108✔
881
  local queue
882

883
  repeat
884
    local success, err = nskt:dohandshake()
294✔
885

886
    if success then
294✔
887
      sto_timeout()
96✔
888
      return nskt
96✔
889

890
    elseif not _isSocketTimeout[err] then
198✔
891
      sto_timeout()
12✔
892
      error("TLS/SSL handshake failed: " .. tostring(err))
12✔
893

894
    elseif sto_timed_out() then
217✔
895
      sto_timeout()
×
896
      return nil, sto_error(err)
×
897

898
    elseif err == "wantwrite" then
186✔
899
      sto_change_queue("write")
×
900
      queue = _writing
×
901

902
    elseif err == "wantread" then
186✔
903
      sto_change_queue("read")
186✔
904
      queue = _reading
186✔
905

906
    else
907
      error("TLS/SSL handshake failed: " .. tostring(err))
×
908
    end
909

910
    coroutine_yield(nskt, queue)
186✔
911
  until false
186✔
912
end
913

914
-- flushes a client write buffer (deprecated)
915
function copas.flush()
194✔
916
end
917

918
-- wraps a TCP socket to use Copas methods (send, receive, flush and settimeout)
919
local _skt_mt_tcp = {
194✔
920
      __tostring = function(self)
921
        return tostring(self.socket).." (copas wrapped)"
18✔
922
      end,
923

924
      __index = {
194✔
925
        send = function (self, data, from, to)
926
          return copas.send (self.socket, data, from, to)
15,213✔
927
        end,
928

929
        receive = function (self, pattern, prefix)
930
          if user_timeouts_receive[self.socket] == 0 then
2,163,350✔
931
            return copas.receivepartial(self.socket, pattern, prefix)
12✔
932
          end
933
          return copas.receive(self.socket, pattern, prefix)
2,163,337✔
934
        end,
935

936
        receivepartial = function (self, pattern, prefix)
937
          return copas.receivepartial(self.socket, pattern, prefix)
×
938
        end,
939

940
        flush = function (self)
941
          return copas.flush(self.socket)
×
942
        end,
943

944
        settimeout = function (self, time)
945
          return copas.settimeout(self.socket, time)
198✔
946
        end,
947

948
        settimeouts = function (self, connect, send, receive)
949
          return copas.settimeouts(self.socket, connect, send, receive)
×
950
        end,
951

952
        -- TODO: socket.connect is a shortcut, and must be provided with an alternative
953
        -- if ssl parameters are available, it will also include a handshake
954
        connect = function(self, ...)
955
          local res, err = copas.connect(self.socket, ...)
210✔
956
          if res then
210✔
957
            if self.ssl_params.sni then self:sni() end
192✔
958
            if self.ssl_params.wrap then res, err = self:dohandshake() end
205✔
959
          end
960
          return res, err
204✔
961
        end,
962

963
        close = function(self, ...)
964
          return copas.close(self.socket, ...)
222✔
965
        end,
966

967
        -- TODO: socket.bind is a shortcut, and must be provided with an alternative
968
        bind = function(self, ...) return self.socket:bind(...) end,
194✔
969

970
        -- TODO: is this DNS related? hence blocking?
971
        getsockname = function(self, ...)
972
          local ok, ip, port, family = pcall(self.socket.getsockname, self.socket, ...)
×
973
          if ok then
×
974
            return ip, port, family
×
975
          else
976
            return nil, "not implemented by LuaSec"
×
977
          end
978
        end,
979

980
        getstats = function(self, ...) return self.socket:getstats(...) end,
194✔
981

982
        setstats = function(self, ...) return self.socket:setstats(...) end,
194✔
983

984
        listen = function(self, ...) return self.socket:listen(...) end,
194✔
985

986
        accept = function(self, ...) return self.socket:accept(...) end,
194✔
987

988
        setoption = function(self, ...)
989
          local ok, res, err = pcall(self.socket.setoption, self.socket, ...)
×
990
          if ok then
×
991
            return res, err
×
992
          else
993
            return nil, "not implemented by LuaSec"
×
994
          end
995
        end,
996

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

1006
        -- TODO: is this DNS related? hence blocking?
1007
        getpeername = function(self, ...)
1008
          local ok, ip, port, family = pcall(self.socket.getpeername, self.socket, ...)
×
1009
          if ok then
×
1010
            return ip, port, family
×
1011
          else
1012
            return nil, "not implemented by LuaSec"
×
1013
          end
1014
        end,
1015

1016
        shutdown = function(self, ...) return self.socket:shutdown(...) end,
194✔
1017

1018
        sni = function(self, names, strict)
1019
          local sslp = self.ssl_params
84✔
1020
          self.socket = ssl_wrap(self.socket, sslp.wrap)
98✔
1021
          if names == nil then
84✔
1022
            names = sslp.sni.names
72✔
1023
            strict = sslp.sni.strict
72✔
1024
          end
1025
          return self.socket:sni(names, strict)
84✔
1026
        end,
1027

1028
        dohandshake = function(self, wrap_params)
1029
          local nskt, err = copas.dohandshake(self.socket, wrap_params or self.ssl_params.wrap)
108✔
1030
          if not nskt then return nskt, err end
96✔
1031
          self.socket = nskt  -- replace internal socket with the newly wrapped ssl one
96✔
1032
          return self
96✔
1033
        end,
1034

1035
        getalpn = function(self, ...)
1036
          local ok, proto, err = pcall(self.socket.getalpn, self.socket, ...)
×
1037
          if ok then
×
1038
            return proto, err
×
1039
          else
1040
            return nil, "not a tls socket"
×
1041
          end
1042
        end,
1043

1044
        getsniname = function(self, ...)
1045
          local ok, name, err = pcall(self.socket.getsniname, self.socket, ...)
×
1046
          if ok then
×
1047
            return name, err
×
1048
          else
1049
            return nil, "not a tls socket"
×
1050
          end
1051
        end,
1052
      }
194✔
1053
}
1054

1055
-- wraps a UDP socket, copy of TCP one adapted for UDP.
1056
local _skt_mt_udp = {__index = { }}
194✔
1057
for k,v in pairs(_skt_mt_tcp) do _skt_mt_udp[k] = _skt_mt_udp[k] or v end
582✔
1058
for k,v in pairs(_skt_mt_tcp.__index) do _skt_mt_udp.__index[k] = v end
4,462✔
1059

1060
_skt_mt_udp.__index.send        = function(self, ...) return self.socket:send(...) end
200✔
1061

1062
_skt_mt_udp.__index.sendto      = function(self, ...) return self.socket:sendto(...) end
212✔
1063

1064

1065
_skt_mt_udp.__index.receive =     function (self, size)
194✔
1066
                                    return copas.receive (self.socket, (size or UDP_DATAGRAM_MAX))
12✔
1067
                                  end
1068

1069
_skt_mt_udp.__index.receivefrom = function (self, size)
194✔
1070
                                    return copas.receivefrom (self.socket, (size or UDP_DATAGRAM_MAX))
24✔
1071
                                  end
1072

1073
                                  -- TODO: is this DNS related? hence blocking?
1074
_skt_mt_udp.__index.setpeername = function(self, ...) return self.socket:setpeername(...) end
200✔
1075

1076
_skt_mt_udp.__index.setsockname = function(self, ...) return self.socket:setsockname(...) end
194✔
1077

1078
                                    -- do not close client, as it is also the server for udp.
1079
_skt_mt_udp.__index.close       = function(self, ...) return true end
206✔
1080

1081
_skt_mt_udp.__index.settimeouts = function (self, connect, send, receive)
194✔
1082
                                    return copas.settimeouts(self.socket, connect, send, receive)
×
1083
                                  end
1084

1085

1086

1087
---
1088
-- Wraps a LuaSocket socket object in an async Copas based socket object.
1089
-- @param skt The socket to wrap
1090
-- @sslt (optional) Table with ssl parameters, use an empty table to use ssl with defaults
1091
-- @return wrapped socket object
1092
function copas.wrap (skt, sslt)
194✔
1093
  if (getmetatable(skt) == _skt_mt_tcp) or (getmetatable(skt) == _skt_mt_udp) then
372✔
1094
    return skt -- already wrapped
×
1095
  end
1096

1097
  skt:settimeout(0)
374✔
1098

1099
  if isTCP(skt) then
434✔
1100
    return setmetatable ({socket = skt, ssl_params = normalize_sslt(sslt)}, _skt_mt_tcp)
406✔
1101
  else
1102
    return setmetatable ({socket = skt}, _skt_mt_udp)
24✔
1103
  end
1104
end
1105

1106
--- Wraps a handler in a function that deals with wrapping the socket and doing the
1107
-- optional ssl handshake.
1108
function copas.handler(handler, sslparams)
194✔
1109
  -- TODO: pass a timeout value to set, and use during handshake
1110
  return function (skt, ...)
1111
    skt = copas.wrap(skt, sslparams) -- this call will normalize the sslparams table
112✔
1112
    local sslp = skt.ssl_params
96✔
1113
    if sslp.sni then skt:sni(sslp.sni.names, sslp.sni.strict) end
96✔
1114
    if sslp.wrap then skt:dohandshake(sslp.wrap) end
96✔
1115
    return handler(skt, ...)
90✔
1116
  end
1117
end
1118

1119

1120
--------------------------------------------------
1121
-- Error handling
1122
--------------------------------------------------
1123

1124
local _errhandlers = setmetatable({}, { __mode = "k" })   -- error handler per coroutine
194✔
1125

1126

1127
function copas.gettraceback(msg, co, skt)
194✔
1128
  local co_str = co == nil and "nil" or copas.getthreadname(co)
38✔
1129
  local skt_str = skt == nil and "nil" or copas.getsocketname(skt)
38✔
1130
  local msg_str = msg == nil and "" or tostring(msg)
38✔
1131
  if msg_str == "" then
38✔
1132
    msg_str = ("(coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str)
×
1133
  else
1134
    msg_str = ("%s (coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str)
38✔
1135
  end
1136

1137
  if type(co) == "thread" then
38✔
1138
    -- regular Copas coroutine
1139
    return debug.traceback(co, msg_str)
38✔
1140
  end
1141
  -- not a coroutine, but the main thread, this happens if a timeout callback
1142
  -- (see `copas.timeout` causes an error (those callbacks run on the main thread).
1143
  return debug.traceback(msg_str, 2)
×
1144
end
1145

1146

1147
local function _deferror(msg, co, skt)
1148
  print(copas.gettraceback(msg, co, skt))
29✔
1149
end
1150

1151

1152
function copas.seterrorhandler(err, default)
194✔
1153
  assert(err == nil or type(err) == "function", "Expected the handler to be a function, or nil")
60✔
1154
  if default then
60✔
1155
    assert(err ~= nil, "Expected the handler to be a function when setting the default")
42✔
1156
    _deferror = err
42✔
1157
  else
1158
    _errhandlers[coroutine_running()] = err
18✔
1159
  end
1160
end
1161
copas.setErrorHandler = copas.seterrorhandler  -- deprecated; old casing
194✔
1162

1163

1164
function copas.geterrorhandler(co)
194✔
1165
  co = co or coroutine_running()
12✔
1166
  return _errhandlers[co] or _deferror
12✔
1167
end
1168

1169

1170
-- if `bool` is truthy, then the original socket errors will be returned in case of timeouts;
1171
-- `timeout, wantread, wantwrite, Operation already in progress`. If falsy, it will always
1172
-- return `timeout`.
1173
function copas.useSocketTimeoutErrors(bool)
194✔
1174
  useSocketTimeoutErrors[coroutine_running()] = not not bool -- force to a boolean
6✔
1175
end
1176

1177
-------------------------------------------------------------------------------
1178
-- Thread handling
1179
-------------------------------------------------------------------------------
1180

1181
local function _doTick (co, skt, ...)
1182
  if not co then return end
249,423✔
1183

1184
  -- if a coroutine was canceled/removed, don't resume it
1185
  if _canceled[co] then
249,423✔
1186
    _canceled[co] = nil -- also clean up the registry
26✔
1187
    _threads[co] = nil
26✔
1188
    return
26✔
1189
  end
1190

1191
  -- res: the socket (being read/write on) or the time to sleep
1192
  -- new_q: either _writing, _reading, or _sleeping
1193
  -- local time_before = gettime()
1194
  local ok, res, new_q = coroutine_resume(co, skt, ...)
249,397✔
1195
  -- local duration = gettime() - time_before
1196
  -- if duration > 1 then
1197
  --   duration = math.floor(duration * 1000)
1198
  --   pcall(_errhandlers[co] or _deferror, "task ran for "..tostring(duration).." milliseconds.", co, skt)
1199
  -- end
1200

1201
  if new_q == _reading or new_q == _writing or new_q == _sleeping then
249,385✔
1202
    -- we're yielding to a new queue
1203
    new_q:insert (res)
244,169✔
1204
    new_q:push (res, co)
244,169✔
1205
    return
244,169✔
1206
  end
1207

1208
  -- coroutine is terminating
1209

1210
  if ok and coroutine_status(co) ~= "dead" then
5,216✔
1211
    -- it called coroutine.yield from a non-Copas function which is unexpected
1212
    ok = false
6✔
1213
    res = "coroutine.yield was called without a resume first, user-code cannot yield to Copas"
6✔
1214
  end
1215

1216
  if not ok then
5,216✔
1217
    local k, e = pcall(_errhandlers[co] or _deferror, res, co, skt)
46✔
1218
    if not k then
46✔
1219
      print("Failed executing error handler: " .. tostring(e))
×
1220
    end
1221
  end
1222

1223
  local skt_to_close = _autoclose[co]
5,216✔
1224
  if skt_to_close then
5,216✔
1225
    skt_to_close:close()
120✔
1226
    _autoclose[co] = nil
120✔
1227
    _autoclose_r[skt_to_close] = nil
120✔
1228
  end
1229

1230
  _errhandlers[co] = nil
5,216✔
1231
end
1232

1233

1234
local _accept do
194✔
1235
  local client_counters = setmetatable({}, { __mode = "k" })
194✔
1236

1237
  -- accepts a connection on socket input
1238
  function _accept(server_skt, handler)
163✔
1239
    local client_skt = server_skt:accept()
126✔
1240
    if client_skt then
126✔
1241
      local count = (client_counters[server_skt] or 0) + 1
126✔
1242
      client_counters[server_skt] = count
126✔
1243
      object_names[client_skt] = object_names[server_skt] .. ":client_" .. count
140✔
1244

1245
      client_skt:settimeout(0)
126✔
1246
      copas.settimeouts(client_skt, user_timeouts_connect[server_skt],  -- copy server socket timeout settings
252✔
1247
        user_timeouts_send[server_skt], user_timeouts_receive[server_skt])
140✔
1248

1249
      local co = coroutine_create(handler)
126✔
1250
      object_names[co] = object_names[server_skt] .. ":handler_" .. count
126✔
1251

1252
      if copas.autoclose then
126✔
1253
        _autoclose[co] = client_skt
126✔
1254
        _autoclose_r[client_skt] = co
126✔
1255
      end
1256

1257
      _doTick(co, client_skt)
126✔
1258
    end
1259
  end
1260
end
1261

1262
-------------------------------------------------------------------------------
1263
-- Adds a server/handler pair to Copas dispatcher
1264
-------------------------------------------------------------------------------
1265

1266
do
1267
  local function addTCPserver(server, handler, timeout, name)
1268
    server:settimeout(0)
96✔
1269
    if name then
96✔
1270
      object_names[server] = name
×
1271
    end
1272
    _servers[server] = handler
96✔
1273
    _reading:insert(server)
96✔
1274
    if timeout then
96✔
1275
      copas.settimeout(server, timeout)
18✔
1276
    end
1277
  end
1278

1279
  local function addUDPserver(server, handler, timeout, name)
1280
    server:settimeout(0)
×
1281
    local co = coroutine_create(handler)
×
1282
    if name then
×
1283
      object_names[server] = name
×
1284
    end
1285
    object_names[co] = object_names[server]..":handler"
×
1286
    _reading:insert(server)
×
1287
    if timeout then
×
1288
      copas.settimeout(server, timeout)
×
1289
    end
1290
    _doTick(co, server)
×
1291
  end
1292

1293

1294
  function copas.addserver(server, handler, timeout, name)
194✔
1295
    if isTCP(server) then
112✔
1296
      addTCPserver(server, handler, timeout, name)
112✔
1297
    else
1298
      addUDPserver(server, handler, timeout, name)
×
1299
    end
1300
  end
1301
end
1302

1303

1304
function copas.removeserver(server, keep_open)
194✔
1305
  local skt = server
90✔
1306
  local mt = getmetatable(server)
90✔
1307
  if mt == _skt_mt_tcp or mt == _skt_mt_udp then
90✔
1308
    skt = server.socket
×
1309
  end
1310

1311
  _servers:remove(skt)
90✔
1312
  _reading:remove(skt)
90✔
1313

1314
  if keep_open then
90✔
1315
    return true
18✔
1316
  end
1317
  return server:close()
72✔
1318
end
1319

1320

1321

1322
-------------------------------------------------------------------------------
1323
-- Adds an new coroutine thread to Copas dispatcher
1324
-------------------------------------------------------------------------------
1325
function copas.addnamedthread(name, handler, ...)
194✔
1326
  if type(name) == "function" and type(handler) == "string" then
5,356✔
1327
    -- old call, flip args for compatibility
1328
    name, handler = handler, name
×
1329
  end
1330

1331
  -- create a coroutine that skips the first argument, which is always the socket
1332
  -- passed by the scheduler, but `nil` in case of a task/thread
1333
  local thread = coroutine_create(function(_, ...)
10,712✔
1334
    copas.pause()
5,356✔
1335
    return handler(...)
5,338✔
1336
  end)
1337
  if name then
5,356✔
1338
    object_names[thread] = name
466✔
1339
  end
1340

1341
  _threads[thread] = true -- register this thread so it can be removed
5,356✔
1342
  _doTick (thread, nil, ...)
5,356✔
1343
  return thread
5,356✔
1344
end
1345

1346

1347
function copas.addthread(handler, ...)
194✔
1348
  return copas.addnamedthread(nil, handler, ...)
4,824✔
1349
end
1350

1351

1352
function copas.removethread(thread)
194✔
1353
  -- if the specified coroutine is registered, add it to the canceled table so
1354
  -- that next time it tries to resume it exits.
1355
  _canceled[thread] = _threads[thread or 0]
54✔
1356
  _sleeping:cancel(thread)
54✔
1357
end
1358

1359

1360

1361
-------------------------------------------------------------------------------
1362
-- Sleep/pause management functions
1363
-------------------------------------------------------------------------------
1364

1365
-- yields the current coroutine and wakes it after 'sleeptime' seconds.
1366
-- If sleeptime < 0 then it sleeps until explicitly woken up using 'wakeup'
1367
-- TODO: deprecated, remove in next major
1368
function copas.sleep(sleeptime)
194✔
1369
  coroutine_yield((sleeptime or 0), _sleeping)
×
1370
end
1371

1372

1373
-- yields the current coroutine and wakes it after 'sleeptime' seconds.
1374
-- if sleeptime < 0 then it sleeps 0 seconds.
1375
function copas.pause(sleeptime)
194✔
1376
  local s = gettime()
239,741✔
1377
  if sleeptime and sleeptime > 0 then
239,741✔
1378
    coroutine_yield(sleeptime, _sleeping)
8,474✔
1379
  else
1380
    coroutine_yield(0, _sleeping)
232,455✔
1381
  end
1382
  return gettime() - s
239,505✔
1383
end
1384

1385

1386
-- yields the current coroutine until explicitly woken up using 'wakeup'
1387
function copas.pauseforever()
194✔
1388
  local s = gettime()
3,222✔
1389
  coroutine_yield(-1, _sleeping)
3,222✔
1390
  return gettime() - s
3,204✔
1391
end
1392

1393

1394
-- Wakes up a sleeping coroutine 'co'.
1395
function copas.wakeup(co)
194✔
1396
  _sleeping:wakeup(co)
3,234✔
1397
end
1398

1399

1400

1401
-------------------------------------------------------------------------------
1402
-- Timeout management
1403
-------------------------------------------------------------------------------
1404

1405
do
1406
  local timeout_register = setmetatable({}, { __mode = "k" })
194✔
1407
  local timerwheel = require("timerwheel").new({
389✔
1408
      now = gettime,
194✔
1409
      precision = TIMEOUT_PRECISION,
194✔
1410
      ringsize = math.floor(60*60*24/TIMEOUT_PRECISION),  -- ring size 1 day
194✔
1411
      err_handler = function(err)
1412
        return _deferror(err, core_timer_thread)
16✔
1413
      end,
1414
    })
1415

1416
  core_timer_thread = copas.addnamedthread("copas_core_timer", function()
388✔
1417
    while true do
1418
      copas.pause(TIMEOUT_PRECISION)
6,374✔
1419
      timerwheel:step()
7,227✔
1420
    end
1421
  end)
1422

1423
  -- get the number of timeouts running
1424
  function copas.gettimeouts()
194✔
1425
    return timerwheel:count()
2,701✔
1426
  end
1427

1428
  --- Sets the timeout for the current coroutine.
1429
  -- @param delay delay (seconds), use 0 (or math.huge) to cancel the timerout
1430
  -- @param callback function with signature: `function(coroutine)` where coroutine is the routine that timed-out
1431
  -- @return true
1432
  function copas.timeout(delay, callback)
194✔
1433
    local co = coroutine_running()
4,362,756✔
1434
    local existing_timer = timeout_register[co]
4,362,756✔
1435

1436
    if existing_timer then
4,362,756✔
1437
      timerwheel:cancel(existing_timer)
4,386✔
1438
    end
1439

1440
    if delay > 0 and delay ~= math.huge then
4,362,756✔
1441
      timeout_register[co] = timerwheel:set(delay, callback, co)
6,911✔
1442
    elseif delay == 0 or delay == math.huge then
4,356,830✔
1443
      timeout_register[co] = nil
4,356,830✔
1444
    else
1445
      error("timout value must be greater than or equal to 0, got: "..tostring(delay))
×
1446
    end
1447

1448
    return true
4,362,756✔
1449
  end
1450

1451
end
1452

1453

1454
-------------------------------------------------------------------------------
1455
-- main tasks: manage readable and writable socket sets
1456
-------------------------------------------------------------------------------
1457
-- a task is an object with a required method `step()` that deals with a
1458
-- single step for that task.
1459

1460
local _tasks = {} do
194✔
1461
  function _tasks:add(tsk)
194✔
1462
    _tasks[#_tasks + 1] = tsk
776✔
1463
  end
1464
end
1465

1466

1467
-- a task to check ready to read events
1468
local _readable_task = {} do
194✔
1469

1470
  _readable_task._events = {}
194✔
1471

1472
  local function tick(skt)
1473
    local handler = _servers[skt]
883✔
1474
    if handler then
883✔
1475
      _accept(skt, handler)
147✔
1476
    else
1477
      _reading:remove(skt)
757✔
1478
      _doTick(_reading:pop(skt), skt)
890✔
1479
    end
1480
  end
1481

1482
  function _readable_task:step()
194✔
1483
    for _, skt in ipairs(self._events) do
234,440✔
1484
      tick(skt)
883✔
1485
    end
1486
  end
1487

1488
  _tasks:add(_readable_task)
225✔
1489
end
1490

1491

1492
-- a task to check ready to write events
1493
local _writable_task = {} do
194✔
1494

1495
  _writable_task._events = {}
194✔
1496

1497
  local function tick(skt)
1498
    _writing:remove(skt)
371✔
1499
    _doTick(_writing:pop(skt), skt)
431✔
1500
  end
1501

1502
  function _writable_task:step()
194✔
1503
    for _, skt in ipairs(self._events) do
233,924✔
1504
      tick(skt)
371✔
1505
    end
1506
  end
1507

1508
  _tasks:add(_writable_task)
225✔
1509
end
1510

1511

1512

1513
-- sleeping threads task
1514
local _sleeping_task = {} do
194✔
1515

1516
  function _sleeping_task:step()
194✔
1517
    local now = gettime()
233,553✔
1518

1519
    local co = _sleeping:pop(now)
233,553✔
1520
    while co do
240,625✔
1521
      -- we're pushing them to _resumable, since that list will be replaced before
1522
      -- executing. This prevents tasks running twice in a row with pause(0) for example.
1523
      -- So here we won't execute, but at _resumable step which is next
1524
      _resumable:push(co)
7,072✔
1525
      co = _sleeping:pop(now)
8,262✔
1526
    end
1527
  end
1528

1529
  _tasks:add(_sleeping_task)
194✔
1530
end
1531

1532

1533

1534
-- resumable threads task
1535
local _resumable_task = {} do
194✔
1536

1537
  function _resumable_task:step()
194✔
1538
    -- replace the resume list before iterating, so items placed in there
1539
    -- will indeed end up in the next copas step, not in this one, and not
1540
    -- create a loop
1541
    local resumelist = _resumable:clear_resumelist()
233,553✔
1542

1543
    for _, co in ipairs(resumelist) do
476,358✔
1544
      _doTick(co)
242,813✔
1545
    end
1546
  end
1547

1548
  _tasks:add(_resumable_task)
194✔
1549
end
1550

1551

1552
-------------------------------------------------------------------------------
1553
-- Checks for reads and writes on sockets
1554
-------------------------------------------------------------------------------
1555
local _select_plain do
194✔
1556

1557
  local last_cleansing = 0
194✔
1558
  local duration = function(t2, t1) return t2-t1 end
233,662✔
1559

1560
  if not socket then
194✔
1561
    -- socket module unavailable, switch to luasystem sleep
1562
    _select_plain = block_sleep
6✔
1563
  else
1564
    -- use socket.select to handle socket-io
1565
    _select_plain = function(timeout)
1566
      local err
1567
      local now = gettime()
233,468✔
1568

1569
      -- remove any closed sockets to prevent select from hanging on them
1570
      if _closed[1] then
233,468✔
1571
        for i, skt in ipairs(_closed) do
441✔
1572
          _closed[i] = { _reading:remove(skt), _writing:remove(skt) }
296✔
1573
        end
1574
      end
1575

1576
      _readable_task._events, _writable_task._events, err = socket.select(_reading, _writing, timeout)
233,468✔
1577
      local r_events, w_events = _readable_task._events, _writable_task._events
233,468✔
1578

1579
      -- inject closed sockets in readable/writeable task so they can error out properly
1580
      if _closed[1] then
233,468✔
1581
        for i, skts in ipairs(_closed) do
441✔
1582
          _closed[i] = nil
222✔
1583
          r_events[#r_events+1] = skts[1]
222✔
1584
          w_events[#w_events+1] = skts[2]
222✔
1585
        end
1586
      end
1587

1588
      if duration(now, last_cleansing) > WATCH_DOG_TIMEOUT then
295,443✔
1589
        last_cleansing = now
182✔
1590

1591
        -- Check all sockets selected for reading, and check how long they have been waiting
1592
        -- for data already, without select returning them as readable
1593
        for skt,time in pairs(_reading_log) do
182✔
1594
          if not r_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
×
1595
            -- This one timedout while waiting to become readable, so move
1596
            -- it in the readable list and try and read anyway, despite not
1597
            -- having been returned by select
1598
            _reading_log[skt] = nil
×
1599
            r_events[#r_events + 1] = skt
×
1600
            r_events[skt] = #r_events
×
1601
          end
1602
        end
1603

1604
        -- Do the same for writing
1605
        for skt,time in pairs(_writing_log) do
182✔
1606
          if not w_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
×
1607
            _writing_log[skt] = nil
×
1608
            w_events[#w_events + 1] = skt
×
1609
            w_events[skt] = #w_events
×
1610
          end
1611
        end
1612
      end
1613

1614
      if err == "timeout" and #r_events + #w_events > 0 then
233,468✔
1615
        return nil
6✔
1616
      else
1617
        return err
233,462✔
1618
      end
1619
    end
1620
  end
1621
end
1622

1623

1624

1625
-------------------------------------------------------------------------------
1626
-- Dispatcher loop step.
1627
-- Listen to client requests and handles them
1628
-- Returns false if no socket-data was handled, or true if there was data
1629
-- handled (or nil + error message)
1630
-------------------------------------------------------------------------------
1631

1632
local copas_stats
1633
local min_ever, max_ever
1634

1635
local _select = _select_plain
194✔
1636

1637
-- instrumented version of _select() to collect stats
1638
local _select_instrumented = function(timeout)
1639
  if copas_stats then
×
1640
    local step_duration = gettime() - copas_stats.step_start
×
1641
    copas_stats.duration_max = math.max(copas_stats.duration_max, step_duration)
×
1642
    copas_stats.duration_min = math.min(copas_stats.duration_min, step_duration)
×
1643
    copas_stats.duration_tot = copas_stats.duration_tot + step_duration
×
1644
    copas_stats.steps = copas_stats.steps + 1
×
1645
  else
1646
    copas_stats = {
×
1647
      duration_max = -1,
1648
      duration_min = 999999,
1649
      duration_tot = 0,
1650
      steps = 0,
1651
    }
1652
  end
1653

1654
  local err = _select_plain(timeout)
×
1655

1656
  local now = gettime()
×
1657
  copas_stats.time_start = copas_stats.time_start or now
×
1658
  copas_stats.step_start = now
×
1659

1660
  return err
×
1661
end
1662

1663

1664
function copas.step(timeout)
194✔
1665
  -- Need to wake up the select call in time for the next sleeping event
1666
  if not _resumable:done() then
295,546✔
1667
    timeout = 0
227,580✔
1668
  else
1669
    timeout = math.min(_sleeping:getnext(), timeout or math.huge)
6,981✔
1670
  end
1671

1672
  local err = _select(timeout)
233,557✔
1673

1674
  for _, tsk in ipairs(_tasks) do
1,167,765✔
1675
    tsk:step()
934,220✔
1676
  end
1677

1678
  if err then
233,545✔
1679
    if err == "timeout" then
232,453✔
1680
      if timeout + 0.01 > TIMEOUT_PRECISION and math.random(100) > 90 then
232,364✔
1681
        -- we were idle, so occasionally do a GC sweep to ensure lingering
1682
        -- sockets are closed, and we don't accidentally block the loop from
1683
        -- exiting
1684
        collectgarbage()
416✔
1685
      end
1686
      return false
232,364✔
1687
    end
1688
    return nil, err
89✔
1689
  end
1690

1691
  return true
1,092✔
1692
end
1693

1694

1695
-------------------------------------------------------------------------------
1696
-- Check whether there is something to do.
1697
-- returns false if there are no sockets for read/write nor tasks scheduled
1698
-- (which means Copas is in an empty spin)
1699
-------------------------------------------------------------------------------
1700
function copas.finished()
194✔
1701
  return #_reading == 0 and #_writing == 0 and _resumable:done() and _sleeping:done(copas.gettimeouts())
234,995✔
1702
end
1703

1704

1705
local resetexit do
194✔
1706
  local exit_semaphore, exiting
1707

1708
  function resetexit()
163✔
1709
    exit_semaphore = copas.semaphore.new(1, 0, math.huge)
393✔
1710
    exiting = false
314✔
1711
  end
1712

1713
  -- Signals tasks to exit. But only if they check for it. By calling `copas.exiting`
1714
  -- they can check if they should exit. Or by calling `copas.waitforexit` they can
1715
  -- wait until the exit signal is given.
1716
  function copas.exit()
194✔
1717
    if exiting then return end
302✔
1718
    exiting = true
302✔
1719
    exit_semaphore:destroy()
302✔
1720
  end
1721

1722
  -- returns whether Copas is in the process of exiting. Exit can be started by
1723
  -- calling `copas.exit()`.
1724
  function copas.exiting()
194✔
1725
    return exiting
598✔
1726
  end
1727

1728
  -- Pauses the current coroutine until Copas is exiting. To be used as an exit
1729
  -- signal for tasks that need to clean up before exiting.
1730
  function copas.waitforexit()
194✔
1731
    exit_semaphore:take(1)
12✔
1732
  end
1733
end
1734

1735

1736
--- Forcibly cancels all pending work and signals exit.
1737
-- Intended for test teardown only. Abandons all registered threads and sockets
1738
-- without giving them a chance to clean up. After this call copas.finished()
1739
-- will return true and the loop will exit. The module is left in a clean state
1740
-- ready for the next copas.loop() call.
1741
function copas.cancelall()
194✔
1742
  -- 1. clear resumable queue
1743
  _resumable:clear_resumelist()
×
1744

1745
  -- 2. drain sleeping heap
1746
  _sleeping:cancelall()
×
1747

1748
  -- 3. close and drain reading sockets
1749
  while _reading[1] do
×
1750
    copas.close(_reading[1])
×
1751
    _reading:remove(_reading[1])
×
1752
  end
1753

1754
  -- 4. close and drain writing sockets
1755
  while _writing[1] do
×
1756
    copas.close(_writing[1])
×
1757
    _writing:remove(_writing[1])
×
1758
  end
1759

1760
  -- 5. remove all servers
1761
  while _servers[1] do
×
1762
    copas.removeserver(_servers[1])
×
1763
  end
1764

1765
  -- 6. clear non-weak ancillary tables
1766
  _closed = {}
×
1767
  _reading_log = {}
×
1768
  _writing_log = {}
×
1769

1770
  -- 7. signal exit
1771
  copas.exit()
×
1772
end
1773

1774

1775
local _getstats do
194✔
1776
  local _getstats_instrumented, _getstats_plain
1777

1778

1779
  function _getstats_plain(enable)
163✔
1780
    -- this function gets hit if turned off, so turn on if true
1781
    if enable == true then
×
1782
      _select = _select_instrumented
×
1783
      _getstats = _getstats_instrumented
×
1784
      -- reset stats
1785
      min_ever = nil
×
1786
      max_ever = nil
×
1787
      copas_stats = nil
×
1788
    end
1789
    return {}
×
1790
  end
1791

1792

1793
  -- convert from seconds to millisecs, with microsec precision
1794
  local function useconds(t)
1795
    return math.floor((t * 1000000) + 0.5) / 1000
×
1796
  end
1797
  -- convert from seconds to seconds, with millisec precision
1798
  local function mseconds(t)
1799
    return math.floor((t * 1000) + 0.5) / 1000
×
1800
  end
1801

1802

1803
  function _getstats_instrumented(enable)
163✔
1804
    if enable == false then
×
1805
      _select = _select_plain
×
1806
      _getstats = _getstats_plain
×
1807
      -- instrumentation disabled, so switch to the plain implementation
1808
      return _getstats(enable)
×
1809
    end
1810
    if (not copas_stats) or (copas_stats.step == 0) then
×
1811
      return {}
×
1812
    end
1813
    local stats = copas_stats
×
1814
    copas_stats = nil
×
1815
    min_ever = math.min(min_ever or 9999999, stats.duration_min)
×
1816
    max_ever = math.max(max_ever or 0, stats.duration_max)
×
1817
    stats.duration_min_ever = min_ever
×
1818
    stats.duration_max_ever = max_ever
×
1819
    stats.duration_avg = stats.duration_tot / stats.steps
×
1820
    stats.step_start = nil
×
1821
    stats.time_end = gettime()
×
1822
    stats.time_tot = stats.time_end - stats.time_start
×
1823
    stats.time_avg = stats.time_tot / stats.steps
×
1824

1825
    stats.duration_avg = useconds(stats.duration_avg)
×
1826
    stats.duration_max = useconds(stats.duration_max)
×
1827
    stats.duration_max_ever = useconds(stats.duration_max_ever)
×
1828
    stats.duration_min = useconds(stats.duration_min)
×
1829
    stats.duration_min_ever = useconds(stats.duration_min_ever)
×
1830
    stats.duration_tot = useconds(stats.duration_tot)
×
1831
    stats.time_avg = useconds(stats.time_avg)
×
1832
    stats.time_start = mseconds(stats.time_start)
×
1833
    stats.time_end = mseconds(stats.time_end)
×
1834
    stats.time_tot = mseconds(stats.time_tot)
×
1835
    return stats
×
1836
  end
1837

1838
  _getstats = _getstats_plain
194✔
1839
end
1840

1841

1842
function copas.status(enable_stats)
194✔
1843
  local res = _getstats(enable_stats)
×
1844
  res.running = not not copas.running
×
1845
  res.timeout = copas.gettimeouts()
×
1846
  res.timer, res.inactive = _sleeping:status()
×
1847
  res.read = #_reading
×
1848
  res.write = #_writing
×
1849
  res.active = _resumable:count()
×
1850
  return res
×
1851
end
1852

1853

1854
-------------------------------------------------------------------------------
1855
-- Dispatcher endless loop.
1856
-- Listen to client requests and handles them forever
1857
-------------------------------------------------------------------------------
1858
function copas.loop(initializer, timeout)
225✔
1859
  if type(initializer) == "function" then
314✔
1860
    copas.addnamedthread("copas_initializer", initializer)
141✔
1861
  else
1862
    timeout = initializer or timeout
192✔
1863
  end
1864

1865
  resetexit()
314✔
1866
  copas.running = true
314✔
1867
  while true do
1868
    copas.step(timeout)
233,557✔
1869
    if copas.finished() then
295,532✔
1870
      if copas.exiting() then
695✔
1871
        break
149✔
1872
      end
1873
      copas.exit()
296✔
1874
    end
1875
  end
1876
  copas.running = false
302✔
1877
end
1878

1879

1880
-------------------------------------------------------------------------------
1881
-- Naming sockets and coroutines.
1882
-------------------------------------------------------------------------------
1883
do
1884
  local function realsocket(skt)
1885
    local mt = getmetatable(skt)
90✔
1886
    if mt == _skt_mt_tcp or mt == _skt_mt_udp then
90✔
1887
      return skt.socket
90✔
1888
    else
1889
      return skt
×
1890
    end
1891
  end
1892

1893

1894
  function copas.setsocketname(name, skt)
225✔
1895
    assert(type(name) == "string", "expected arg #1 to be a string")
90✔
1896
    skt = assert(realsocket(skt), "expected arg #2 to be a socket")
105✔
1897
    object_names[skt] = name
90✔
1898
  end
1899

1900

1901
  function copas.getsocketname(skt)
225✔
1902
    skt = assert(realsocket(skt), "expected arg #1 to be a socket")
×
1903
    return object_names[skt]
×
1904
  end
1905
end
1906

1907

1908
function copas.setthreadname(name, coro)
225✔
1909
  assert(type(name) == "string", "expected arg #1 to be a string")
60✔
1910
  coro = coro or coroutine_running()
60✔
1911
  assert(type(coro) == "thread", "expected arg #2 to be a coroutine or nil")
60✔
1912
  object_names[coro] = name
60✔
1913
end
1914

1915

1916
function copas.getthreadname(coro)
225✔
1917
  coro = coro or coroutine_running()
38✔
1918
  assert(type(coro) == "thread", "expected arg #1 to be a coroutine or nil")
38✔
1919
  return object_names[coro]
40✔
1920
end
1921

1922
-------------------------------------------------------------------------------
1923
-- Debug functionality.
1924
-------------------------------------------------------------------------------
1925
do
1926
  copas.debug = {}
194✔
1927

1928
  local log_core    -- if truthy, the core-timer will also be logged
1929
  local debug_log   -- function used as logger
1930

1931

1932
  local debug_yield = function(skt, queue)
1933
    local name = object_names[coroutine_running()]
5,570✔
1934

1935
    if log_core or name ~= "copas_core_timer" then
5,570✔
1936
      if queue == _sleeping then
5,549✔
1937
        debug_log("yielding '", name, "' to SLEEP for ", skt," seconds")
5,485✔
1938

1939
      elseif queue == _writing then
64✔
1940
        debug_log("yielding '", name, "' to WRITE on '", object_names[skt], "'")
14✔
1941

1942
      elseif queue == _reading then
52✔
1943
        debug_log("yielding '", name, "' to READ on '", object_names[skt], "'")
54✔
1944

1945
      else
1946
        debug_log("thread '", name, "' yielding to unexpected queue; ", tostring(queue), " (", type(queue), ")", debug.traceback())
×
1947
      end
1948
    end
1949

1950
    return coroutine.yield(skt, queue)
5,570✔
1951
  end
1952

1953

1954
  local debug_resume = function(coro, skt, ...)
1955
    local name = object_names[coro]
5,582✔
1956

1957
    if skt then
5,582✔
1958
      debug_log("resuming '", name, "' for socket '", object_names[skt], "'")
64✔
1959
    else
1960
      if log_core or name ~= "copas_core_timer" then
5,518✔
1961
        debug_log("resuming '", name, "'")
5,497✔
1962
      end
1963
    end
1964
    return coroutine.resume(coro, skt, ...)
5,582✔
1965
  end
1966

1967

1968
  local debug_create = function(f)
1969
    local f_wrapped = function(...)
1970
      local results = pack(f(...))
14✔
1971
      debug_log("exiting '", object_names[coroutine_running()], "'")
12✔
1972
      return unpack(results)
12✔
1973
    end
1974

1975
    return coroutine.create(f_wrapped)
12✔
1976
  end
1977

1978

1979
  debug_log = fnil
194✔
1980

1981

1982
  -- enables debug output for all coroutine operations.
1983
  function copas.debug.start(logger, core)
388✔
1984
    log_core = core
6✔
1985
    debug_log = logger or print
6✔
1986
    coroutine_yield = debug_yield
6✔
1987
    coroutine_resume = debug_resume
6✔
1988
    coroutine_create = debug_create
6✔
1989
  end
1990

1991

1992
  -- disables debug output for coroutine operations.
1993
  function copas.debug.stop()
388✔
1994
    debug_log = fnil
×
1995
    coroutine_yield = coroutine.yield
×
1996
    coroutine_resume = coroutine.resume
×
1997
    coroutine_create = coroutine.create
×
1998
  end
1999

2000
  do
2001
    local call_id = 0
194✔
2002

2003
    -- Description table of socket functions for debug output.
2004
    -- each socket function name has TWO entries;
2005
    -- 'name_in' and 'name_out', each being an array of names/descriptions of respectively
2006
    -- input parameters and return values.
2007
    -- If either table has a 'callback' key, then that is a function that will be called
2008
    -- with the parameters/return-values for further inspection.
2009
    local args = {
194✔
2010
      settimeout_in = {
194✔
2011
        "socket ",
163✔
2012
        "seconds",
163✔
2013
        "mode   ",
2014
      },
194✔
2015
      settimeout_out = {
194✔
2016
        "success",
163✔
2017
        "error  ",
2018
      },
194✔
2019
      connect_in = {
194✔
2020
        "socket ",
163✔
2021
        "address",
163✔
2022
        "port   ",
2023
      },
194✔
2024
      connect_out = {
194✔
2025
        "success",
163✔
2026
        "error  ",
2027
      },
194✔
2028
      getfd_in = {
194✔
2029
        "socket ",
2030
        -- callback = function(...)
2031
        --   print(debug.traceback("called from:", 4))
2032
        -- end,
2033
      },
194✔
2034
      getfd_out = {
194✔
2035
        "fd",
2036
      },
194✔
2037
      send_in = {
194✔
2038
        "socket   ",
163✔
2039
        "data     ",
163✔
2040
        "idx-start",
163✔
2041
        "idx-end  ",
2042
      },
194✔
2043
      send_out = {
194✔
2044
        "last-idx-send    ",
163✔
2045
        "error            ",
163✔
2046
        "err-last-idx-send",
2047
      },
194✔
2048
      receive_in = {
194✔
2049
        "socket ",
163✔
2050
        "pattern",
163✔
2051
        "prefix ",
2052
      },
194✔
2053
      receive_out = {
194✔
2054
        "received    ",
163✔
2055
        "error       ",
163✔
2056
        "partial data",
2057
      },
194✔
2058
      dirty_in = {
194✔
2059
        "socket",
2060
        -- callback = function(...)
2061
        --   print(debug.traceback("called from:", 4))
2062
        -- end,
2063
      },
194✔
2064
      dirty_out = {
194✔
2065
        "data in read-buffer",
2066
      },
194✔
2067
      close_in = {
194✔
2068
        "socket",
2069
        -- callback = function(...)
2070
        --   print(debug.traceback("called from:", 4))
2071
        -- end,
2072
      },
194✔
2073
      close_out = {
194✔
2074
        "success",
163✔
2075
        "error",
2076
      },
194✔
2077
    }
2078
    local function print_call(func, msg, ...)
2079
      print(msg)
884✔
2080
      local arg = pack(...)
884✔
2081
      local desc = args[func] or {}
884✔
2082
      for i = 1, math.max(arg.n, #desc) do
1,912✔
2083
        local value = arg[i]
1,028✔
2084
        if type(value) == "string" then
1,028✔
2085
          local xvalue = value:sub(1,30)
36✔
2086
          if xvalue ~= value then
36✔
2087
            xvalue = xvalue .."(...truncated)"
×
2088
          end
2089
          print("\t"..(desc[i] or i)..": '"..tostring(xvalue).."' ("..type(value).." #"..#value..")")
36✔
2090
        else
2091
          print("\t"..(desc[i] or i)..": '"..tostring(value).."' ("..type(value)..")")
992✔
2092
        end
2093
      end
2094
      if desc.callback then
884✔
2095
        desc.callback(...)
×
2096
      end
2097
    end
2098

2099
    local debug_mt = {
194✔
2100
      __index = function(self, key)
2101
        local value = self.__original_socket[key]
442✔
2102
        if type(value) ~= "function" then
442✔
2103
          return value
×
2104
        end
2105
        return function(self2, ...)
2106
            local my_id = call_id + 1
442✔
2107
            call_id = my_id
442✔
2108
            local results
2109

2110
            if self2 ~= self then
442✔
2111
              -- there is no self
2112
              print_call(tostring(key).."_in", my_id .. "-calling '"..tostring(key) .. "' with; ", self, ...)
×
2113
              results = pack(value(self, ...))
×
2114
            else
2115
              print_call(tostring(key).."_in", my_id .. "-calling '" .. tostring(key) .. "' with; ", self.__original_socket, ...)
442✔
2116
              results = pack(value(self.__original_socket, ...))
549✔
2117
            end
2118
            print_call(tostring(key).."_out", my_id .. "-results '"..tostring(key) .. "' returned; ", unpack(results))
549✔
2119
            return unpack(results)
442✔
2120
          end
2121
      end,
2122
      __tostring = function(self)
2123
        return tostring(self.__original_socket)
48✔
2124
      end
2125
    }
2126

2127

2128
    -- wraps a socket (copas or luasocket) in a debug version printing all calls
2129
    -- and their parameters/return values. Extremely noisy!
2130
    -- returns the wrapped socket.
2131
    -- NOTE: only for plain sockets, will not support TLS
2132
    function copas.debug.socket(original_skt)
388✔
2133
      if (getmetatable(original_skt) == _skt_mt_tcp) or (getmetatable(original_skt) == _skt_mt_udp) then
12✔
2134
        -- already wrapped as Copas socket, so recurse with the original luasocket one
2135
        original_skt.socket = copas.debug.socket(original_skt.socket)
×
2136
        return original_skt
×
2137
      end
2138

2139
      local proxy = setmetatable({
24✔
2140
        __original_socket = original_skt
12✔
2141
      }, debug_mt)
12✔
2142

2143
      return proxy
12✔
2144
    end
2145
  end
2146
end
2147

2148

2149
return copas
194✔
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