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

lunarmodules / copas / 30290467123

27 Jul 2026 05:42PM UTC coverage: 84.244% (-1.1%) from 85.334%
30290467123

push

github

web-flow
Merge 53b2d76fc into c0eb97fbe

43 of 71 new or added lines in 1 file covered. (60.56%)

106 existing lines in 4 files now uncovered.

1465 of 1739 relevant lines covered (84.24%)

59952.65 hits per line

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

78.63
/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
240,617✔
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
784✔
63
local pack = function(...) return { n = select("#", ...), ...} end
1,070✔
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✔
68
  pcall = require("coxpcall").pcall
32✔
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)
204✔
157
    if key ~= nil then
204✔
158
      rawset(self, key, name)
204✔
159
    end
160
    return name
204✔
161
  end
162
})
163

164
-------------------------------------------------------------------------------
165
-- Simple set implementation
166
-- tracks at most one waiting coroutine per 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
2,542✔
179
        self[#self + 1] = skt
1,325✔
180
        reverse[skt] = #self
1,325✔
181
        return skt
1,325✔
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,877✔
189
      if index then
1,877✔
190
        reverse[skt] = nil
1,319✔
191
        local top = self[#self]
1,319✔
192
        self[#self] = nil
1,319✔
193
        if top ~= skt then
1,319✔
194
          reverse[top] = index
173✔
195
          self[index] = top
173✔
196
        end
197
        return skt
1,319✔
198
      end
199
    end
200

201
  end
202

203
  do  -- single-waiter implementation
204
    -- At most one coroutine may be waiting on a given socket at a time: a
205
    -- socket is only ever driven by one logical owner, so a second,
206
    -- concurrent claim indicates a bug rather than a case to queue for.
207
    local waiters = setmetatable({}, { __mode = "k" }) -- coroutine by socket
600✔
208

209
    -- Registers the coroutine as the socket's sole waiter, to be resumed
210
    -- once the socket becomes ready.
211
    -- @return true on success, or nil + error message if another coroutine
212
    -- is already waiting on this socket (same wording LuaSocket itself uses
213
    -- for the equivalent conflict at the OS-socket level, eg. calling
214
    -- connect() again before the first non-blocking attempt completes).
215
    function set:claim(skt, co)
600✔
216
      if waiters[skt] then
1,229✔
217
        return nil, "Operation already in progress"
12✔
218
      end
219
      waiters[skt] = co
1,217✔
220
      return true
1,217✔
221
    end
222

223
    -- Clears and returns the coroutine waiting on the socket, or nil if
224
    -- none is waiting.
225
    function set:release(skt)
600✔
226
      local co = waiters[skt]
1,217✔
227
      waiters[skt] = nil
1,217✔
228
      return co
1,217✔
229
    end
230

231
    -- Drops the socket from the set and discards its waiting coroutine (if
232
    -- any), without resuming it. Only call this when the socket is being
233
    -- discarded outright and no waiter is expected to be woken through the
234
    -- normal readiness path (e.g. the socket object itself is being thrown
235
    -- away). Do not use this as a general substitute for `remove`: if a
236
    -- waiter still needs to observe the outcome (for example a coroutine
237
    -- waiting on a socket that is being closed, which is resumed via the
238
    -- normal tick()/release() path with a "closed" result), purging here
239
    -- would silently drop it instead.
240
    function set:purge(skt)
600✔
NEW
241
      waiters[skt] = nil
×
NEW
242
      self:remove(skt)
×
243
    end
244

245
  end
246

247
  return set
600✔
248
end
249

250

251

252
-- Threads immediately resumable
253
local _resumable = {} do
200✔
254
  local resumelist = {}
200✔
255

256
  function _resumable:push(co)
200✔
257
    resumelist[#resumelist + 1] = co
240,200✔
258
  end
259

260
  function _resumable:clear_resumelist()
200✔
261
    local lst = resumelist
230,772✔
262
    resumelist = {}
230,772✔
263
    return lst
230,772✔
264
  end
265

266
  function _resumable:done()
200✔
267
    return resumelist[1] == nil
235,141✔
268
  end
269

270
  function _resumable:count()
200✔
UNCOV
271
    return #resumelist + #_resumable
×
272
  end
273

274
end
275

276

277

278
-- Similar to the socket set above, but tailored for the use of
279
-- sleeping threads
280
local _sleeping = {} do
200✔
281

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

285

286
  -- Required base implementation
287
  -----------------------------------------
288
  _sleeping.insert = fnil
200✔
289
  _sleeping.remove = fnil
200✔
290

291
  -- push a new timer on the heap
292
  function _sleeping:push(sleeptime, co)
200✔
293
    if sleeptime < 0 then
240,387✔
294
      lethargy[co] = true
3,378✔
295
    elseif sleeptime == 0 then
237,009✔
296
      _resumable:push(co)
288,397✔
297
    else
298
      heap:insert(gettime() + sleeptime, co)
8,251✔
299
    end
300
  end
301

302
  -- find the thread that should wake up to the time, if any
303
  function _sleeping:pop(time)
200✔
304
    if time < (heap:peekValue() or math.huge) then
300,092✔
305
      return
230,772✔
306
    end
307
    return heap:pop()
8,028✔
308
  end
309

310
  -- additional methods for time management
311
  -----------------------------------------
312
  function _sleeping:getnext()  -- returns delay until next sleep expires, or nil if there is none
200✔
313
    local t = heap:peekValue()
6,971✔
314
    if t then
6,971✔
315
      -- never report less than 0, because select() might block
316
      return math.max(t - gettime(), 0)
6,971✔
317
    end
318
  end
319

320
  function _sleeping:wakeup(co)
200✔
321
    if lethargy[co] then
3,384✔
322
      lethargy[co] = nil
3,318✔
323
      _resumable:push(co)
3,318✔
324
      return true
3,318✔
325
    end
326
    if heap:remove(co) then
77✔
327
      _resumable:push(co)
12✔
328
      return true
12✔
329
    end
330
    return nil, "not sleeping"
54✔
331
  end
332

333
  -- non-destructive check; unlike wakeup/cancel it doesn't remove 'co'
334
  function _sleeping:issleeping(co)
200✔
335
    if lethargy[co] then
606✔
336
      return true
564✔
337
    end
338
    return heap:valueByPayload(co) ~= nil
49✔
339
  end
340

341
  function _sleeping:cancel(co)
200✔
342
    lethargy[co] = nil
66✔
343
    heap:remove(co)
66✔
344
  end
345

346
  function _sleeping:cancelall()
200✔
UNCOV
347
    while heap:size() > 0 do heap:pop() end
×
348
    heap:insert(gettime() + TIMEOUT_PRECISION, core_timer_thread)
×
349
    -- lethargy is weak; copas's idle GC sweeps will clean it within a few steps
350
  end
351

352
  -- @param tos number of timeouts running
353
  function _sleeping:done(tos)
200✔
354
    -- return true if we have nothing more to do
355
    -- the timeout task doesn't qualify as work (fallbacks only),
356
    -- the lethargy also doesn't qualify as work ('dead' tasks),
357
    -- but the combination of a timeout + a lethargy can be work
358
    return heap:size() == 1       -- 1 means only the timeout-timer task is running
4,153✔
359
           and not (tos > 0 and next(lethargy))
3,559✔
360
  end
361

362
  -- gets number of threads in binaryheap and lethargy
363
  function _sleeping:status()
200✔
UNCOV
364
    local c = 0
×
365
    for _ in pairs(lethargy) do c = c + 1 end
×
366

UNCOV
367
    return heap:size(), c
×
368
  end
369

370
end   -- _sleeping
371

372

373

374
-------------------------------------------------------------------------------
375
-- Tracking coroutines and sockets
376
-------------------------------------------------------------------------------
377

378
local _servers = newsocketset() -- servers being handled
200✔
379
local _threads = setmetatable({}, {__mode = "k"})  -- registered threads added with addthread()
200✔
380
local _canceled = setmetatable({}, {__mode = "k"}) -- threads that are canceled and pending removal
200✔
381
local _autoclose = setmetatable({}, {__mode = "kv"}) -- sockets (value) to close when a thread (key) exits
200✔
382
local _autoclose_r = setmetatable({}, {__mode = "kv"}) -- reverse: sockets (key) to close when a thread (value) exits
200✔
383

384

385
-- for each socket we log the last read and last write times to enable the
386
-- watchdog to follow up if it takes too long.
387
-- tables contain the time, indexed by the socket
388
local _reading_log = {}
200✔
389
local _writing_log = {}
200✔
390

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

393
local _reading = newsocketset() -- sockets currently being read
200✔
394
local _writing = newsocketset() -- sockets currently being written
200✔
395
local _isSocketTimeout = { -- set of errors indicating a socket-timeout
200✔
396
  ["timeout"] = true,      -- default LuaSocket timeout
168✔
397
  ["wantread"] = true,     -- LuaSec specific timeout
168✔
398
  ["wantwrite"] = true,    -- LuaSec specific timeout
168✔
399
}
400

401
-------------------------------------------------------------------------------
402
-- Coroutine based socket timeouts.
403
-------------------------------------------------------------------------------
404
local user_timeouts_connect
405
local user_timeouts_send
406
local user_timeouts_receive
407
do
408
  local timeout_mt = {
200✔
409
    __mode = "k",
168✔
410
    __index = function(self, skt)
411
      -- if there is no timeout found, we insert one automatically, to block forever
412
      self[skt] = math.huge
474✔
413
      return self[skt]
474✔
414
    end,
415
  }
416

417
  user_timeouts_connect = setmetatable({}, timeout_mt)
200✔
418
  user_timeouts_send = setmetatable({}, timeout_mt)
200✔
419
  user_timeouts_receive = setmetatable({}, timeout_mt)
200✔
420
end
421

422
local useSocketTimeoutErrors = setmetatable({},{ __mode = "k" })
200✔
423

424

425
-- sto = socket-time-out
426
local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
200✔
427

428
  local socket_register = setmetatable({}, { __mode = "k" })    -- socket by coroutine
200✔
429
  local operation_register = setmetatable({}, { __mode = "k" }) -- operation "read"/"write" by coroutine
200✔
430
  local timeout_flags = setmetatable({}, { __mode = "k" })      -- true if timedout, by coroutine
200✔
431

432

433
  -- The callback called when a socket timeout occurs.
434
  local function socket_callback(co)
435
    local skt = socket_register[co]
84✔
436
    local queue = operation_register[co]
84✔
437

438
    -- flag the timeout and resume the coroutine
439
    timeout_flags[co] = true
84✔
440
    _resumable:push(co)
84✔
441

442
    -- release our claim on the socket and stop watching it; the timer, not
443
    -- the readiness path, is resuming `co`, so nothing else will do this
444
    if queue == "read" then
84✔
445
      _reading:release(skt)
72✔
446
      _reading:remove(skt)
84✔
447
    elseif queue == "write" then
12✔
448
      _writing:release(skt)
12✔
449
      _writing:remove(skt)
14✔
450
    else
UNCOV
451
      error("bad queue name; expected 'read'/'write', got: "..tostring(queue))
×
452
    end
453
  end
454

455

456
  -- Sets a socket timeout.
457
  -- Calling it as `sto_timeout()` will cancel the timeout.
458
  -- @param skt (socket) the socket on which to operate, use 'nil' to cancel the current timeout
459
  -- @param queue (string) the queue the socket is currently in: "read" or "write"
460
  -- @param use_connect_to (bool) if truthy, use the connect timeout instead of the
461
  --   read/write timeout implied by queue. Needed because connect also uses the "write"
462
  --   queue, so the queue value alone cannot distinguish connect from send operations.
463
  -- @return true
UNCOV
464
  function sto_timeout(skt, queue, use_connect_to)
168✔
465
    local co = coroutine_running()
4,297,150✔
466
    socket_register[co] = skt
4,297,150✔
467
    operation_register[co] = queue
4,297,150✔
468
    timeout_flags[co] = nil
4,297,150✔
469
    if skt then
4,297,150✔
470
      local to = (use_connect_to and user_timeouts_connect[skt]) or
2,148,586✔
471
                 (queue == "read" and user_timeouts_receive[skt]) or
2,148,260✔
472
                 user_timeouts_send[skt]
15,228✔
473
      copas.timeout(to, socket_callback)
2,715,301✔
474
    else
475
      copas.timeout(0)
2,148,575✔
476
    end
477
    return true
4,297,150✔
478
  end
479

480

481
  -- Changes the timeout to a different queue (read/write).
482
  -- Only usefull with ssl-handshakes and "wantread", "wantwrite" errors, when
483
  -- the queue has to be changed, so the timeout handler knows where to find the socket.
484
  -- @param queue (string) the new queue the socket is in, must be either "read" or "write"
485
  -- @return true
UNCOV
486
  function sto_change_queue(queue)
168✔
487
    operation_register[coroutine_running()] = queue
1,001✔
488
    return true
1,001✔
489
  end
490

491

492
  -- Responds with `true` if the operation timed-out.
UNCOV
493
  function sto_timed_out()
168✔
494
    return timeout_flags[coroutine_running()]
1,313✔
495
  end
496

497

498
  -- Returns the proper timeout error
UNCOV
499
  function sto_error(err)
168✔
500
    return useSocketTimeoutErrors[coroutine_running()] and err or "timeout"
84✔
501
  end
502

503
  -- only in case of testing export some internals
504
  if _G._TEST then
200✔
505
    copas._socket_register = socket_register
6✔
506
    copas._operation_register = operation_register
6✔
507
    copas._timeout_flags = timeout_flags
6✔
508
  end
509
end
510

511

512

513
-- Claims the socket for the current coroutine and yields to wait for it to
514
-- become ready, returning `true` once resumed.
515
-- @return nil + error if another coroutine is already waiting on this
516
-- socket (a bug in the caller, not a Copas failure). Callers must not
517
-- yield in that case: the claim was never taken, so nothing would ever
518
-- wake this coroutine up again.
519
local function wait_on(queue, skt)
520
  local claimed, err = queue:claim(skt, coroutine_running())
1,229✔
521
  if not claimed then
1,229✔
522
    return nil, err
12✔
523
  end
524
  queue:insert(skt)
1,217✔
525
  coroutine_yield(skt, queue)
1,217✔
526
  return true
1,217✔
527
end
528

529

530
-------------------------------------------------------------------------------
531
-- Coroutine based socket I/O functions.
532
-------------------------------------------------------------------------------
533

534
-- Returns "tcp"" for plain TCP and "ssl" for ssl-wrapped sockets, so truthy
535
-- for tcp based, and falsy for udp based.
536
local isTCP do
200✔
537
  local lookup = {
200✔
538
    tcp = "tcp",
168✔
539
    SSL = "ssl",
168✔
540
  }
541

UNCOV
542
  function isTCP(socket)
168✔
543
    return lookup[tostring(socket):sub(1,3)]
805✔
544
  end
545
end
546

547
function copas.close(skt, ...)
200✔
548
  _closed[#_closed+1] = skt
228✔
549
  return skt:close(...)
228✔
550
end
551

552

553

554
-- nil or negative is indefinitly
555
function copas.settimeout(skt, timeout)
200✔
556
  timeout = timeout or -1
228✔
557
  if type(timeout) ~= "number" then
228✔
558
    return nil, "timeout must be 'nil' or a number"
18✔
559
  end
560

561
  return copas.settimeouts(skt, timeout, timeout, timeout)
210✔
562
end
563

564
-- negative is indefinitly, nil means do not change
565
function copas.settimeouts(skt, connect, send, read)
200✔
566

567
  if connect ~= nil and type(connect) ~= "number" then
456✔
UNCOV
568
    return nil, "connect timeout must be 'nil' or a number"
×
569
  end
570
  if connect then
456✔
571
    if connect < 0 then
456✔
UNCOV
572
      connect = nil
×
573
    end
574
    user_timeouts_connect[skt] = connect
456✔
575
  end
576

577

578
  if send ~= nil and type(send) ~= "number" then
456✔
UNCOV
579
    return nil, "send timeout must be 'nil' or a number"
×
580
  end
581
  if send then
456✔
582
    if send < 0 then
456✔
UNCOV
583
      send = nil
×
584
    end
585
    user_timeouts_send[skt] = send
456✔
586
  end
587

588

589
  if read ~= nil and type(read) ~= "number" then
456✔
UNCOV
590
    return nil, "read timeout must be 'nil' or a number"
×
591
  end
592
  if read then
456✔
593
    if read < 0 then
456✔
UNCOV
594
      read = nil
×
595
    end
596
    user_timeouts_receive[skt] = read
456✔
597
  end
598

599

600
  return true
456✔
601
end
602

603
-- reads a pattern from a client and yields to the reading set on timeouts
604
-- UDP: a UDP socket expects a second argument to be a number, so it MUST
605
-- be provided as the 'pattern' below defaults to a string. Will throw a
606
-- 'bad argument' error if omitted.
607
-- SECURITY: the default pattern "*l" has no maximum length, matching LuaSocket
608
-- and Lua file-io semantics. It buffers until a newline/EOF/error, and the
609
-- per-operation timeout resets on every partial receive, so it does not bound
610
-- the accumulated size either. Do not use the default line-read directly on
611
-- untrusted/remote input without an application-enforced size limit; use a
612
-- numeric (sized) pattern or receivepartial with your own cumulative cap instead.
613
function copas.receive(client, pattern, part)
200✔
614
  local s, err
615
  pattern = pattern or "*l"
2,132,993✔
616
  local current_log = _reading_log
2,132,993✔
617
  sto_timeout(client, "read")
2,132,993✔
618

619
  repeat
620
    s, err, part = client:receive(pattern, part)
2,133,612✔
621

622
    -- guarantees that high throughput doesn't take other threads to starvation
623
    if (math.random(100) > 90) then
2,133,612✔
624
      copas.pause()
213,297✔
625
    end
626

627
    if s then
2,133,612✔
628
      current_log[client] = nil
2,132,873✔
629
      sto_timeout()
2,132,873✔
630
      return s, err, part
2,132,873✔
631

632
    elseif not _isSocketTimeout[err] then
739✔
633
      current_log[client] = nil
48✔
634
      sto_timeout()
48✔
635
      return s, err, part
48✔
636

637
    elseif sto_timed_out() then
810✔
638
      current_log[client] = nil
66✔
639
      sto_timeout()
66✔
640
      return nil, sto_error(err), part
77✔
641
    end
642

643
    if err == "wantwrite" then -- wantwrite may be returned during SSL renegotiations
625✔
UNCOV
644
      current_log = _writing_log
×
645
      current_log[client] = gettime()
×
646
      sto_change_queue("write")
×
NEW
647
      local ok, werr = wait_on(_writing, client)
×
NEW
648
      if not ok then
×
NEW
649
        current_log[client] = nil
×
NEW
650
        sto_timeout()
×
NEW
651
        return nil, werr, part
×
652
      end
653
    else
654
      current_log = _reading_log
625✔
655
      current_log[client] = gettime()
625✔
656
      sto_change_queue("read")
625✔
657
      local ok, werr = wait_on(_reading, client)
625✔
658
      if not ok then
625✔
659
        current_log[client] = nil
6✔
660
        sto_timeout()
6✔
661
        return nil, werr, part
6✔
662
      end
663
    end
664
  until false
619✔
665
end
666

667
-- receives data from a client over UDP. Not available for TCP.
668
-- (this is a copy of receive() method, adapted for receivefrom() use)
669
function copas.receivefrom(client, size)
200✔
670
  local s, err, port
671
  size = size or UDP_DATAGRAM_MAX
24✔
672
  sto_timeout(client, "read")
24✔
673

674
  repeat
675
    s, err, port = client:receivefrom(size) -- upon success err holds ip address
48✔
676

677
    -- garantees that high throughput doesn't take other threads to starvation
678
    if (math.random(100) > 90) then
48✔
679
      copas.pause()
7✔
680
    end
681

682
    if s then
48✔
683
      _reading_log[client] = nil
18✔
684
      sto_timeout()
18✔
685
      return s, err, port
18✔
686

687
    elseif err ~= "timeout" then
30✔
UNCOV
688
      _reading_log[client] = nil
×
689
      sto_timeout()
×
690
      return s, err, port
×
691

692
    elseif sto_timed_out() then
35✔
693
      _reading_log[client] = nil
6✔
694
      sto_timeout()
6✔
695
      return nil, sto_error(err), port
7✔
696
    end
697

698
    _reading_log[client] = gettime()
24✔
699
    local ok, werr = wait_on(_reading, client)
24✔
700
    if not ok then
24✔
NEW
701
      _reading_log[client] = nil
×
NEW
702
      sto_timeout()
×
NEW
703
      return nil, werr, port
×
704
    end
705
  until false
24✔
706
end
707

708
-- same as above but with special treatment when reading chunks,
709
-- unblocks on any data received.
710
function copas.receivepartial(client, pattern, part)
200✔
711
  local s, err
712
  pattern = pattern or "*l"
12✔
713
  local orig_size = #(part or "")
12✔
714
  local current_log = _reading_log
12✔
715
  sto_timeout(client, "read")
12✔
716

717
  repeat
718
    s, err, part = client:receive(pattern, part)
18✔
719

720
    -- guarantees that high throughput doesn't take other threads to starvation
721
    if (math.random(100) > 90) then
18✔
UNCOV
722
      copas.pause()
×
723
    end
724

725
    if s or (type(part) == "string" and #part > orig_size) then
18✔
726
      current_log[client] = nil
12✔
727
      sto_timeout()
12✔
728
      return s, err, part
12✔
729

730
    elseif not _isSocketTimeout[err] then
6✔
UNCOV
731
      current_log[client] = nil
×
732
      sto_timeout()
×
733
      return s, err, part
×
734

735
    elseif sto_timed_out() then
7✔
UNCOV
736
      current_log[client] = nil
×
737
      sto_timeout()
×
738
      return nil, sto_error(err), part
×
739
    end
740

741
    if err == "wantwrite" then
6✔
UNCOV
742
      current_log = _writing_log
×
743
      current_log[client] = gettime()
×
744
      sto_change_queue("write")
×
NEW
745
      local ok, werr = wait_on(_writing, client)
×
NEW
746
      if not ok then
×
NEW
747
        current_log[client] = nil
×
NEW
748
        sto_timeout()
×
NEW
749
        return nil, werr, part
×
750
      end
751
    else
752
      current_log = _reading_log
6✔
753
      current_log[client] = gettime()
6✔
754
      sto_change_queue("read")
6✔
755
      local ok, werr = wait_on(_reading, client)
6✔
756
      if not ok then
6✔
NEW
757
        current_log[client] = nil
×
NEW
758
        sto_timeout()
×
NEW
759
        return nil, werr, part
×
760
      end
761
    end
762
  until false
6✔
763
end
764
copas.receivePartial = copas.receivepartial  -- compat: receivePartial is deprecated
200✔
765

766
-- sends data to a client. The operation is buffered and
767
-- yields to the writing set on timeouts
768
-- Note: from and to parameters will be ignored by/for UDP sockets
769
function copas.send(client, data, from, to)
200✔
770
  local s, err
771
  from = from or 1
15,228✔
772
  local lastIndex = from - 1
15,228✔
773
  local current_log = _writing_log
15,228✔
774
  sto_timeout(client, "write")
15,228✔
775

776
  repeat
777
    s, err, lastIndex = client:send(data, lastIndex + 1, to)
15,406✔
778

779
    -- guarantees that high throughput doesn't take other threads to starvation
780
    if (math.random(100) > 90) then
15,406✔
781
      copas.pause()
1,524✔
782
    end
783

784
    if s then
15,406✔
785
      current_log[client] = nil
15,192✔
786
      sto_timeout()
15,192✔
787
      return s, err, lastIndex
15,192✔
788

789
    elseif not _isSocketTimeout[err] then
214✔
790
      current_log[client] = nil
30✔
791
      sto_timeout()
30✔
792
      return s, err, lastIndex
30✔
793

794
    elseif sto_timed_out() then
214✔
UNCOV
795
      current_log[client] = nil
×
796
      sto_timeout()
×
797
      return nil, sto_error(err), lastIndex
×
798
    end
799

800
    if err == "wantread" then
184✔
UNCOV
801
      current_log = _reading_log
×
802
      current_log[client] = gettime()
×
803
      sto_change_queue("read")
×
NEW
804
      local ok, werr = wait_on(_reading, client)
×
NEW
805
      if not ok then
×
NEW
806
        current_log[client] = nil
×
NEW
807
        sto_timeout()
×
NEW
808
        return nil, werr, lastIndex
×
809
      end
810
    else
811
      current_log = _writing_log
184✔
812
      current_log[client] = gettime()
184✔
813
      sto_change_queue("write")
184✔
814
      local ok, werr = wait_on(_writing, client)
184✔
815
      if not ok then
184✔
816
        current_log[client] = nil
6✔
817
        sto_timeout()
6✔
818
        return nil, werr, lastIndex
6✔
819
      end
820
    end
821
  until false
178✔
822
end
823

824
function copas.sendto(client, data, ip, port)
200✔
825
  -- deprecated; for backward compatibility only, since UDP doesn't block on sending
UNCOV
826
  return client:sendto(data, ip, port)
×
827
end
828

829
-- waits until connection is completed
830
function copas.connect(skt, host, port)
200✔
831
  skt:settimeout(0)
212✔
832
  local ret, err, tried_more_than_once
833
  sto_timeout(skt, "write", true)
210✔
834

835
  repeat
836
    ret, err = skt:connect(host, port)
422✔
837

838
    -- non-blocking connect on Windows results in error "Operation already
839
    -- in progress" to indicate that it is completing the request async. So essentially
840
    -- it is the same as "timeout"
841
    if ret or (err ~= "timeout" and err ~= "Operation already in progress") then
414✔
842
      _writing_log[skt] = nil
198✔
843
      sto_timeout()
198✔
844
      -- Once the async connect completes, Windows returns the error "already connected"
845
      -- to indicate it is done, so that error should be ignored. Except when it is the
846
      -- first call to connect, then it was already connected to something else and the
847
      -- error should be returned
848
      if (not ret) and (err == "already connected" and tried_more_than_once) then
198✔
UNCOV
849
        return 1
×
850
      end
851
      return ret, err
198✔
852

853
    elseif sto_timed_out() then
252✔
854
      _writing_log[skt] = nil
12✔
855
      sto_timeout()
12✔
856
      return nil, sto_error(err)
14✔
857
    end
858

859
    tried_more_than_once = tried_more_than_once or true
204✔
860
    _writing_log[skt] = gettime()
204✔
861
    local ok, werr = wait_on(_writing, skt)
204✔
862
    if not ok then
204✔
NEW
863
      _writing_log[skt] = nil
×
NEW
864
      sto_timeout()
×
NEW
865
      return nil, werr
×
866
    end
867
  until false
204✔
868
end
869

870

871
-- Wraps a tcp socket in an ssl socket and configures it. If the socket was
872
-- already wrapped, it does nothing and returns the socket.
873
-- @param wrap_params the parameters for the ssl-context
874
-- @return wrapped socket, or throws an error
875
local function ssl_wrap(skt, wrap_params)
876
  if isTCP(skt) == "ssl" then return skt end -- was already wrapped
224✔
877
  if not wrap_params then
108✔
UNCOV
878
    error("cannot wrap socket into a secure socket (using 'ssl.wrap()') without parameters/context")
×
879
  end
880

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

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

888
  local co = _autoclose_r[skt]
108✔
889
  if co then
108✔
890
    -- socket registered for autoclose, move registration to wrapped one
891
    _autoclose[co] = nskt
24✔
892
    _autoclose_r[skt] = nil
24✔
893
    _autoclose_r[nskt] = co
24✔
894
  end
895

896
  local sock_name = object_names[skt]
108✔
897
  if sock_name ~= tostring(skt) then
108✔
898
    -- socket had a custom name, so copy it over
899
    object_names[nskt] = sock_name
36✔
900
  end
901
  return nskt
108✔
902
end
903

904

905
-- For each luasec method we have a subtable, allows for future extension.
906
-- Required structure:
907
-- {
908
--   wrap = ... -- parameter to 'wrap()'; the ssl parameter table, or the context object
909
--   sni = {                  -- parameters to 'sni()'
910
--     names = string | table -- 1st parameter
911
--     strict = bool          -- 2nd parameter
912
--   }
913
-- }
914
local function normalize_sslt(sslt)
915
  local t = type(sslt)
354✔
916
  local r = setmetatable({}, {
708✔
917
    __index = function(self, key)
918
      -- a bug if this happens, here as a sanity check, just being careful since
919
      -- this is security stuff
UNCOV
920
      error("accessing unknown 'ssl_params' table key: "..tostring(key))
×
921
    end,
922
  })
923
  if t == "nil" then
354✔
924
    r.wrap = false
240✔
925
    r.sni = false
240✔
926

927
  elseif t == "table" then
114✔
928
    if sslt.mode or sslt.protocol then
114✔
929
      -- has the mandatory fields for the ssl-params table for handshake
930
      -- backward compatibility
931
      r.wrap = sslt
24✔
932
      r.sni = false
24✔
933
    else
934
      -- has the target definition, copy our known keys
935
      r.wrap = sslt.wrap or false -- 'or false' because we do not want nils
90✔
936
      r.sni = sslt.sni or false -- 'or false' because we do not want nils
90✔
937
    end
938

UNCOV
939
  elseif t == "userdata" then
×
940
    -- it's an ssl-context object for the handshake
941
    -- backward compatibility
UNCOV
942
    r.wrap = sslt
×
943
    r.sni = false
×
944

945
  else
UNCOV
946
    error("ssl parameters; did not expect type "..tostring(sslt))
×
947
  end
948

949
  return r
354✔
950
end
951

952

953
---
954
-- Peforms an (async) ssl handshake on a connected TCP client socket.
955
-- NOTE: if not ssl-wrapped already, then replace all previous socket references, with the returned new ssl wrapped socket
956
-- Throws error and does not return nil+error, as that might silently fail
957
-- in code like this;
958
--   copas.addserver(s1, function(skt)
959
--       skt = copas.wrap(skt, sparams)
960
--       skt:dohandshake()   --> without explicit error checking, this fails silently and
961
--       skt:send(body)      --> continues unencrypted
962
-- @param skt Regular LuaSocket CLIENT socket object
963
-- @param wrap_params Table with ssl parameters
964
-- @return wrapped ssl socket, or throws an error
965
function copas.dohandshake(skt, wrap_params)
200✔
966
  ssl = ssl or require("ssl")
108✔
967

968
  local nskt = ssl_wrap(skt, wrap_params)
108✔
969

970
  sto_timeout(nskt, "write", true)
108✔
971
  local queue
972

973
  repeat
974
    local success, err = nskt:dohandshake()
294✔
975

976
    if success then
294✔
977
      sto_timeout()
96✔
978
      return nskt
96✔
979

980
    elseif not _isSocketTimeout[err] then
198✔
981
      sto_timeout()
12✔
982
      error("TLS/SSL handshake failed: " .. tostring(err))
12✔
983

984
    elseif sto_timed_out() then
217✔
UNCOV
985
      sto_timeout()
×
986
      return nil, sto_error(err)
×
987

988
    elseif err == "wantwrite" then
186✔
UNCOV
989
      sto_change_queue("write")
×
990
      queue = _writing
×
991

992
    elseif err == "wantread" then
186✔
993
      sto_change_queue("read")
186✔
994
      queue = _reading
186✔
995

996
    else
UNCOV
997
      error("TLS/SSL handshake failed: " .. tostring(err))
×
998
    end
999

1000
    local ok, werr = wait_on(queue, nskt)
186✔
1001
    if not ok then
186✔
NEW
1002
      sto_timeout()
×
NEW
1003
      error("TLS/SSL handshake failed: " .. tostring(werr))
×
1004
    end
1005
  until false
186✔
1006
end
1007

1008
-- flushes a client write buffer (deprecated)
1009
function copas.flush()
200✔
1010
end
1011

1012
-- wraps a TCP socket to use Copas methods (send, receive, flush and settimeout)
1013
local _skt_mt_tcp = {
200✔
1014
      __tostring = function(self)
1015
        return tostring(self.socket).." (copas wrapped)"
18✔
1016
      end,
1017

1018
      __index = {
200✔
1019
        send = function (self, data, from, to)
1020
          return copas.send (self.socket, data, from, to)
15,222✔
1021
        end,
1022

1023
        receive = function (self, pattern, prefix)
1024
          if user_timeouts_receive[self.socket] == 0 then
2,132,970✔
1025
            return copas.receivepartial(self.socket, pattern, prefix)
12✔
1026
          end
1027
          return copas.receive(self.socket, pattern, prefix)
2,132,957✔
1028
        end,
1029

1030
        receivepartial = function (self, pattern, prefix)
UNCOV
1031
          return copas.receivepartial(self.socket, pattern, prefix)
×
1032
        end,
1033

1034
        flush = function (self)
UNCOV
1035
          return copas.flush(self.socket)
×
1036
        end,
1037

1038
        settimeout = function (self, time)
1039
          return copas.settimeout(self.socket, time)
204✔
1040
        end,
1041

1042
        settimeouts = function (self, connect, send, receive)
UNCOV
1043
          return copas.settimeouts(self.socket, connect, send, receive)
×
1044
        end,
1045

1046
        -- TODO: socket.connect is a shortcut, and must be provided with an alternative
1047
        -- if ssl parameters are available, it will also include a handshake
1048
        connect = function(self, ...)
1049
          local res, err = copas.connect(self.socket, ...)
210✔
1050
          if res then
210✔
1051
            if self.ssl_params.sni then self:sni() end
192✔
1052
            if self.ssl_params.wrap then res, err = self:dohandshake() end
205✔
1053
          end
1054
          return res, err
204✔
1055
        end,
1056

1057
        close = function(self, ...)
1058
          return copas.close(self.socket, ...)
222✔
1059
        end,
1060

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

1064
        -- TODO: is this DNS related? hence blocking?
1065
        getsockname = function(self, ...)
UNCOV
1066
          local ok, ip, port, family = pcall(self.socket.getsockname, self.socket, ...)
×
1067
          if ok then
×
1068
            return ip, port, family
×
1069
          else
UNCOV
1070
            return nil, "not implemented by LuaSec"
×
1071
          end
1072
        end,
1073

1074
        getstats = function(self, ...) return self.socket:getstats(...) end,
200✔
1075

1076
        setstats = function(self, ...) return self.socket:setstats(...) end,
200✔
1077

1078
        listen = function(self, ...) return self.socket:listen(...) end,
200✔
1079

1080
        accept = function(self, ...) return self.socket:accept(...) end,
200✔
1081

1082
        setoption = function(self, ...)
UNCOV
1083
          local ok, res, err = pcall(self.socket.setoption, self.socket, ...)
×
1084
          if ok then
×
1085
            return res, err
×
1086
          else
UNCOV
1087
            return nil, "not implemented by LuaSec"
×
1088
          end
1089
        end,
1090

1091
        getoption = function(self, ...)
UNCOV
1092
          local ok, val, err = pcall(self.socket.getoption, self.socket, ...)
×
1093
          if ok then
×
1094
            return val, err
×
1095
          else
UNCOV
1096
            return nil, "not implemented by LuaSec"
×
1097
          end
1098
        end,
1099

1100
        -- TODO: is this DNS related? hence blocking?
1101
        getpeername = function(self, ...)
UNCOV
1102
          local ok, ip, port, family = pcall(self.socket.getpeername, self.socket, ...)
×
1103
          if ok then
×
1104
            return ip, port, family
×
1105
          else
UNCOV
1106
            return nil, "not implemented by LuaSec"
×
1107
          end
1108
        end,
1109

1110
        shutdown = function(self, ...) return self.socket:shutdown(...) end,
200✔
1111

1112
        sni = function(self, names, strict)
1113
          local sslp = self.ssl_params
84✔
1114
          self.socket = ssl_wrap(self.socket, sslp.wrap)
98✔
1115
          if names == nil then
84✔
1116
            names = sslp.sni.names
72✔
1117
            strict = sslp.sni.strict
72✔
1118
          end
1119
          return self.socket:sni(names, strict)
84✔
1120
        end,
1121

1122
        dohandshake = function(self, wrap_params)
1123
          local nskt, err = copas.dohandshake(self.socket, wrap_params or self.ssl_params.wrap)
108✔
1124
          if not nskt then return nskt, err end
96✔
1125
          self.socket = nskt  -- replace internal socket with the newly wrapped ssl one
96✔
1126
          return self
96✔
1127
        end,
1128

1129
        getalpn = function(self, ...)
UNCOV
1130
          local ok, proto, err = pcall(self.socket.getalpn, self.socket, ...)
×
1131
          if ok then
×
1132
            return proto, err
×
1133
          else
UNCOV
1134
            return nil, "not a tls socket"
×
1135
          end
1136
        end,
1137

1138
        getsniname = function(self, ...)
UNCOV
1139
          local ok, name, err = pcall(self.socket.getsniname, self.socket, ...)
×
1140
          if ok then
×
1141
            return name, err
×
1142
          else
UNCOV
1143
            return nil, "not a tls socket"
×
1144
          end
1145
        end,
1146
      }
200✔
1147
}
1148

1149
-- wraps a UDP socket, copy of TCP one adapted for UDP.
1150
local _skt_mt_udp = {__index = { }}
200✔
1151
for k,v in pairs(_skt_mt_tcp) do _skt_mt_udp[k] = _skt_mt_udp[k] or v end
600✔
1152
for k,v in pairs(_skt_mt_tcp.__index) do _skt_mt_udp.__index[k] = v end
4,600✔
1153

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

1156
_skt_mt_udp.__index.sendto      = function(self, ...) return self.socket:sendto(...) end
224✔
1157

1158

1159
_skt_mt_udp.__index.receive =     function (self, size)
200✔
1160
                                    return copas.receive (self.socket, (size or UDP_DATAGRAM_MAX))
18✔
1161
                                  end
1162

1163
_skt_mt_udp.__index.receivefrom = function (self, size)
200✔
1164
                                    return copas.receivefrom (self.socket, (size or UDP_DATAGRAM_MAX))
24✔
1165
                                  end
1166

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

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

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

1175
_skt_mt_udp.__index.settimeouts = function (self, connect, send, receive)
200✔
UNCOV
1176
                                    return copas.settimeouts(self.socket, connect, send, receive)
×
1177
                                  end
1178

1179

1180

1181
---
1182
-- Wraps a LuaSocket socket object in an async Copas based socket object.
1183
-- @param skt The socket to wrap
1184
-- @sslt (optional) Table with ssl parameters, use an empty table to use ssl with defaults
1185
-- @return wrapped socket object
1186
function copas.wrap (skt, sslt)
200✔
1187
  if (getmetatable(skt) == _skt_mt_tcp) or (getmetatable(skt) == _skt_mt_udp) then
390✔
UNCOV
1188
    return skt -- already wrapped
×
1189
  end
1190

1191
  skt:settimeout(0)
392✔
1192

1193
  if isTCP(skt) then
455✔
1194
    return setmetatable ({socket = skt, ssl_params = normalize_sslt(sslt)}, _skt_mt_tcp)
413✔
1195
  else
1196
    return setmetatable ({socket = skt}, _skt_mt_udp)
36✔
1197
  end
1198
end
1199

1200
--- Wraps a handler in a function that deals with wrapping the socket and doing the
1201
-- optional ssl handshake.
1202
function copas.handler(handler, sslparams)
200✔
1203
  -- TODO: pass a timeout value to set, and use during handshake
1204
  return function (skt, ...)
1205
    skt = copas.wrap(skt, sslparams) -- this call will normalize the sslparams table
112✔
1206
    local sslp = skt.ssl_params
96✔
1207
    if sslp.sni then skt:sni(sslp.sni.names, sslp.sni.strict) end
96✔
1208
    if sslp.wrap then skt:dohandshake(sslp.wrap) end
96✔
1209
    return handler(skt, ...)
90✔
1210
  end
1211
end
1212

1213

1214
--------------------------------------------------
1215
-- Error handling
1216
--------------------------------------------------
1217

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

1220

1221
function copas.gettraceback(msg, co, skt)
200✔
1222
  local co_str = co == nil and "nil" or copas.getthreadname(co)
38✔
1223
  local skt_str = skt == nil and "nil" or copas.getsocketname(skt)
38✔
1224
  local msg_str = msg == nil and "" or tostring(msg)
38✔
1225
  if msg_str == "" then
38✔
UNCOV
1226
    msg_str = ("(coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str)
×
1227
  else
1228
    msg_str = ("%s (coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str)
38✔
1229
  end
1230

1231
  if type(co) == "thread" then
38✔
1232
    -- regular Copas coroutine
1233
    return debug.traceback(co, msg_str)
38✔
1234
  end
1235
  -- not a coroutine, but the main thread, this happens if a timeout callback
1236
  -- (see `copas.timeout` causes an error (those callbacks run on the main thread).
UNCOV
1237
  return debug.traceback(msg_str, 2)
×
1238
end
1239

1240

1241
local function _deferror(msg, co, skt)
1242
  print(copas.gettraceback(msg, co, skt))
29✔
1243
end
1244

1245

1246
function copas.seterrorhandler(err, default)
200✔
1247
  assert(err == nil or type(err) == "function", "Expected the handler to be a function, or nil")
60✔
1248
  if default then
60✔
1249
    assert(err ~= nil, "Expected the handler to be a function when setting the default")
42✔
1250
    _deferror = err
42✔
1251
  else
1252
    _errhandlers[coroutine_running()] = err
18✔
1253
  end
1254
end
1255
copas.setErrorHandler = copas.seterrorhandler  -- deprecated; old casing
200✔
1256

1257

1258
function copas.geterrorhandler(co)
200✔
1259
  co = co or coroutine_running()
12✔
1260
  return _errhandlers[co] or _deferror
12✔
1261
end
1262

1263

1264
-- if `bool` is truthy, then the original socket errors will be returned in case of timeouts;
1265
-- `timeout, wantread, wantwrite, Operation already in progress`. If falsy, it will always
1266
-- return `timeout`.
1267
function copas.useSocketTimeoutErrors(bool)
200✔
1268
  useSocketTimeoutErrors[coroutine_running()] = not not bool -- force to a boolean
6✔
1269
end
1270

1271
-------------------------------------------------------------------------------
1272
-- Thread handling
1273
-------------------------------------------------------------------------------
1274

1275
local function _doTick (co, skt, ...)
1276
  if not co then return end
247,102✔
1277

1278
  -- if a coroutine was canceled/removed, don't resume it
1279
  if _canceled[co] then
247,102✔
1280
    _canceled[co] = nil -- also clean up the registry
18✔
1281
    _threads[co] = nil
18✔
1282
    return
18✔
1283
  end
1284

1285
  -- res: the socket (being read/write on) or the time to sleep
1286
  -- new_q: either _writing, _reading, or _sleeping
1287
  -- local time_before = gettime()
1288
  local ok, res, new_q = coroutine_resume(co, skt, ...)
247,084✔
1289
  -- local duration = gettime() - time_before
1290
  -- if duration > 1 then
1291
  --   duration = math.floor(duration * 1000)
1292
  --   pcall(_errhandlers[co] or _deferror, "task ran for "..tostring(duration).." milliseconds.", co, skt)
1293
  -- end
1294

1295
  if new_q == _reading or new_q == _writing then
247,072✔
1296
    -- we're yielding to wait on a socket; the claim was already taken by
1297
    -- the coroutine itself before it yielded (see wait_on below), so by
1298
    -- construction this can't fail here
1299
    new_q:insert (res)
1,217✔
1300
    return
1,217✔
1301
  elseif new_q == _sleeping then
245,855✔
1302
    -- we're yielding to sleep
1303
    new_q:insert (res)
240,387✔
1304
    new_q:push (res, co)
240,387✔
1305
    return
240,387✔
1306
  end
1307

1308
  -- coroutine is terminating
1309

1310
  if ok and coroutine_status(co) ~= "dead" then
5,468✔
1311
    -- it called coroutine.yield from a non-Copas function which is unexpected
1312
    ok = false
6✔
1313
    res = "coroutine.yield was called without a resume first, user-code cannot yield to Copas"
6✔
1314
  end
1315

1316
  if not ok then
5,468✔
1317
    local k, e = pcall(_errhandlers[co] or _deferror, res, co, skt)
46✔
1318
    if not k then
46✔
UNCOV
1319
      print("Failed executing error handler: " .. tostring(e))
×
1320
    end
1321
  end
1322

1323
  local skt_to_close = _autoclose[co]
5,468✔
1324
  if skt_to_close then
5,468✔
1325
    skt_to_close:close()
132✔
1326
    _autoclose[co] = nil
132✔
1327
    _autoclose_r[skt_to_close] = nil
132✔
1328
  end
1329

1330
  _errhandlers[co] = nil
5,468✔
1331
end
1332

1333

1334
local _accept do
200✔
1335
  local client_counters = setmetatable({}, { __mode = "k" })
200✔
1336

1337
  -- accepts a connection on socket input
UNCOV
1338
  function _accept(server_skt, handler)
168✔
1339
    local client_skt = server_skt:accept()
138✔
1340
    if client_skt then
138✔
1341
      local count = (client_counters[server_skt] or 0) + 1
138✔
1342
      client_counters[server_skt] = count
138✔
1343
      object_names[client_skt] = object_names[server_skt] .. ":client_" .. count
154✔
1344

1345
      client_skt:settimeout(0)
138✔
1346
      copas.settimeouts(client_skt, user_timeouts_connect[server_skt],  -- copy server socket timeout settings
276✔
1347
        user_timeouts_send[server_skt], user_timeouts_receive[server_skt])
154✔
1348

1349
      local co = coroutine_create(handler)
138✔
1350
      object_names[co] = object_names[server_skt] .. ":handler_" .. count
138✔
1351

1352
      if copas.autoclose then
138✔
1353
        _autoclose[co] = client_skt
138✔
1354
        _autoclose_r[client_skt] = co
138✔
1355
      end
1356

1357
      _doTick(co, client_skt)
138✔
1358
    end
1359
  end
1360
end
1361

1362
-------------------------------------------------------------------------------
1363
-- Adds a server/handler pair to Copas dispatcher
1364
-------------------------------------------------------------------------------
1365

1366
do
1367
  local function addTCPserver(server, handler, timeout, name)
1368
    server:settimeout(0)
108✔
1369
    if name then
108✔
UNCOV
1370
      object_names[server] = name
×
1371
    end
1372
    _servers[server] = handler
108✔
1373
    _reading:insert(server)
108✔
1374
    if timeout then
108✔
1375
      copas.settimeout(server, timeout)
18✔
1376
    end
1377
  end
1378

1379
  local function addUDPserver(server, handler, timeout, name)
UNCOV
1380
    server:settimeout(0)
×
1381
    local co = coroutine_create(handler)
×
1382
    if name then
×
1383
      object_names[server] = name
×
1384
    end
UNCOV
1385
    object_names[co] = object_names[server]..":handler"
×
1386
    _reading:insert(server)
×
1387
    if timeout then
×
1388
      copas.settimeout(server, timeout)
×
1389
    end
UNCOV
1390
    _doTick(co, server)
×
1391
  end
1392

1393

1394
  function copas.addserver(server, handler, timeout, name)
200✔
1395
    if isTCP(server) then
126✔
1396
      addTCPserver(server, handler, timeout, name)
126✔
1397
    else
UNCOV
1398
      addUDPserver(server, handler, timeout, name)
×
1399
    end
1400
  end
1401
end
1402

1403

1404
function copas.removeserver(server, keep_open)
200✔
1405
  local skt = server
102✔
1406
  local mt = getmetatable(server)
102✔
1407
  if mt == _skt_mt_tcp or mt == _skt_mt_udp then
102✔
UNCOV
1408
    skt = server.socket
×
1409
  end
1410

1411
  _servers:remove(skt)
102✔
1412
  _reading:remove(skt)
102✔
1413

1414
  if keep_open then
102✔
1415
    return true
18✔
1416
  end
1417
  return server:close()
84✔
1418
end
1419

1420

1421

1422
-------------------------------------------------------------------------------
1423
-- Adds an new coroutine thread to Copas dispatcher
1424
-------------------------------------------------------------------------------
1425
function copas.addnamedthread(name, handler, ...)
200✔
1426
  if type(name) == "function" and type(handler) == "string" then
5,638✔
1427
    -- old call, flip args for compatibility
UNCOV
1428
    name, handler = handler, name
×
1429
  end
1430

1431
  -- create a coroutine that skips the first argument, which is always the socket
1432
  -- passed by the scheduler, but `nil` in case of a task/thread
1433
  local thread = coroutine_create(function(_, ...)
11,276✔
1434
    copas.pause()
5,638✔
1435
    return handler(...)
5,614✔
1436
  end)
1437
  if name then
5,638✔
1438
    object_names[thread] = name
532✔
1439
  end
1440

1441
  _threads[thread] = true -- register this thread so it can be removed
5,638✔
1442
  _doTick (thread, nil, ...)
5,638✔
1443
  return thread
5,638✔
1444
end
1445

1446

1447
function copas.addthread(handler, ...)
200✔
1448
  return copas.addnamedthread(nil, handler, ...)
5,040✔
1449
end
1450

1451

1452
function copas.removethread(thread)
200✔
1453
  -- if the specified coroutine is registered, add it to the canceled table so
1454
  -- that next time it tries to resume it exits.
1455
  _canceled[thread] = _threads[thread or 0]
66✔
1456
  _sleeping:cancel(thread)
66✔
1457
end
1458

1459

1460

1461
-------------------------------------------------------------------------------
1462
-- Sleep/pause management functions
1463
-------------------------------------------------------------------------------
1464

1465
-- yields the current coroutine and wakes it after 'sleeptime' seconds.
1466
-- If sleeptime < 0 then it sleeps until explicitly woken up using 'wakeup'
1467
-- TODO: deprecated, remove in next major
1468
function copas.sleep(sleeptime)
200✔
UNCOV
1469
  coroutine_yield((sleeptime or 0), _sleeping)
×
1470
end
1471

1472

1473
-- yields the current coroutine and wakes it after 'sleeptime' seconds.
1474
-- if sleeptime < 0 then it sleeps 0 seconds.
1475
function copas.pause(sleeptime)
200✔
1476
  local s = gettime()
237,009✔
1477
  if sleeptime and sleeptime > 0 then
237,009✔
1478
    coroutine_yield(sleeptime, _sleeping)
9,576✔
1479
  else
1480
    coroutine_yield(0, _sleeping)
228,758✔
1481
  end
1482
  return gettime() - s
236,773✔
1483
end
1484

1485

1486
-- yields the current coroutine until explicitly woken up using 'wakeup'
1487
function copas.pauseforever()
200✔
1488
  local s = gettime()
3,378✔
1489
  coroutine_yield(-1, _sleeping)
3,378✔
1490
  return gettime() - s
3,318✔
1491
end
1492

1493

1494
-- Wakes up a sleeping coroutine 'co'.
1495
-- @return true on success, or nil+"not sleeping" if 'co' wasn't sleeping
1496
-- (eg. it was already woken up, finished, or canceled through `copas.removethread`).
1497
function copas.wakeup(co)
200✔
1498
  return _sleeping:wakeup(co)
3,384✔
1499
end
1500

1501

1502
-- Checks whether a coroutine 'co' is currently sleeping (paused or
1503
-- paused-forever), without waking it up. Useful to detect a coroutine
1504
-- that was canceled (eg. through `copas.removethread`) while it was
1505
-- expected to still be waiting.
1506
function copas.issleeping(co)
200✔
1507
  return _sleeping:issleeping(co)
606✔
1508
end
1509

1510

1511

1512
-------------------------------------------------------------------------------
1513
-- Timeout management
1514
-------------------------------------------------------------------------------
1515

1516
do
1517
  local timeout_register = setmetatable({}, { __mode = "k" })
200✔
1518
  local timerwheel = require("timerwheel").new({
401✔
1519
      now = gettime,
200✔
1520
      precision = TIMEOUT_PRECISION,
200✔
1521
      ringsize = math.floor(60*60*24/TIMEOUT_PRECISION),  -- ring size 1 day
200✔
1522
      err_handler = function(err)
1523
        return _deferror(err, core_timer_thread)
16✔
1524
      end,
1525
    })
1526

1527
  core_timer_thread = copas.addnamedthread("copas_core_timer", function()
400✔
1528
    while true do
1529
      copas.pause(TIMEOUT_PRECISION)
7,123✔
1530
      timerwheel:step()
8,069✔
1531
    end
1532
  end)
1533

1534
  -- get the number of timeouts running
1535
  function copas.gettimeouts()
200✔
1536
    return timerwheel:count()
3,559✔
1537
  end
1538

1539
  --- Sets the timeout for the current coroutine.
1540
  -- @param delay delay (seconds), use 0 (or math.huge) to cancel the timerout
1541
  -- @param callback function with signature: `function(coroutine)` where coroutine is the routine that timed-out
1542
  -- @return true
1543
  function copas.timeout(delay, callback)
200✔
1544
    local co = coroutine_running()
4,302,290✔
1545
    local existing_timer = timeout_register[co]
4,302,290✔
1546

1547
    if existing_timer then
4,302,290✔
1548
      timerwheel:cancel(existing_timer)
4,524✔
1549
    end
1550

1551
    if delay > 0 and delay ~= math.huge then
4,302,290✔
1552
      timeout_register[co] = timerwheel:set(delay, callback, co)
7,110✔
1553
    elseif delay == 0 or delay == math.huge then
4,296,178✔
1554
      timeout_register[co] = nil
4,296,178✔
1555
    else
UNCOV
1556
      error("timout value must be greater than or equal to 0, got: "..tostring(delay))
×
1557
    end
1558

1559
    return true
4,302,290✔
1560
  end
1561

1562
end
1563

1564

1565
-------------------------------------------------------------------------------
1566
-- main tasks: manage readable and writable socket sets
1567
-------------------------------------------------------------------------------
1568
-- a task is an object with a required method `step()` that deals with a
1569
-- single step for that task.
1570

1571
local _tasks = {} do
200✔
1572
  function _tasks:add(tsk)
200✔
1573
    _tasks[#_tasks + 1] = tsk
800✔
1574
  end
1575
end
1576

1577

1578
-- a task to check ready to read events
1579
local _readable_task = {} do
200✔
1580

1581
  _readable_task._events = {}
200✔
1582

1583
  local function tick(skt)
1584
    local handler = _servers[skt]
901✔
1585
    if handler then
901✔
1586
      _accept(skt, handler)
161✔
1587
    else
1588
      _reading:remove(skt)
763✔
1589
      _doTick(_reading:release(skt), skt)
894✔
1590
    end
1591
  end
1592

1593
  function _readable_task:step()
200✔
1594
    for _, skt in ipairs(self._events) do
231,678✔
1595
      tick(skt)
901✔
1596
    end
1597
  end
1598

1599
  _tasks:add(_readable_task)
232✔
1600
end
1601

1602

1603
-- a task to check ready to write events
1604
local _writable_task = {} do
200✔
1605

1606
  _writable_task._events = {}
200✔
1607

1608
  local function tick(skt)
1609
    _writing:remove(skt)
370✔
1610
    _doTick(_writing:release(skt), skt)
431✔
1611
  end
1612

1613
  function _writable_task:step()
200✔
1614
    for _, skt in ipairs(self._events) do
231,142✔
1615
      tick(skt)
370✔
1616
    end
1617
  end
1618

1619
  _tasks:add(_writable_task)
232✔
1620
end
1621

1622

1623

1624
-- sleeping threads task
1625
local _sleeping_task = {} do
200✔
1626

1627
  function _sleeping_task:step()
200✔
1628
    local now = gettime()
230,772✔
1629

1630
    local co = _sleeping:pop(now)
230,772✔
1631
    while co do
238,800✔
1632
      -- we're pushing them to _resumable, since that list will be replaced before
1633
      -- executing. This prevents tasks running twice in a row with pause(0) for example.
1634
      -- So here we won't execute, but at _resumable step which is next
1635
      _resumable:push(co)
8,028✔
1636
      co = _sleeping:pop(now)
9,352✔
1637
    end
1638
  end
1639

1640
  _tasks:add(_sleeping_task)
200✔
1641
end
1642

1643

1644

1645
-- resumable threads task
1646
local _resumable_task = {} do
200✔
1647

1648
  function _resumable_task:step()
200✔
1649
    -- replace the resume list before iterating, so items placed in there
1650
    -- will indeed end up in the next copas step, not in this one, and not
1651
    -- create a loop
1652
    local resumelist = _resumable:clear_resumelist()
230,772✔
1653

1654
    for _, co in ipairs(resumelist) do
470,958✔
1655
      _doTick(co)
240,193✔
1656
    end
1657
  end
1658

1659
  _tasks:add(_resumable_task)
200✔
1660
end
1661

1662

1663
-------------------------------------------------------------------------------
1664
-- Checks for reads and writes on sockets
1665
-------------------------------------------------------------------------------
1666
local _select_plain do
200✔
1667

1668
  local last_cleansing = 0
200✔
1669
  local duration = function(t2, t1) return t2-t1 end
230,887✔
1670

1671
  if not socket then
200✔
1672
    -- socket module unavailable, switch to luasystem sleep
1673
    _select_plain = block_sleep
6✔
1674
  else
1675
    -- use socket.select to handle socket-io
1676
    _select_plain = function(timeout)
1677
      local err
1678
      local now = gettime()
230,687✔
1679

1680
      -- remove any closed sockets to prevent select from hanging on them
1681
      if _closed[1] then
230,687✔
1682
        for i, skt in ipairs(_closed) do
452✔
1683
          _closed[i] = { _reading:remove(skt), _writing:remove(skt) }
304✔
1684
        end
1685
      end
1686

1687
      _readable_task._events, _writable_task._events, err = socket.select(_reading, _writing, timeout)
230,687✔
1688
      local r_events, w_events = _readable_task._events, _writable_task._events
230,687✔
1689

1690
      -- inject closed sockets in readable/writeable task so they can error out properly
1691
      if _closed[1] then
230,687✔
1692
        for i, skts in ipairs(_closed) do
452✔
1693
          _closed[i] = nil
228✔
1694
          r_events[#r_events+1] = skts[1]
228✔
1695
          w_events[#w_events+1] = skts[2]
228✔
1696
        end
1697
      end
1698

1699
      if duration(now, last_cleansing) > WATCH_DOG_TIMEOUT then
290,640✔
1700
        last_cleansing = now
188✔
1701

1702
        -- Check all sockets selected for reading, and check how long they have been waiting
1703
        -- for data already, without select returning them as readable
1704
        for skt,time in pairs(_reading_log) do
188✔
UNCOV
1705
          if not r_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
×
1706
            -- This one timedout while waiting to become readable, so move
1707
            -- it in the readable list and try and read anyway, despite not
1708
            -- having been returned by select
UNCOV
1709
            _reading_log[skt] = nil
×
1710
            r_events[#r_events + 1] = skt
×
1711
            r_events[skt] = #r_events
×
1712
          end
1713
        end
1714

1715
        -- Do the same for writing
1716
        for skt,time in pairs(_writing_log) do
188✔
UNCOV
1717
          if not w_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
×
1718
            _writing_log[skt] = nil
×
1719
            w_events[#w_events + 1] = skt
×
1720
            w_events[skt] = #w_events
×
1721
          end
1722
        end
1723
      end
1724

1725
      if err == "timeout" and #r_events + #w_events > 0 then
230,687✔
1726
        return nil
6✔
1727
      else
1728
        return err
230,681✔
1729
      end
1730
    end
1731
  end
1732
end
1733

1734

1735

1736
-------------------------------------------------------------------------------
1737
-- Dispatcher loop step.
1738
-- Listen to client requests and handles them
1739
-- Returns false if no socket-data was handled, or true if there was data
1740
-- handled (or nil + error message)
1741
-------------------------------------------------------------------------------
1742

1743
local copas_stats
1744
local min_ever, max_ever
1745

1746
local _select = _select_plain
200✔
1747

1748
-- instrumented version of _select() to collect stats
1749
local _select_instrumented = function(timeout)
UNCOV
1750
  if copas_stats then
×
1751
    local step_duration = gettime() - copas_stats.step_start
×
1752
    copas_stats.duration_max = math.max(copas_stats.duration_max, step_duration)
×
1753
    copas_stats.duration_min = math.min(copas_stats.duration_min, step_duration)
×
1754
    copas_stats.duration_tot = copas_stats.duration_tot + step_duration
×
1755
    copas_stats.steps = copas_stats.steps + 1
×
1756
  else
UNCOV
1757
    copas_stats = {
×
1758
      duration_max = -1,
1759
      duration_min = 999999,
1760
      duration_tot = 0,
1761
      steps = 0,
1762
    }
1763
  end
1764

UNCOV
1765
  local err = _select_plain(timeout)
×
1766

UNCOV
1767
  local now = gettime()
×
1768
  copas_stats.time_start = copas_stats.time_start or now
×
1769
  copas_stats.step_start = now
×
1770

UNCOV
1771
  return err
×
1772
end
1773

1774

1775
function copas.step(timeout)
200✔
1776
  -- Need to wake up the select call in time for the next sleeping event
1777
  if not _resumable:done() then
290,745✔
1778
    timeout = 0
223,806✔
1779
  else
1780
    timeout = math.min(_sleeping:getnext(), timeout or math.huge)
8,117✔
1781
  end
1782

1783
  local err = _select(timeout)
230,777✔
1784

1785
  for _, tsk in ipairs(_tasks) do
1,153,863✔
1786
    tsk:step()
923,098✔
1787
  end
1788

1789
  if err then
230,765✔
1790
    if err == "timeout" then
229,656✔
1791
      if timeout + 0.01 > TIMEOUT_PRECISION and math.random(100) > 90 then
229,566✔
1792
        -- we were idle, so occasionally do a GC sweep to ensure lingering
1793
        -- sockets are closed, and we don't accidentally block the loop from
1794
        -- exiting
1795
        collectgarbage()
484✔
1796
      end
1797
      return false
229,566✔
1798
    end
1799
    return nil, err
90✔
1800
  end
1801

1802
  return true
1,109✔
1803
end
1804

1805

1806
-------------------------------------------------------------------------------
1807
-- Check whether there is something to do.
1808
-- returns false if there are no sockets for read/write nor tasks scheduled
1809
-- (which means Copas is in an empty spin)
1810
-------------------------------------------------------------------------------
1811
function copas.finished()
200✔
1812
  return #_reading == 0 and #_writing == 0 and _resumable:done() and _sleeping:done(copas.gettimeouts())
232,680✔
1813
end
1814

1815

1816
local resetexit do
200✔
1817
  local exit_semaphore, exiting
1818

UNCOV
1819
  function resetexit()
168✔
1820
    exit_semaphore = copas.semaphore.new(1, 0, math.huge)
471✔
1821
    exiting = false
380✔
1822
  end
1823

1824
  -- Signals tasks to exit. But only if they check for it. By calling `copas.exiting`
1825
  -- they can check if they should exit. Or by calling `copas.waitforexit` they can
1826
  -- wait until the exit signal is given.
1827
  function copas.exit()
200✔
1828
    if exiting then return end
368✔
1829
    exiting = true
368✔
1830
    exit_semaphore:destroy()
368✔
1831
  end
1832

1833
  -- returns whether Copas is in the process of exiting. Exit can be started by
1834
  -- calling `copas.exit()`.
1835
  function copas.exiting()
232✔
1836
    return exiting
730✔
1837
  end
1838

1839
  -- Pauses the current coroutine until Copas is exiting. To be used as an exit
1840
  -- signal for tasks that need to clean up before exiting.
1841
  function copas.waitforexit()
232✔
1842
    exit_semaphore:take(1)
12✔
1843
  end
1844
end
1845

1846

1847
--- Forcibly cancels all pending work and signals exit.
1848
-- Intended for test teardown only. Abandons all registered threads and sockets
1849
-- without giving them a chance to clean up. After this call copas.finished()
1850
-- will return true and the loop will exit. The module is left in a clean state
1851
-- ready for the next copas.loop() call.
1852
function copas.cancelall()
200✔
1853
  -- 1. clear resumable queue
UNCOV
1854
  _resumable:clear_resumelist()
×
1855

1856
  -- 2. drain sleeping heap
UNCOV
1857
  _sleeping:cancelall()
×
1858

1859
  -- 3. close and drain reading sockets
UNCOV
1860
  while _reading[1] do
×
1861
    copas.close(_reading[1])
×
1862
    _reading:remove(_reading[1])
×
1863
  end
1864

1865
  -- 4. close and drain writing sockets
UNCOV
1866
  while _writing[1] do
×
1867
    copas.close(_writing[1])
×
1868
    _writing:remove(_writing[1])
×
1869
  end
1870

1871
  -- 5. remove all servers
UNCOV
1872
  while _servers[1] do
×
1873
    copas.removeserver(_servers[1])
×
1874
  end
1875

1876
  -- 6. clear non-weak ancillary tables
UNCOV
1877
  _closed = {}
×
1878
  _reading_log = {}
×
1879
  _writing_log = {}
×
1880

1881
  -- 7. signal exit
UNCOV
1882
  copas.exit()
×
1883
end
1884

1885

1886
local _getstats do
200✔
1887
  local _getstats_instrumented, _getstats_plain
1888

1889

UNCOV
1890
  function _getstats_plain(enable)
168✔
1891
    -- this function gets hit if turned off, so turn on if true
UNCOV
1892
    if enable == true then
×
1893
      _select = _select_instrumented
×
1894
      _getstats = _getstats_instrumented
×
1895
      -- reset stats
UNCOV
1896
      min_ever = nil
×
1897
      max_ever = nil
×
1898
      copas_stats = nil
×
1899
    end
UNCOV
1900
    return {}
×
1901
  end
1902

1903

1904
  -- convert from seconds to millisecs, with microsec precision
1905
  local function useconds(t)
UNCOV
1906
    return math.floor((t * 1000000) + 0.5) / 1000
×
1907
  end
1908
  -- convert from seconds to seconds, with millisec precision
1909
  local function mseconds(t)
UNCOV
1910
    return math.floor((t * 1000) + 0.5) / 1000
×
1911
  end
1912

1913

UNCOV
1914
  function _getstats_instrumented(enable)
168✔
UNCOV
1915
    if enable == false then
×
1916
      _select = _select_plain
×
1917
      _getstats = _getstats_plain
×
1918
      -- instrumentation disabled, so switch to the plain implementation
UNCOV
1919
      return _getstats(enable)
×
1920
    end
UNCOV
1921
    if (not copas_stats) or (copas_stats.step == 0) then
×
1922
      return {}
×
1923
    end
UNCOV
1924
    local stats = copas_stats
×
1925
    copas_stats = nil
×
1926
    min_ever = math.min(min_ever or 9999999, stats.duration_min)
×
1927
    max_ever = math.max(max_ever or 0, stats.duration_max)
×
1928
    stats.duration_min_ever = min_ever
×
1929
    stats.duration_max_ever = max_ever
×
1930
    stats.duration_avg = stats.duration_tot / stats.steps
×
1931
    stats.step_start = nil
×
1932
    stats.time_end = gettime()
×
1933
    stats.time_tot = stats.time_end - stats.time_start
×
1934
    stats.time_avg = stats.time_tot / stats.steps
×
1935

UNCOV
1936
    stats.duration_avg = useconds(stats.duration_avg)
×
1937
    stats.duration_max = useconds(stats.duration_max)
×
1938
    stats.duration_max_ever = useconds(stats.duration_max_ever)
×
1939
    stats.duration_min = useconds(stats.duration_min)
×
1940
    stats.duration_min_ever = useconds(stats.duration_min_ever)
×
1941
    stats.duration_tot = useconds(stats.duration_tot)
×
1942
    stats.time_avg = useconds(stats.time_avg)
×
1943
    stats.time_start = mseconds(stats.time_start)
×
1944
    stats.time_end = mseconds(stats.time_end)
×
1945
    stats.time_tot = mseconds(stats.time_tot)
×
1946
    return stats
×
1947
  end
1948

1949
  _getstats = _getstats_plain
200✔
1950
end
1951

1952

1953
function copas.status(enable_stats)
200✔
UNCOV
1954
  local res = _getstats(enable_stats)
×
1955
  res.running = not not copas.running
×
1956
  res.timeout = copas.gettimeouts()
×
1957
  res.timer, res.inactive = _sleeping:status()
×
1958
  res.read = #_reading
×
1959
  res.write = #_writing
×
1960
  res.active = _resumable:count()
×
1961
  return res
×
1962
end
1963

1964

1965
-------------------------------------------------------------------------------
1966
-- Dispatcher endless loop.
1967
-- Listen to client requests and handles them forever
1968
-------------------------------------------------------------------------------
1969
function copas.loop(initializer, timeout)
232✔
1970
  if type(initializer) == "function" then
380✔
1971
    copas.addnamedthread("copas_initializer", initializer)
204✔
1972
  else
1973
    timeout = initializer or timeout
204✔
1974
  end
1975

1976
  resetexit()
380✔
1977
  copas.running = true
380✔
1978
  while true do
1979
    copas.step(timeout)
230,777✔
1980
    if copas.finished() then
290,731✔
1981
      if copas.exiting() then
849✔
1982
        break
182✔
1983
      end
1984
      copas.exit()
362✔
1985
    end
1986
  end
1987
  copas.running = false
368✔
1988
end
1989

1990

1991
-------------------------------------------------------------------------------
1992
-- Naming sockets and coroutines.
1993
-------------------------------------------------------------------------------
1994
do
1995
  local function realsocket(skt)
1996
    local mt = getmetatable(skt)
90✔
1997
    if mt == _skt_mt_tcp or mt == _skt_mt_udp then
90✔
1998
      return skt.socket
90✔
1999
    else
UNCOV
2000
      return skt
×
2001
    end
2002
  end
2003

2004

2005
  function copas.setsocketname(name, skt)
232✔
2006
    assert(type(name) == "string", "expected arg #1 to be a string")
90✔
2007
    skt = assert(realsocket(skt), "expected arg #2 to be a socket")
105✔
2008
    object_names[skt] = name
90✔
2009
  end
2010

2011

2012
  function copas.getsocketname(skt)
232✔
UNCOV
2013
    skt = assert(realsocket(skt), "expected arg #1 to be a socket")
×
2014
    return object_names[skt]
×
2015
  end
2016
end
2017

2018

2019
function copas.setthreadname(name, coro)
232✔
2020
  assert(type(name) == "string", "expected arg #1 to be a string")
60✔
2021
  coro = coro or coroutine_running()
60✔
2022
  assert(type(coro) == "thread", "expected arg #2 to be a coroutine or nil")
60✔
2023
  object_names[coro] = name
60✔
2024
end
2025

2026

2027
function copas.getthreadname(coro)
232✔
2028
  coro = coro or coroutine_running()
38✔
2029
  assert(type(coro) == "thread", "expected arg #1 to be a coroutine or nil")
38✔
2030
  return object_names[coro]
40✔
2031
end
2032

2033
-------------------------------------------------------------------------------
2034
-- Debug functionality.
2035
-------------------------------------------------------------------------------
2036
do
2037
  copas.debug = {}
200✔
2038

2039
  local log_core    -- if truthy, the core-timer will also be logged
2040
  local debug_log   -- function used as logger
2041

2042

2043
  local debug_yield = function(skt, queue)
2044
    local name = object_names[coroutine_running()]
5,379✔
2045

2046
    if log_core or name ~= "copas_core_timer" then
5,379✔
2047
      if queue == _sleeping then
5,357✔
2048
        debug_log("yielding '", name, "' to SLEEP for ", skt," seconds")
5,290✔
2049

2050
      elseif queue == _writing then
67✔
2051
        debug_log("yielding '", name, "' to WRITE on '", object_names[skt], "'")
14✔
2052

2053
      elseif queue == _reading then
55✔
2054
        debug_log("yielding '", name, "' to READ on '", object_names[skt], "'")
57✔
2055

2056
      else
UNCOV
2057
        debug_log("thread '", name, "' yielding to unexpected queue; ", tostring(queue), " (", type(queue), ")", debug.traceback())
×
2058
      end
2059
    end
2060

2061
    return coroutine.yield(skt, queue)
5,379✔
2062
  end
2063

2064

2065
  local debug_resume = function(coro, skt, ...)
2066
    local name = object_names[coro]
5,391✔
2067

2068
    if skt then
5,391✔
2069
      debug_log("resuming '", name, "' for socket '", object_names[skt], "'")
67✔
2070
    else
2071
      if log_core or name ~= "copas_core_timer" then
5,324✔
2072
        debug_log("resuming '", name, "'")
5,302✔
2073
      end
2074
    end
2075
    return coroutine.resume(coro, skt, ...)
5,391✔
2076
  end
2077

2078

2079
  local debug_create = function(f)
2080
    local f_wrapped = function(...)
2081
      local results = pack(f(...))
14✔
2082
      debug_log("exiting '", object_names[coroutine_running()], "'")
12✔
2083
      return unpack(results)
12✔
2084
    end
2085

2086
    return coroutine.create(f_wrapped)
12✔
2087
  end
2088

2089

2090
  debug_log = fnil
200✔
2091

2092

2093
  -- enables debug output for all coroutine operations.
2094
  function copas.debug.start(logger, core)
400✔
2095
    log_core = core
6✔
2096
    debug_log = logger or print
6✔
2097
    coroutine_yield = debug_yield
6✔
2098
    coroutine_resume = debug_resume
6✔
2099
    coroutine_create = debug_create
6✔
2100
  end
2101

2102

2103
  -- disables debug output for coroutine operations.
2104
  function copas.debug.stop()
400✔
UNCOV
2105
    debug_log = fnil
×
2106
    coroutine_yield = coroutine.yield
×
2107
    coroutine_resume = coroutine.resume
×
2108
    coroutine_create = coroutine.create
×
2109
  end
2110

2111
  do
2112
    local call_id = 0
200✔
2113

2114
    -- Description table of socket functions for debug output.
2115
    -- each socket function name has TWO entries;
2116
    -- 'name_in' and 'name_out', each being an array of names/descriptions of respectively
2117
    -- input parameters and return values.
2118
    -- If either table has a 'callback' key, then that is a function that will be called
2119
    -- with the parameters/return-values for further inspection.
2120
    local args = {
200✔
2121
      settimeout_in = {
200✔
2122
        "socket ",
168✔
2123
        "seconds",
168✔
2124
        "mode   ",
2125
      },
200✔
2126
      settimeout_out = {
200✔
2127
        "success",
168✔
2128
        "error  ",
2129
      },
200✔
2130
      connect_in = {
200✔
2131
        "socket ",
168✔
2132
        "address",
168✔
2133
        "port   ",
2134
      },
200✔
2135
      connect_out = {
200✔
2136
        "success",
168✔
2137
        "error  ",
2138
      },
200✔
2139
      getfd_in = {
200✔
2140
        "socket ",
2141
        -- callback = function(...)
2142
        --   print(debug.traceback("called from:", 4))
2143
        -- end,
2144
      },
200✔
2145
      getfd_out = {
200✔
2146
        "fd",
2147
      },
200✔
2148
      send_in = {
200✔
2149
        "socket   ",
168✔
2150
        "data     ",
168✔
2151
        "idx-start",
168✔
2152
        "idx-end  ",
2153
      },
200✔
2154
      send_out = {
200✔
2155
        "last-idx-send    ",
168✔
2156
        "error            ",
168✔
2157
        "err-last-idx-send",
2158
      },
200✔
2159
      receive_in = {
200✔
2160
        "socket ",
168✔
2161
        "pattern",
168✔
2162
        "prefix ",
2163
      },
200✔
2164
      receive_out = {
200✔
2165
        "received    ",
168✔
2166
        "error       ",
168✔
2167
        "partial data",
2168
      },
200✔
2169
      dirty_in = {
200✔
2170
        "socket",
2171
        -- callback = function(...)
2172
        --   print(debug.traceback("called from:", 4))
2173
        -- end,
2174
      },
200✔
2175
      dirty_out = {
200✔
2176
        "data in read-buffer",
2177
      },
200✔
2178
      close_in = {
200✔
2179
        "socket",
2180
        -- callback = function(...)
2181
        --   print(debug.traceback("called from:", 4))
2182
        -- end,
2183
      },
200✔
2184
      close_out = {
200✔
2185
        "success",
168✔
2186
        "error",
2187
      },
200✔
2188
    }
2189
    local function print_call(func, msg, ...)
2190
      print(msg)
572✔
2191
      local arg = pack(...)
572✔
2192
      local desc = args[func] or {}
572✔
2193
      for i = 1, math.max(arg.n, #desc) do
1,288✔
2194
        local value = arg[i]
716✔
2195
        if type(value) == "string" then
716✔
2196
          local xvalue = value:sub(1,30)
36✔
2197
          if xvalue ~= value then
36✔
UNCOV
2198
            xvalue = xvalue .."(...truncated)"
×
2199
          end
2200
          print("\t"..(desc[i] or i)..": '"..tostring(xvalue).."' ("..type(value).." #"..#value..")")
36✔
2201
        else
2202
          print("\t"..(desc[i] or i)..": '"..tostring(value).."' ("..type(value)..")")
680✔
2203
        end
2204
      end
2205
      if desc.callback then
572✔
UNCOV
2206
        desc.callback(...)
×
2207
      end
2208
    end
2209

2210
    local debug_mt = {
200✔
2211
      __index = function(self, key)
2212
        local value = self.__original_socket[key]
286✔
2213
        if type(value) ~= "function" then
286✔
UNCOV
2214
          return value
×
2215
        end
2216
        return function(self2, ...)
2217
            local my_id = call_id + 1
286✔
2218
            call_id = my_id
286✔
2219
            local results
2220

2221
            if self2 ~= self then
286✔
2222
              -- there is no self
UNCOV
2223
              print_call(tostring(key).."_in", my_id .. "-calling '"..tostring(key) .. "' with; ", self, ...)
×
2224
              results = pack(value(self, ...))
×
2225
            else
2226
              print_call(tostring(key).."_in", my_id .. "-calling '" .. tostring(key) .. "' with; ", self.__original_socket, ...)
286✔
2227
              results = pack(value(self.__original_socket, ...))
312✔
2228
            end
2229
            print_call(tostring(key).."_out", my_id .. "-results '"..tostring(key) .. "' returned; ", unpack(results))
312✔
2230
            return unpack(results)
286✔
2231
          end
2232
      end,
2233
      __tostring = function(self)
2234
        return tostring(self.__original_socket)
48✔
2235
      end
2236
    }
2237

2238

2239
    -- wraps a socket (copas or luasocket) in a debug version printing all calls
2240
    -- and their parameters/return values. Extremely noisy!
2241
    -- returns the wrapped socket.
2242
    -- NOTE: only for plain sockets, will not support TLS
2243
    function copas.debug.socket(original_skt)
400✔
2244
      if (getmetatable(original_skt) == _skt_mt_tcp) or (getmetatable(original_skt) == _skt_mt_udp) then
12✔
2245
        -- already wrapped as Copas socket, so recurse with the original luasocket one
UNCOV
2246
        original_skt.socket = copas.debug.socket(original_skt.socket)
×
2247
        return original_skt
×
2248
      end
2249

2250
      local proxy = setmetatable({
24✔
2251
        __original_socket = original_skt
12✔
2252
      }, debug_mt)
12✔
2253

2254
      return proxy
12✔
2255
    end
2256
  end
2257
end
2258

2259

2260
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