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

lunarmodules / copas / 8124323163

02 Mar 2024 05:23PM UTC coverage: 84.373% (+0.5%) from 83.848%
8124323163

push

github

Tieske
fix(test): wrap socket as a copas one

1285 of 1523 relevant lines covered (84.37%)

54005.79 hits per line

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

79.78
/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-2023 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
146✔
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
146✔
20
  error("you must require copas before require'ing copas.http")
×
21
end
22

23

24
local socket = require "socket"
146✔
25
local binaryheap = require "binaryheap"
146✔
26
local gettime = socket.gettime
146✔
27
local ssl -- only loaded upon demand
28

29
local WATCH_DOG_TIMEOUT = 120
146✔
30
local UDP_DATAGRAM_MAX = socket._DATAGRAMSIZE or 8192
146✔
31
local TIMEOUT_PRECISION = 0.1  -- 100ms
146✔
32
local fnil = function() end
188,752✔
33

34

35
local coroutine_create = coroutine.create
146✔
36
local coroutine_running = coroutine.running
146✔
37
local coroutine_yield = coroutine.yield
146✔
38
local coroutine_resume = coroutine.resume
146✔
39
local coroutine_status = coroutine.status
146✔
40

41

42
-- nil-safe versions for pack/unpack
43
local _unpack = unpack or table.unpack
146✔
44
local unpack = function(t, i, j) return _unpack(t, i or 1, j or t.n or #t) end
376✔
45
local pack = function(...) return { n = select("#", ...), ...} end
486✔
46

47

48
local pcall = pcall
146✔
49
if _VERSION=="Lua 5.1" and not jit then     -- obsolete: only for Lua 5.1 compatibility
146✔
50
  pcall = require("coxpcall").pcall
28✔
51
  coroutine_running = require("coxpcall").running
28✔
52
end
53

54

55
do
56
  -- Redefines LuaSocket functions with coroutine safe versions (pure Lua)
57
  -- (this allows the use of socket.http from within copas)
58
  local err_mt = {
146✔
59
    __tostring = function (self)
60
      return "Copas 'try' error intermediate table: '"..tostring(self[1].."'")
×
61
    end,
62
  }
63

64
  local function statusHandler(status, ...)
65
    if status then return ... end
70✔
66
    local err = (...)
25✔
67
    if type(err) == "table" and getmetatable(err) == err_mt then
25✔
68
      return nil, err[1]
25✔
69
    else
70
      error(err)
×
71
    end
72
  end
73

74
  function socket.protect(func)
146✔
75
    return function (...)
76
            return statusHandler(pcall(func, ...))
84✔
77
          end
78
  end
79

80
  function socket.newtry(finalizer)
146✔
81
    return function (...)
82
            local status = (...)
1,095✔
83
            if not status then
1,095✔
84
              pcall(finalizer or fnil, select(2, ...))
25✔
85
              error(setmetatable({ (select(2, ...)) }, err_mt), 0)
25✔
86
            end
87
            return ...
1,070✔
88
          end
89
  end
90

91
  socket.try = socket.newtry()
174✔
92
end
93

94

95
-- Setup the Copas meta table to auto-load submodules and define a default method
96
local copas do
146✔
97
  local submodules = { "ftp", "http", "lock", "queue", "semaphore", "smtp", "timer" }
146✔
98
  for i, key in ipairs(submodules) do
1,168✔
99
    submodules[key] = true
1,022✔
100
    submodules[i] = nil
1,022✔
101
  end
102

103
  copas = setmetatable({},{
292✔
104
    __index = function(self, key)
105
      if submodules[key] then
55✔
106
        self[key] = require("copas."..key)
55✔
107
        submodules[key] = nil
55✔
108
        return rawget(self, key)
55✔
109
      end
110
    end,
111
    __call = function(self, ...)
112
      return self.loop(...)
5✔
113
    end,
114
  })
146✔
115
end
116

117

118
-- Meta information is public even if beginning with an "_"
119
copas._COPYRIGHT   = "Copyright (C) 2005-2013 Kepler Project, 2015-2023 Thijs Schreijer"
146✔
120
copas._DESCRIPTION = "Coroutine Oriented Portable Asynchronous Services"
146✔
121
copas._VERSION     = "Copas 4.7.0"
146✔
122

123
-- Close the socket associated with the current connection after the handler finishes
124
copas.autoclose = true
146✔
125

126
-- indicator for the loop running
127
copas.running = false
146✔
128

129

130
-------------------------------------------------------------------------------
131
-- Object names, to track names of thread/coroutines and sockets
132
-------------------------------------------------------------------------------
133
local object_names = setmetatable({}, {
292✔
134
  __mode = "k",
118✔
135
  __index = function(self, key)
136
    local name = tostring(key)
145✔
137
    if key ~= nil then
145✔
138
      rawset(self, key, name)
145✔
139
    end
140
    return name
145✔
141
  end
142
})
143

144
-------------------------------------------------------------------------------
145
-- Simple set implementation
146
-- adds a FIFO queue for each socket in the set
147
-------------------------------------------------------------------------------
148

149
local function newsocketset()
150
  local set = {}
438✔
151

152
  do  -- set implementation
153
    local reverse = {}
438✔
154

155
    -- Adds a socket to the set, does nothing if it exists
156
    -- @return skt if added, or nil if it existed
157
    function set:insert(skt)
438✔
158
      if not reverse[skt] then
892✔
159
        self[#self + 1] = skt
892✔
160
        reverse[skt] = #self
892✔
161
        return skt
892✔
162
      end
163
    end
164

165
    -- Removes socket from the set, does nothing if not found
166
    -- @return skt if removed, or nil if it wasn't in the set
167
    function set:remove(skt)
438✔
168
      local index = reverse[skt]
1,289✔
169
      if index then
1,289✔
170
        reverse[skt] = nil
887✔
171
        local top = self[#self]
887✔
172
        self[#self] = nil
887✔
173
        if top ~= skt then
887✔
174
          reverse[top] = index
81✔
175
          self[index] = top
81✔
176
        end
177
        return skt
887✔
178
      end
179
    end
180

181
  end
182

183
  do  -- queues implementation
184
    local fifo_queues = setmetatable({},{
876✔
185
      __mode = "k",                 -- auto collect queue if socket is gone
354✔
186
      __index = function(self, skt) -- auto create fifo queue if not found
187
        local newfifo = {}
330✔
188
        self[skt] = newfifo
330✔
189
        return newfifo
330✔
190
      end,
191
    })
192

193
    -- pushes an item in the fifo queue for the socket.
194
    function set:push(skt, itm)
438✔
195
      local queue = fifo_queues[skt]
817✔
196
      queue[#queue + 1] = itm
817✔
197
    end
198

199
    -- pops an item from the fifo queue for the socket
200
    function set:pop(skt)
438✔
201
      local queue = fifo_queues[skt]
757✔
202
      return table.remove(queue, 1)
757✔
203
    end
204

205
  end
206

207
  return set
438✔
208
end
209

210

211

212
-- Threads immediately resumable
213
local _resumable = {} do
146✔
214
  local resumelist = {}
146✔
215

216
  function _resumable:push(co)
146✔
217
    resumelist[#resumelist + 1] = co
188,493✔
218
  end
219

220
  function _resumable:clear_resumelist()
146✔
221
    local lst = resumelist
180,798✔
222
    resumelist = {}
180,798✔
223
    return lst
180,798✔
224
  end
225

226
  function _resumable:done()
146✔
227
    return resumelist[1] == nil
182,996✔
228
  end
229

230
  function _resumable:count()
146✔
231
    return #resumelist + #_resumable
×
232
  end
233

234
end
235

236

237

238
-- Similar to the socket set above, but tailored for the use of
239
-- sleeping threads
240
local _sleeping = {} do
146✔
241

242
  local heap = binaryheap.minUnique()
146✔
243
  local lethargy = setmetatable({}, { __mode = "k" }) -- list of coroutines sleeping without a wakeup time
146✔
244

245

246
  -- Required base implementation
247
  -----------------------------------------
248
  _sleeping.insert = fnil
146✔
249
  _sleeping.remove = fnil
146✔
250

251
  -- push a new timer on the heap
252
  function _sleeping:push(sleeptime, co)
146✔
253
    if sleeptime < 0 then
188,601✔
254
      lethargy[co] = true
2,575✔
255
    elseif sleeptime == 0 then
186,026✔
256
      _resumable:push(co)
240,898✔
257
    else
258
      heap:insert(gettime() + sleeptime, co)
5,437✔
259
    end
260
  end
261

262
  -- find the thread that should wake up to the time, if any
263
  function _sleeping:pop(time)
146✔
264
    if time < (heap:peekValue() or math.huge) then
247,474✔
265
      return
180,798✔
266
    end
267
    return heap:pop()
5,274✔
268
  end
269

270
  -- additional methods for time management
271
  -----------------------------------------
272
  function _sleeping:getnext()  -- returns delay until next sleep expires, or nil if there is none
146✔
273
    local t = heap:peekValue()
4,291✔
274
    if t then
4,291✔
275
      -- never report less than 0, because select() might block
276
      return math.max(t - gettime(), 0)
4,291✔
277
    end
278
  end
279

280
  function _sleeping:wakeup(co)
146✔
281
    if lethargy[co] then
2,580✔
282
      lethargy[co] = nil
2,560✔
283
      _resumable:push(co)
2,560✔
284
      return
2,560✔
285
    end
286
    if heap:remove(co) then
24✔
287
      _resumable:push(co)
10✔
288
    end
289
  end
290

291
  function _sleeping:cancel(co)
146✔
292
    lethargy[co] = nil
30✔
293
    heap:remove(co)
30✔
294
  end
295

296
  -- @param tos number of timeouts running
297
  function _sleeping:done(tos)
146✔
298
    -- return true if we have nothing more to do
299
    -- the timeout task doesn't qualify as work (fallbacks only),
300
    -- the lethargy also doesn't qualify as work ('dead' tasks),
301
    -- but the combination of a timeout + a lethargy can be work
302
    return heap:size() == 1       -- 1 means only the timeout-timer task is running
1,938✔
303
           and not (tos > 0 and next(lethargy))
1,616✔
304
  end
305

306
  -- gets number of threads in binaryheap and lethargy
307
  function _sleeping:status()
146✔
308
    local c = 0
×
309
    for _ in pairs(lethargy) do c = c + 1 end
×
310

311
    return heap:size(), c
×
312
  end
313

314
end   -- _sleeping
315

316

317

318
-------------------------------------------------------------------------------
319
-- Tracking coroutines and sockets
320
-------------------------------------------------------------------------------
321

322
local _servers = newsocketset() -- servers being handled
146✔
323
local _threads = setmetatable({}, {__mode = "k"})  -- registered threads added with addthread()
146✔
324
local _canceled = setmetatable({}, {__mode = "k"}) -- threads that are canceled and pending removal
146✔
325
local _autoclose = setmetatable({}, {__mode = "kv"}) -- sockets (value) to close when a thread (key) exits
146✔
326
local _autoclose_r = setmetatable({}, {__mode = "kv"}) -- reverse: sockets (key) to close when a thread (value) exits
146✔
327

328

329
-- for each socket we log the last read and last write times to enable the
330
-- watchdog to follow up if it takes too long.
331
-- tables contain the time, indexed by the socket
332
local _reading_log = {}
146✔
333
local _writing_log = {}
146✔
334

335
local _closed = {} -- track sockets that have been closed (list/array)
146✔
336

337
local _reading = newsocketset() -- sockets currently being read
146✔
338
local _writing = newsocketset() -- sockets currently being written
146✔
339
local _isSocketTimeout = { -- set of errors indicating a socket-timeout
146✔
340
  ["timeout"] = true,      -- default LuaSocket timeout
118✔
341
  ["wantread"] = true,     -- LuaSec specific timeout
118✔
342
  ["wantwrite"] = true,    -- LuaSec specific timeout
118✔
343
}
344

345
-------------------------------------------------------------------------------
346
-- Coroutine based socket timeouts.
347
-------------------------------------------------------------------------------
348
local user_timeouts_connect
349
local user_timeouts_send
350
local user_timeouts_receive
351
do
352
  local timeout_mt = {
146✔
353
    __mode = "k",
118✔
354
    __index = function(self, skt)
355
      -- if there is no timeout found, we insert one automatically, to block forever
356
      self[skt] = math.huge
345✔
357
      return self[skt]
345✔
358
    end,
359
  }
360

361
  user_timeouts_connect = setmetatable({}, timeout_mt)
146✔
362
  user_timeouts_send = setmetatable({}, timeout_mt)
146✔
363
  user_timeouts_receive = setmetatable({}, timeout_mt)
146✔
364
end
365

366
local useSocketTimeoutErrors = setmetatable({},{ __mode = "k" })
146✔
367

368

369
-- sto = socket-time-out
370
local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
146✔
371

372
  local socket_register = setmetatable({}, { __mode = "k" })    -- socket by coroutine
146✔
373
  local operation_register = setmetatable({}, { __mode = "k" }) -- operation "read"/"write" by coroutine
146✔
374
  local timeout_flags = setmetatable({}, { __mode = "k" })      -- true if timedout, by coroutine
146✔
375

376

377
  local function socket_callback(co)
378
    local skt = socket_register[co]
60✔
379
    local queue = operation_register[co]
60✔
380

381
    -- flag the timeout and resume the coroutine
382
    timeout_flags[co] = true
60✔
383
    _resumable:push(co)
60✔
384

385
    -- clear the socket from the current queue
386
    if queue == "read" then
60✔
387
      _reading:remove(skt)
60✔
388
    elseif queue == "write" then
10✔
389
      _writing:remove(skt)
12✔
390
    else
391
      error("bad queue name; expected 'read'/'write', got: "..tostring(queue))
×
392
    end
393
  end
394

395

396
  -- Sets a socket timeout.
397
  -- Calling it as `sto_timeout()` will cancel the timeout.
398
  -- @param queue (string) the queue the socket is currently in, must be either "read" or "write"
399
  -- @param skt (socket) the socket on which to operate
400
  -- @param use_connect_to (bool) timeout to use is determined based on queue (read/write) or if this
401
  -- is truthy, it is the connect timeout.
402
  -- @return true
403
  function sto_timeout(skt, queue, use_connect_to)
118✔
404
    local co = coroutine_running()
3,421,176✔
405
    socket_register[co] = skt
3,421,176✔
406
    operation_register[co] = queue
3,421,176✔
407
    timeout_flags[co] = nil
3,421,176✔
408
    if skt then
3,421,176✔
409
      local to = (use_connect_to and user_timeouts_connect[skt]) or
1,710,629✔
410
                 (queue == "read" and user_timeouts_receive[skt]) or
1,710,365✔
411
                 user_timeouts_send[skt]
12,295✔
412
      copas.timeout(to, socket_callback)
2,291,941✔
413
    else
414
      copas.timeout(0)
1,710,558✔
415
    end
416
    return true
3,421,176✔
417
  end
418

419

420
  -- Changes the timeout to a different queue (read/write).
421
  -- Only usefull with ssl-handshakes and "wantread", "wantwrite" errors, when
422
  -- the queue has to be changed, so the timeout handler knows where to find the socket.
423
  -- @param queue (string) the new queue the socket is in, must be either "read" or "write"
424
  -- @return true
425
  function sto_change_queue(queue)
118✔
426
    operation_register[coroutine_running()] = queue
697✔
427
    return true
697✔
428
  end
429

430

431
  -- Responds with `true` if the operation timed-out.
432
  function sto_timed_out()
118✔
433
    return timeout_flags[coroutine_running()]
877✔
434
  end
435

436

437
  -- Returns the proper timeout error
438
  function sto_error(err)
118✔
439
    return useSocketTimeoutErrors[coroutine_running()] and err or "timeout"
60✔
440
  end
441
end
442

443

444

445
-------------------------------------------------------------------------------
446
-- Coroutine based socket I/O functions.
447
-------------------------------------------------------------------------------
448

449
-- Returns "tcp"" for plain TCP and "ssl" for ssl-wrapped sockets, so truthy
450
-- for tcp based, and falsy for udp based.
451
local isTCP do
146✔
452
  local lookup = {
146✔
453
    tcp = "tcp",
118✔
454
    SSL = "ssl",
118✔
455
  }
456

457
  function isTCP(socket)
118✔
458
    return lookup[tostring(socket):sub(1,3)]
618✔
459
  end
460
end
461

462
function copas.close(skt, ...)
146✔
463
  _closed[#_closed+1] = skt
175✔
464
  return skt:close(...)
175✔
465
end
466

467

468

469
-- nil or negative is indefinitly
470
function copas.settimeout(skt, timeout)
146✔
471
  timeout = timeout or -1
170✔
472
  if type(timeout) ~= "number" then
170✔
473
    return nil, "timeout must be 'nil' or a number"
15✔
474
  end
475

476
  return copas.settimeouts(skt, timeout, timeout, timeout)
155✔
477
end
478

479
-- negative is indefinitly, nil means do not change
480
function copas.settimeouts(skt, connect, send, read)
146✔
481

482
  if connect ~= nil and type(connect) ~= "number" then
340✔
483
    return nil, "connect timeout must be 'nil' or a number"
×
484
  end
485
  if connect then
340✔
486
    if connect < 0 then
340✔
487
      connect = nil
×
488
    end
489
    user_timeouts_connect[skt] = connect
340✔
490
  end
491

492

493
  if send ~= nil and type(send) ~= "number" then
340✔
494
    return nil, "send timeout must be 'nil' or a number"
×
495
  end
496
  if send then
340✔
497
    if send < 0 then
340✔
498
      send = nil
×
499
    end
500
    user_timeouts_send[skt] = send
340✔
501
  end
502

503

504
  if read ~= nil and type(read) ~= "number" then
340✔
505
    return nil, "read timeout must be 'nil' or a number"
×
506
  end
507
  if read then
340✔
508
    if read < 0 then
340✔
509
      read = nil
×
510
    end
511
    user_timeouts_receive[skt] = read
340✔
512
  end
513

514

515
  return true
340✔
516
end
517

518
-- reads a pattern from a client and yields to the reading set on timeouts
519
-- UDP: a UDP socket expects a second argument to be a number, so it MUST
520
-- be provided as the 'pattern' below defaults to a string. Will throw a
521
-- 'bad argument' error if omitted.
522
function copas.receive(client, pattern, part)
146✔
523
  local s, err
524
  pattern = pattern or "*l"
1,698,038✔
525
  local current_log = _reading_log
1,698,038✔
526
  sto_timeout(client, "read")
1,698,038✔
527

528
  repeat
529
    s, err, part = client:receive(pattern, part)
1,698,465✔
530

531
    -- guarantees that high throughput doesn't take other threads to starvation
532
    if (math.random(100) > 90) then
1,698,465✔
533
      copas.pause()
169,891✔
534
    end
535

536
    if s then
1,698,465✔
537
      current_log[client] = nil
1,697,953✔
538
      sto_timeout()
1,697,953✔
539
      return s, err, part
1,697,953✔
540

541
    elseif not _isSocketTimeout[err] then
512✔
542
      current_log[client] = nil
40✔
543
      sto_timeout()
40✔
544
      return s, err, part
40✔
545

546
    elseif sto_timed_out() then
564✔
547
      current_log[client] = nil
45✔
548
      return nil, sto_error(err), part
54✔
549
    end
550

551
    if err == "wantwrite" then -- wantwrite may be returned during SSL renegotiations
427✔
552
      current_log = _writing_log
×
553
      current_log[client] = gettime()
×
554
      sto_change_queue("write")
×
555
      coroutine_yield(client, _writing)
×
556
    else
557
      current_log = _reading_log
427✔
558
      current_log[client] = gettime()
427✔
559
      sto_change_queue("read")
427✔
560
      coroutine_yield(client, _reading)
427✔
561
    end
562
  until false
427✔
563
end
564

565
-- receives data from a client over UDP. Not available for TCP.
566
-- (this is a copy of receive() method, adapted for receivefrom() use)
567
function copas.receivefrom(client, size)
146✔
568
  local s, err, port
569
  size = size or UDP_DATAGRAM_MAX
20✔
570
  sto_timeout(client, "read")
20✔
571

572
  repeat
573
    s, err, port = client:receivefrom(size) -- upon success err holds ip address
40✔
574

575
    -- garantees that high throughput doesn't take other threads to starvation
576
    if (math.random(100) > 90) then
40✔
577
      copas.pause()
2✔
578
    end
579

580
    if s then
40✔
581
      _reading_log[client] = nil
15✔
582
      sto_timeout()
15✔
583
      return s, err, port
15✔
584

585
    elseif err ~= "timeout" then
25✔
586
      _reading_log[client] = nil
×
587
      sto_timeout()
×
588
      return s, err, port
×
589

590
    elseif sto_timed_out() then
30✔
591
      _reading_log[client] = nil
5✔
592
      return nil, sto_error(err), port
6✔
593
    end
594

595
    _reading_log[client] = gettime()
20✔
596
    coroutine_yield(client, _reading)
20✔
597
  until false
20✔
598
end
599

600
-- same as above but with special treatment when reading chunks,
601
-- unblocks on any data received.
602
function copas.receivepartial(client, pattern, part)
146✔
603
  local s, err
604
  pattern = pattern or "*l"
10✔
605
  local orig_size = #(part or "")
10✔
606
  local current_log = _reading_log
10✔
607
  sto_timeout(client, "read")
10✔
608

609
  repeat
610
    s, err, part = client:receive(pattern, part)
10✔
611

612
    -- guarantees that high throughput doesn't take other threads to starvation
613
    if (math.random(100) > 90) then
10✔
614
      copas.pause()
×
615
    end
616

617
    if s or (type(part) == "string" and #part > orig_size) then
10✔
618
      current_log[client] = nil
10✔
619
      sto_timeout()
10✔
620
      return s, err, part
10✔
621

622
    elseif not _isSocketTimeout[err] then
×
623
      current_log[client] = nil
×
624
      sto_timeout()
×
625
      return s, err, part
×
626

627
    elseif sto_timed_out() then
×
628
      current_log[client] = nil
×
629
      return nil, sto_error(err), part
×
630
    end
631

632
    if err == "wantwrite" then
×
633
      current_log = _writing_log
×
634
      current_log[client] = gettime()
×
635
      sto_change_queue("write")
×
636
      coroutine_yield(client, _writing)
×
637
    else
638
      current_log = _reading_log
×
639
      current_log[client] = gettime()
×
640
      sto_change_queue("read")
×
641
      coroutine_yield(client, _reading)
×
642
    end
643
  until false
×
644
end
645
copas.receivePartial = copas.receivepartial  -- compat: receivePartial is deprecated
146✔
646

647
-- sends data to a client. The operation is buffered and
648
-- yields to the writing set on timeouts
649
-- Note: from and to parameters will be ignored by/for UDP sockets
650
function copas.send(client, data, from, to)
146✔
651
  local s, err
652
  from = from or 1
12,295✔
653
  local lastIndex = from - 1
12,295✔
654
  local current_log = _writing_log
12,295✔
655
  sto_timeout(client, "write")
12,295✔
656

657
  repeat
658
    s, err, lastIndex = client:send(data, lastIndex + 1, to)
12,440✔
659

660
    -- guarantees that high throughput doesn't take other threads to starvation
661
    if (math.random(100) > 90) then
12,440✔
662
      copas.pause()
1,196✔
663
    end
664

665
    if s then
12,440✔
666
      current_log[client] = nil
12,275✔
667
      sto_timeout()
12,275✔
668
      return s, err, lastIndex
12,275✔
669

670
    elseif not _isSocketTimeout[err] then
165✔
671
      current_log[client] = nil
20✔
672
      sto_timeout()
20✔
673
      return s, err, lastIndex
20✔
674

675
    elseif sto_timed_out() then
174✔
676
      current_log[client] = nil
×
677
      return nil, sto_error(err), lastIndex
×
678
    end
679

680
    if err == "wantread" then
145✔
681
      current_log = _reading_log
×
682
      current_log[client] = gettime()
×
683
      sto_change_queue("read")
×
684
      coroutine_yield(client, _reading)
×
685
    else
686
      current_log = _writing_log
145✔
687
      current_log[client] = gettime()
145✔
688
      sto_change_queue("write")
145✔
689
      coroutine_yield(client, _writing)
145✔
690
    end
691
  until false
145✔
692
end
693

694
function copas.sendto(client, data, ip, port)
146✔
695
  -- deprecated; for backward compatibility only, since UDP doesn't block on sending
696
  return client:sendto(data, ip, port)
×
697
end
698

699
-- waits until connection is completed
700
function copas.connect(skt, host, port)
146✔
701
  skt:settimeout(0)
171✔
702
  local ret, err, tried_more_than_once
703
  sto_timeout(skt, "write", true)
170✔
704

705
  repeat
706
    ret, err = skt:connect(host, port)
274✔
707

708
    -- non-blocking connect on Windows results in error "Operation already
709
    -- in progress" to indicate that it is completing the request async. So essentially
710
    -- it is the same as "timeout"
711
    if ret or (err ~= "timeout" and err ~= "Operation already in progress") then
270✔
712
      _writing_log[skt] = nil
160✔
713
      sto_timeout()
160✔
714
      -- Once the async connect completes, Windows returns the error "already connected"
715
      -- to indicate it is done, so that error should be ignored. Except when it is the
716
      -- first call to connect, then it was already connected to something else and the
717
      -- error should be returned
718
      if (not ret) and (err == "already connected" and tried_more_than_once) then
160✔
719
        return 1
×
720
      end
721
      return ret, err
160✔
722

723
    elseif sto_timed_out() then
132✔
724
      _writing_log[skt] = nil
10✔
725
      return nil, sto_error(err)
12✔
726
    end
727

728
    tried_more_than_once = tried_more_than_once or true
100✔
729
    _writing_log[skt] = gettime()
100✔
730
    coroutine_yield(skt, _writing)
100✔
731
  until false
100✔
732
end
733

734

735
-- Wraps a tcp socket in an ssl socket and configures it. If the socket was
736
-- already wrapped, it does nothing and returns the socket.
737
-- @param wrap_params the parameters for the ssl-context
738
-- @return wrapped socket, or throws an error
739
local function ssl_wrap(skt, wrap_params)
740
  if isTCP(skt) == "ssl" then return skt end -- was already wrapped
180✔
741
  if not wrap_params then
85✔
742
    error("cannot wrap socket into a secure socket (using 'ssl.wrap()') without parameters/context")
×
743
  end
744

745
  ssl = ssl or require("ssl")
85✔
746
  local nskt = assert(ssl.wrap(skt, wrap_params)) -- assert, because we do not want to silently ignore this one!!
102✔
747

748
  nskt:settimeout(0)  -- non-blocking on the ssl-socket
85✔
749
  copas.settimeouts(nskt, user_timeouts_connect[skt],
170✔
750
    user_timeouts_send[skt], user_timeouts_receive[skt]) -- copy copas user-timeout to newly wrapped one
89✔
751

752
  local co = _autoclose_r[skt]
85✔
753
  if co then
85✔
754
    -- socket registered for autoclose, move registration to wrapped one
755
    _autoclose[co] = nskt
20✔
756
    _autoclose_r[skt] = nil
20✔
757
    _autoclose_r[nskt] = co
20✔
758
  end
759

760
  local sock_name = object_names[skt]
85✔
761
  if sock_name ~= tostring(skt) then
85✔
762
    -- socket had a custom name, so copy it over
763
    object_names[nskt] = sock_name
30✔
764
  end
765
  return nskt
85✔
766
end
767

768

769
-- For each luasec method we have a subtable, allows for future extension.
770
-- Required structure:
771
-- {
772
--   wrap = ... -- parameter to 'wrap()'; the ssl parameter table, or the context object
773
--   sni = {                  -- parameters to 'sni()'
774
--     names = string | table -- 1st parameter
775
--     strict = bool          -- 2nd parameter
776
--   }
777
-- }
778
local function normalize_sslt(sslt)
779
  local t = type(sslt)
270✔
780
  local r = setmetatable({}, {
540✔
781
    __index = function(self, key)
782
      -- a bug if this happens, here as a sanity check, just being careful since
783
      -- this is security stuff
784
      error("accessing unknown 'ssl_params' table key: "..tostring(key))
×
785
    end,
786
  })
787
  if t == "nil" then
270✔
788
    r.wrap = false
185✔
789
    r.sni = false
185✔
790

791
  elseif t == "table" then
85✔
792
    if sslt.mode or sslt.protocol then
85✔
793
      -- has the mandatory fields for the ssl-params table for handshake
794
      -- backward compatibility
795
      r.wrap = sslt
20✔
796
      r.sni = false
20✔
797
    else
798
      -- has the target definition, copy our known keys
799
      r.wrap = sslt.wrap or false -- 'or false' because we do not want nils
65✔
800
      r.sni = sslt.sni or false -- 'or false' because we do not want nils
65✔
801
    end
802

803
  elseif t == "userdata" then
×
804
    -- it's an ssl-context object for the handshake
805
    -- backward compatibility
806
    r.wrap = sslt
×
807
    r.sni = false
×
808

809
  else
810
    error("ssl parameters; did not expect type "..tostring(sslt))
×
811
  end
812

813
  return r
270✔
814
end
815

816

817
---
818
-- Peforms an (async) ssl handshake on a connected TCP client socket.
819
-- NOTE: if not ssl-wrapped already, then replace all previous socket references, with the returned new ssl wrapped socket
820
-- Throws error and does not return nil+error, as that might silently fail
821
-- in code like this;
822
--   copas.addserver(s1, function(skt)
823
--       skt = copas.wrap(skt, sparams)
824
--       skt:dohandshake()   --> without explicit error checking, this fails silently and
825
--       skt:send(body)      --> continues unencrypted
826
-- @param skt Regular LuaSocket CLIENT socket object
827
-- @param wrap_params Table with ssl parameters
828
-- @return wrapped ssl socket, or throws an error
829
function copas.dohandshake(skt, wrap_params)
146✔
830
  ssl = ssl or require("ssl")
85✔
831

832
  local nskt = ssl_wrap(skt, wrap_params)
85✔
833

834
  sto_timeout(nskt, "write", true)
85✔
835
  local queue
836

837
  repeat
838
    local success, err = nskt:dohandshake()
210✔
839

840
    if success then
210✔
841
      sto_timeout()
75✔
842
      return nskt
75✔
843

844
    elseif not _isSocketTimeout[err] then
135✔
845
      sto_timeout()
10✔
846
      error("TLS/SSL handshake failed: " .. tostring(err))
10✔
847

848
    elseif sto_timed_out() then
150✔
849
      return nil, sto_error(err)
×
850

851
    elseif err == "wantwrite" then
125✔
852
      sto_change_queue("write")
×
853
      queue = _writing
×
854

855
    elseif err == "wantread" then
125✔
856
      sto_change_queue("read")
125✔
857
      queue = _reading
125✔
858

859
    else
860
      error("TLS/SSL handshake failed: " .. tostring(err))
×
861
    end
862

863
    coroutine_yield(nskt, queue)
125✔
864
  until false
125✔
865
end
866

867
-- flushes a client write buffer (deprecated)
868
function copas.flush()
146✔
869
end
870

871
-- wraps a TCP socket to use Copas methods (send, receive, flush and settimeout)
872
local _skt_mt_tcp = {
146✔
873
      __tostring = function(self)
874
        return tostring(self.socket).." (copas wrapped)"
15✔
875
      end,
876

877
      __index = {
146✔
878
        send = function (self, data, from, to)
879
          return copas.send (self.socket, data, from, to)
12,290✔
880
        end,
881

882
        receive = function (self, pattern, prefix)
883
          if user_timeouts_receive[self.socket] == 0 then
1,698,034✔
884
            return copas.receivepartial(self.socket, pattern, prefix)
10✔
885
          end
886
          return copas.receive(self.socket, pattern, prefix)
1,698,023✔
887
        end,
888

889
        receivepartial = function (self, pattern, prefix)
890
          return copas.receivepartial(self.socket, pattern, prefix)
×
891
        end,
892

893
        flush = function (self)
894
          return copas.flush(self.socket)
×
895
        end,
896

897
        settimeout = function (self, time)
898
          return copas.settimeout(self.socket, time)
155✔
899
        end,
900

901
        settimeouts = function (self, connect, send, receive)
902
          return copas.settimeouts(self.socket, connect, send, receive)
×
903
        end,
904

905
        -- TODO: socket.connect is a shortcut, and must be provided with an alternative
906
        -- if ssl parameters are available, it will also include a handshake
907
        connect = function(self, ...)
908
          local res, err = copas.connect(self.socket, ...)
170✔
909
          if res then
170✔
910
            if self.ssl_params.sni then self:sni() end
155✔
911
            if self.ssl_params.wrap then res, err = self:dohandshake() end
167✔
912
          end
913
          return res, err
165✔
914
        end,
915

916
        close = function(self, ...)
917
          return copas.close(self.socket, ...)
175✔
918
        end,
919

920
        -- TODO: socket.bind is a shortcut, and must be provided with an alternative
921
        bind = function(self, ...) return self.socket:bind(...) end,
146✔
922

923
        -- TODO: is this DNS related? hence blocking?
924
        getsockname = function(self, ...)
925
          local ok, ip, port, family = pcall(self.socket.getsockname, self.socket, ...)
×
926
          if ok then
×
927
            return ip, port, family
×
928
          else
929
            return nil, "not implemented by LuaSec"
×
930
          end
931
        end,
932

933
        getstats = function(self, ...) return self.socket:getstats(...) end,
146✔
934

935
        setstats = function(self, ...) return self.socket:setstats(...) end,
146✔
936

937
        listen = function(self, ...) return self.socket:listen(...) end,
146✔
938

939
        accept = function(self, ...) return self.socket:accept(...) end,
146✔
940

941
        setoption = function(self, ...)
942
          local ok, res, err = pcall(self.socket.setoption, self.socket, ...)
×
943
          if ok then
×
944
            return res, err
×
945
          else
946
            return nil, "not implemented by LuaSec"
×
947
          end
948
        end,
949

950
        getoption = function(self, ...)
951
          local ok, val, err = pcall(self.socket.getoption, self.socket, ...)
×
952
          if ok then
×
953
            return val, err
×
954
          else
955
            return nil, "not implemented by LuaSec"
×
956
          end
957
        end,
958

959
        -- TODO: is this DNS related? hence blocking?
960
        getpeername = function(self, ...)
961
          local ok, ip, port, family = pcall(self.socket.getpeername, self.socket, ...)
×
962
          if ok then
×
963
            return ip, port, family
×
964
          else
965
            return nil, "not implemented by LuaSec"
×
966
          end
967
        end,
968

969
        shutdown = function(self, ...) return self.socket:shutdown(...) end,
146✔
970

971
        sni = function(self, names, strict)
972
          local sslp = self.ssl_params
65✔
973
          self.socket = ssl_wrap(self.socket, sslp.wrap)
78✔
974
          if names == nil then
65✔
975
            names = sslp.sni.names
55✔
976
            strict = sslp.sni.strict
55✔
977
          end
978
          return self.socket:sni(names, strict)
65✔
979
        end,
980

981
        dohandshake = function(self, wrap_params)
982
          local nskt, err = copas.dohandshake(self.socket, wrap_params or self.ssl_params.wrap)
85✔
983
          if not nskt then return nskt, err end
75✔
984
          self.socket = nskt  -- replace internal socket with the newly wrapped ssl one
75✔
985
          return self
75✔
986
        end,
987

988
        getalpn = function(self, ...)
989
          local ok, proto, err = pcall(self.socket.getalpn, self.socket, ...)
×
990
          if ok then
×
991
            return proto, err
×
992
          else
993
            return nil, "not a tls socket"
×
994
          end
995
        end,
996

997
        getsniname = function(self, ...)
998
          local ok, name, err = pcall(self.socket.getsniname, self.socket, ...)
×
999
          if ok then
×
1000
            return name, err
×
1001
          else
1002
            return nil, "not a tls socket"
×
1003
          end
1004
        end,
1005
      }
146✔
1006
}
1007

1008
-- wraps a UDP socket, copy of TCP one adapted for UDP.
1009
local _skt_mt_udp = {__index = { }}
146✔
1010
for k,v in pairs(_skt_mt_tcp) do _skt_mt_udp[k] = _skt_mt_udp[k] or v end
438✔
1011
for k,v in pairs(_skt_mt_tcp.__index) do _skt_mt_udp.__index[k] = v end
3,358✔
1012

1013
_skt_mt_udp.__index.send        = function(self, ...) return self.socket:send(...) end
151✔
1014

1015
_skt_mt_udp.__index.sendto      = function(self, ...) return self.socket:sendto(...) end
161✔
1016

1017

1018
_skt_mt_udp.__index.receive =     function (self, size)
146✔
1019
                                    return copas.receive (self.socket, (size or UDP_DATAGRAM_MAX))
10✔
1020
                                  end
1021

1022
_skt_mt_udp.__index.receivefrom = function (self, size)
146✔
1023
                                    return copas.receivefrom (self.socket, (size or UDP_DATAGRAM_MAX))
20✔
1024
                                  end
1025

1026
                                  -- TODO: is this DNS related? hence blocking?
1027
_skt_mt_udp.__index.setpeername = function(self, ...) return self.socket:setpeername(...) end
151✔
1028

1029
_skt_mt_udp.__index.setsockname = function(self, ...) return self.socket:setsockname(...) end
146✔
1030

1031
                                    -- do not close client, as it is also the server for udp.
1032
_skt_mt_udp.__index.close       = function(self, ...) return true end
156✔
1033

1034
_skt_mt_udp.__index.settimeouts = function (self, connect, send, receive)
146✔
1035
                                    return copas.settimeouts(self.socket, connect, send, receive)
×
1036
                                  end
1037

1038

1039

1040
---
1041
-- Wraps a LuaSocket socket object in an async Copas based socket object.
1042
-- @param skt The socket to wrap
1043
-- @sslt (optional) Table with ssl parameters, use an empty table to use ssl with defaults
1044
-- @return wrapped socket object
1045
function copas.wrap (skt, sslt)
146✔
1046
  if (getmetatable(skt) == _skt_mt_tcp) or (getmetatable(skt) == _skt_mt_udp) then
290✔
1047
    return skt -- already wrapped
×
1048
  end
1049

1050
  skt:settimeout(0)
291✔
1051

1052
  if isTCP(skt) then
348✔
1053
    return setmetatable ({socket = skt, ssl_params = normalize_sslt(sslt)}, _skt_mt_tcp)
324✔
1054
  else
1055
    return setmetatable ({socket = skt}, _skt_mt_udp)
20✔
1056
  end
1057
end
1058

1059
--- Wraps a handler in a function that deals with wrapping the socket and doing the
1060
-- optional ssl handshake.
1061
function copas.handler(handler, sslparams)
146✔
1062
  -- TODO: pass a timeout value to set, and use during handshake
1063
  return function (skt, ...)
1064
    skt = copas.wrap(skt, sslparams) -- this call will normalize the sslparams table
96✔
1065
    local sslp = skt.ssl_params
80✔
1066
    if sslp.sni then skt:sni(sslp.sni.names, sslp.sni.strict) end
80✔
1067
    if sslp.wrap then skt:dohandshake(sslp.wrap) end
80✔
1068
    return handler(skt, ...)
75✔
1069
  end
1070
end
1071

1072

1073
--------------------------------------------------
1074
-- Error handling
1075
--------------------------------------------------
1076

1077
local _errhandlers = setmetatable({}, { __mode = "k" })   -- error handler per coroutine
146✔
1078

1079

1080
function copas.gettraceback(msg, co, skt)
146✔
1081
  local co_str = co == nil and "nil" or copas.getthreadname(co)
31✔
1082
  local skt_str = skt == nil and "nil" or copas.getsocketname(skt)
31✔
1083
  local msg_str = msg == nil and "" or tostring(msg)
31✔
1084
  if msg_str == "" then
31✔
1085
    msg_str = ("(coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str)
×
1086
  else
1087
    msg_str = ("%s (coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str)
31✔
1088
  end
1089

1090
  if type(co) == "thread" then
31✔
1091
    -- regular Copas coroutine
1092
    return debug.traceback(co, msg_str)
31✔
1093
  end
1094
  -- not a coroutine, but the main thread, this happens if a timeout callback
1095
  -- (see `copas.timeout` causes an error (those callbacks run on the main thread).
1096
  return debug.traceback(msg_str, 2)
×
1097
end
1098

1099

1100
local function _deferror(msg, co, skt)
1101
  print(copas.gettraceback(msg, co, skt))
24✔
1102
end
1103

1104

1105
function copas.seterrorhandler(err, default)
146✔
1106
  assert(err == nil or type(err) == "function", "Expected the handler to be a function, or nil")
50✔
1107
  if default then
50✔
1108
    assert(err ~= nil, "Expected the handler to be a function when setting the default")
35✔
1109
    _deferror = err
35✔
1110
  else
1111
    _errhandlers[coroutine_running()] = err
15✔
1112
  end
1113
end
1114
copas.setErrorHandler = copas.seterrorhandler  -- deprecated; old casing
146✔
1115

1116

1117
function copas.geterrorhandler(co)
146✔
1118
  co = co or coroutine_running()
10✔
1119
  return _errhandlers[co] or _deferror
10✔
1120
end
1121

1122

1123
-- if `bool` is truthy, then the original socket errors will be returned in case of timeouts;
1124
-- `timeout, wantread, wantwrite, Operation already in progress`. If falsy, it will always
1125
-- return `timeout`.
1126
function copas.useSocketTimeoutErrors(bool)
146✔
1127
  useSocketTimeoutErrors[coroutine_running()] = not not bool -- force to a boolean
5✔
1128
end
1129

1130
-------------------------------------------------------------------------------
1131
-- Thread handling
1132
-------------------------------------------------------------------------------
1133

1134
local function _doTick (co, skt, ...)
1135
  if not co then return end
193,625✔
1136

1137
  -- if a coroutine was canceled/removed, don't resume it
1138
  if _canceled[co] then
193,625✔
1139
    _canceled[co] = nil -- also clean up the registry
11✔
1140
    _threads[co] = nil
11✔
1141
    return
11✔
1142
  end
1143

1144
  -- res: the socket (being read/write on) or the time to sleep
1145
  -- new_q: either _writing, _reading, or _sleeping
1146
  -- local time_before = gettime()
1147
  local ok, res, new_q = coroutine_resume(co, skt, ...)
193,614✔
1148
  -- local duration = gettime() - time_before
1149
  -- if duration > 1 then
1150
  --   duration = math.floor(duration * 1000)
1151
  --   pcall(_errhandlers[co] or _deferror, "task ran for "..tostring(duration).." milliseconds.", co, skt)
1152
  -- end
1153

1154
  if new_q == _reading or new_q == _writing or new_q == _sleeping then
193,609✔
1155
    -- we're yielding to a new queue
1156
    new_q:insert (res)
189,418✔
1157
    new_q:push (res, co)
189,418✔
1158
    return
189,418✔
1159
  end
1160

1161
  -- coroutine is terminating
1162

1163
  if ok and coroutine_status(co) ~= "dead" then
4,191✔
1164
    -- it called coroutine.yield from a non-Copas function which is unexpected
1165
    ok = false
5✔
1166
    res = "coroutine.yield was called without a resume first, user-code cannot yield to Copas"
5✔
1167
  end
1168

1169
  if not ok then
4,191✔
1170
    local k, e = pcall(_errhandlers[co] or _deferror, res, co, skt)
38✔
1171
    if not k then
38✔
1172
      print("Failed executing error handler: " .. tostring(e))
×
1173
    end
1174
  end
1175

1176
  local skt_to_close = _autoclose[co]
4,191✔
1177
  if skt_to_close then
4,191✔
1178
    skt_to_close:close()
95✔
1179
    _autoclose[co] = nil
95✔
1180
    _autoclose_r[skt_to_close] = nil
95✔
1181
  end
1182

1183
  _errhandlers[co] = nil
4,191✔
1184
end
1185

1186

1187
local _accept do
146✔
1188
  local client_counters = setmetatable({}, { __mode = "k" })
146✔
1189

1190
  -- accepts a connection on socket input
1191
  function _accept(server_skt, handler)
118✔
1192
    local client_skt = server_skt:accept()
100✔
1193
    if client_skt then
100✔
1194
      local count = (client_counters[server_skt] or 0) + 1
100✔
1195
      client_counters[server_skt] = count
100✔
1196
      object_names[client_skt] = object_names[server_skt] .. ":client_" .. count
113✔
1197

1198
      client_skt:settimeout(0)
100✔
1199
      copas.settimeouts(client_skt, user_timeouts_connect[server_skt],  -- copy server socket timeout settings
200✔
1200
        user_timeouts_send[server_skt], user_timeouts_receive[server_skt])
113✔
1201

1202
      local co = coroutine_create(handler)
100✔
1203
      object_names[co] = object_names[server_skt] .. ":handler_" .. count
100✔
1204

1205
      if copas.autoclose then
100✔
1206
        _autoclose[co] = client_skt
100✔
1207
        _autoclose_r[client_skt] = co
100✔
1208
      end
1209

1210
      _doTick(co, client_skt)
100✔
1211
    end
1212
  end
1213
end
1214

1215
-------------------------------------------------------------------------------
1216
-- Adds a server/handler pair to Copas dispatcher
1217
-------------------------------------------------------------------------------
1218

1219
do
1220
  local function addTCPserver(server, handler, timeout, name)
1221
    server:settimeout(0)
75✔
1222
    if name then
75✔
1223
      object_names[server] = name
×
1224
    end
1225
    _servers[server] = handler
75✔
1226
    _reading:insert(server)
75✔
1227
    if timeout then
75✔
1228
      copas.settimeout(server, timeout)
15✔
1229
    end
1230
  end
1231

1232
  local function addUDPserver(server, handler, timeout, name)
1233
    server:settimeout(0)
×
1234
    local co = coroutine_create(handler)
×
1235
    if name then
×
1236
      object_names[server] = name
×
1237
    end
1238
    object_names[co] = object_names[server]..":handler"
×
1239
    _reading:insert(server)
×
1240
    if timeout then
×
1241
      copas.settimeout(server, timeout)
×
1242
    end
1243
    _doTick(co, server)
×
1244
  end
1245

1246

1247
  function copas.addserver(server, handler, timeout, name)
146✔
1248
    if isTCP(server) then
90✔
1249
      addTCPserver(server, handler, timeout, name)
90✔
1250
    else
1251
      addUDPserver(server, handler, timeout, name)
×
1252
    end
1253
  end
1254
end
1255

1256

1257
function copas.removeserver(server, keep_open)
146✔
1258
  local skt = server
70✔
1259
  local mt = getmetatable(server)
70✔
1260
  if mt == _skt_mt_tcp or mt == _skt_mt_udp then
70✔
1261
    skt = server.socket
×
1262
  end
1263

1264
  _servers:remove(skt)
70✔
1265
  _reading:remove(skt)
70✔
1266

1267
  if keep_open then
70✔
1268
    return true
15✔
1269
  end
1270
  return server:close()
55✔
1271
end
1272

1273

1274

1275
-------------------------------------------------------------------------------
1276
-- Adds an new coroutine thread to Copas dispatcher
1277
-------------------------------------------------------------------------------
1278
function copas.addnamedthread(name, handler, ...)
146✔
1279
  if type(name) == "function" and type(handler) == "string" then
4,282✔
1280
    -- old call, flip args for compatibility
1281
    name, handler = handler, name
×
1282
  end
1283

1284
  -- create a coroutine that skips the first argument, which is always the socket
1285
  -- passed by the scheduler, but `nil` in case of a task/thread
1286
  local thread = coroutine_create(function(_, ...)
8,564✔
1287
    copas.pause()
4,282✔
1288
    return handler(...)
4,277✔
1289
  end)
1290
  if name then
4,282✔
1291
    object_names[thread] = name
327✔
1292
  end
1293

1294
  _threads[thread] = true -- register this thread so it can be removed
4,282✔
1295
  _doTick (thread, nil, ...)
4,282✔
1296
  return thread
4,282✔
1297
end
1298

1299

1300
function copas.addthread(handler, ...)
146✔
1301
  return copas.addnamedthread(nil, handler, ...)
3,955✔
1302
end
1303

1304

1305
function copas.removethread(thread)
146✔
1306
  -- if the specified coroutine is registered, add it to the canceled table so
1307
  -- that next time it tries to resume it exits.
1308
  _canceled[thread] = _threads[thread or 0]
30✔
1309
  _sleeping:cancel(thread)
30✔
1310
end
1311

1312

1313

1314
-------------------------------------------------------------------------------
1315
-- Sleep/pause management functions
1316
-------------------------------------------------------------------------------
1317

1318
-- yields the current coroutine and wakes it after 'sleeptime' seconds.
1319
-- If sleeptime < 0 then it sleeps until explicitly woken up using 'wakeup'
1320
-- TODO: deprecated, remove in next major
1321
function copas.sleep(sleeptime)
146✔
1322
  coroutine_yield((sleeptime or 0), _sleeping)
×
1323
end
1324

1325

1326
-- yields the current coroutine and wakes it after 'sleeptime' seconds.
1327
-- if sleeptime < 0 then it sleeps 0 seconds.
1328
function copas.pause(sleeptime)
146✔
1329
  if sleeptime and sleeptime > 0 then
186,026✔
1330
    coroutine_yield(sleeptime, _sleeping)
6,487✔
1331
  else
1332
    coroutine_yield(0, _sleeping)
180,589✔
1333
  end
1334
end
1335

1336

1337
-- yields the current coroutine until explicitly woken up using 'wakeup'
1338
function copas.pauseforever()
146✔
1339
  coroutine_yield(-1, _sleeping)
2,575✔
1340
end
1341

1342

1343
-- Wakes up a sleeping coroutine 'co'.
1344
function copas.wakeup(co)
146✔
1345
  _sleeping:wakeup(co)
2,580✔
1346
end
1347

1348

1349

1350
-------------------------------------------------------------------------------
1351
-- Timeout management
1352
-------------------------------------------------------------------------------
1353

1354
do
1355
  local timeout_register = setmetatable({}, { __mode = "k" })
146✔
1356
  local time_out_thread
1357
  local timerwheel = require("timerwheel").new({
292✔
1358
      precision = TIMEOUT_PRECISION,
146✔
1359
      ringsize = math.floor(60*60*24/TIMEOUT_PRECISION),  -- ring size 1 day
146✔
1360
      err_handler = function(err)
1361
        return _deferror(err, time_out_thread)
13✔
1362
      end,
1363
    })
1364

1365
  time_out_thread = copas.addnamedthread("copas_core_timer", function()
292✔
1366
    while true do
1367
      copas.pause(TIMEOUT_PRECISION)
4,788✔
1368
      timerwheel:step()
5,571✔
1369
    end
1370
  end)
1371

1372
  -- get the number of timeouts running
1373
  function copas.gettimeouts()
146✔
1374
    return timerwheel:count()
1,616✔
1375
  end
1376

1377
  --- Sets the timeout for the current coroutine.
1378
  -- @param delay delay (seconds), use 0 (or math.huge) to cancel the timerout
1379
  -- @param callback function with signature: `function(coroutine)` where coroutine is the routine that timed-out
1380
  -- @return true
1381
  function copas.timeout(delay, callback)
146✔
1382
    local co = coroutine_running()
3,425,039✔
1383
    local existing_timer = timeout_register[co]
3,425,039✔
1384

1385
    if existing_timer then
3,425,039✔
1386
      timerwheel:cancel(existing_timer)
3,504✔
1387
    end
1388

1389
    if delay > 0 and delay ~= math.huge then
3,425,039✔
1390
      timeout_register[co] = timerwheel:set(delay, callback, co)
5,787✔
1391
    elseif delay == 0 or delay == math.huge then
3,420,222✔
1392
      timeout_register[co] = nil
3,420,222✔
1393
    else
1394
      error("timout value must be greater than or equal to 0, got: "..tostring(delay))
×
1395
    end
1396

1397
    return true
3,425,039✔
1398
  end
1399

1400
end
1401

1402

1403
-------------------------------------------------------------------------------
1404
-- main tasks: manage readable and writable socket sets
1405
-------------------------------------------------------------------------------
1406
-- a task is an object with a required method `step()` that deals with a
1407
-- single step for that task.
1408

1409
local _tasks = {} do
146✔
1410
  function _tasks:add(tsk)
146✔
1411
    _tasks[#_tasks + 1] = tsk
584✔
1412
  end
1413
end
1414

1415

1416
-- a task to check ready to read events
1417
local _readable_task = {} do
146✔
1418

1419
  local function tick(skt)
1420
    local handler = _servers[skt]
622✔
1421
    if handler then
622✔
1422
      _accept(skt, handler)
120✔
1423
    else
1424
      _reading:remove(skt)
522✔
1425
      _doTick(_reading:pop(skt), skt)
624✔
1426
    end
1427
  end
1428

1429
  function _readable_task:step()
146✔
1430
    for _, skt in ipairs(self._events) do
181,423✔
1431
      tick(skt)
622✔
1432
    end
1433
  end
1434

1435
  _tasks:add(_readable_task)
174✔
1436
end
1437

1438

1439
-- a task to check ready to write events
1440
local _writable_task = {} do
146✔
1441

1442
  local function tick(skt)
1443
    _writing:remove(skt)
235✔
1444
    _doTick(_writing:pop(skt), skt)
282✔
1445
  end
1446

1447
  function _writable_task:step()
146✔
1448
    for _, skt in ipairs(self._events) do
181,033✔
1449
      tick(skt)
235✔
1450
    end
1451
  end
1452

1453
  _tasks:add(_writable_task)
174✔
1454
end
1455

1456

1457

1458
-- sleeping threads task
1459
local _sleeping_task = {} do
146✔
1460

1461
  function _sleeping_task:step()
146✔
1462
    local now = gettime()
180,798✔
1463

1464
    local co = _sleeping:pop(now)
180,798✔
1465
    while co do
186,072✔
1466
      -- we're pushing them to _resumable, since that list will be replaced before
1467
      -- executing. This prevents tasks running twice in a row with pause(0) for example.
1468
      -- So here we won't execute, but at _resumable step which is next
1469
      _resumable:push(co)
5,274✔
1470
      co = _sleeping:pop(now)
6,325✔
1471
    end
1472
  end
1473

1474
  _tasks:add(_sleeping_task)
146✔
1475
end
1476

1477

1478

1479
-- resumable threads task
1480
local _resumable_task = {} do
146✔
1481

1482
  function _resumable_task:step()
146✔
1483
    -- replace the resume list before iterating, so items placed in there
1484
    -- will indeed end up in the next copas step, not in this one, and not
1485
    -- create a loop
1486
    local resumelist = _resumable:clear_resumelist()
180,798✔
1487

1488
    for _, co in ipairs(resumelist) do
369,282✔
1489
      _doTick(co)
188,486✔
1490
    end
1491
  end
1492

1493
  _tasks:add(_resumable_task)
146✔
1494
end
1495

1496

1497
-------------------------------------------------------------------------------
1498
-- Checks for reads and writes on sockets
1499
-------------------------------------------------------------------------------
1500
local _select_plain do
146✔
1501

1502
  local last_cleansing = 0
146✔
1503
  local duration = function(t2, t1) return t2-t1 end
180,947✔
1504

1505
  _select_plain = function(timeout)
1506
    local err
1507
    local now = gettime()
180,801✔
1508

1509
    -- remove any closed sockets to prevent select from hanging on them
1510
    if _closed[1] then
180,801✔
1511
      for i, skt in ipairs(_closed) do
331✔
1512
        _closed[i] = { _reading:remove(skt), _writing:remove(skt) }
232✔
1513
      end
1514
    end
1515

1516
    _readable_task._events, _writable_task._events, err = socket.select(_reading, _writing, timeout)
180,801✔
1517
    local r_events, w_events = _readable_task._events, _writable_task._events
180,801✔
1518

1519
    -- inject closed sockets in readable/writeable task so they can error out properly
1520
    if _closed[1] then
180,801✔
1521
      for i, skts in ipairs(_closed) do
331✔
1522
        _closed[i] = nil
166✔
1523
        r_events[#r_events+1] = skts[1]
166✔
1524
        w_events[#w_events+1] = skts[2]
166✔
1525
      end
1526
    end
1527

1528
    if duration(now, last_cleansing) > WATCH_DOG_TIMEOUT then
241,153✔
1529
      last_cleansing = now
141✔
1530

1531
      -- Check all sockets selected for reading, and check how long they have been waiting
1532
      -- for data already, without select returning them as readable
1533
      for skt,time in pairs(_reading_log) do
141✔
1534
        if not r_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
×
1535
          -- This one timedout while waiting to become readable, so move
1536
          -- it in the readable list and try and read anyway, despite not
1537
          -- having been returned by select
1538
          _reading_log[skt] = nil
×
1539
          r_events[#r_events + 1] = skt
×
1540
          r_events[skt] = #r_events
×
1541
        end
1542
      end
1543

1544
      -- Do the same for writing
1545
      for skt,time in pairs(_writing_log) do
141✔
1546
        if not w_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
×
1547
          _writing_log[skt] = nil
×
1548
          w_events[#w_events + 1] = skt
×
1549
          w_events[skt] = #w_events
×
1550
        end
1551
      end
1552
    end
1553

1554
    if err == "timeout" and #r_events + #w_events > 0 then
180,801✔
1555
      return nil
5✔
1556
    else
1557
      return err
180,796✔
1558
    end
1559
  end
1560
end
1561

1562

1563

1564
-------------------------------------------------------------------------------
1565
-- Dispatcher loop step.
1566
-- Listen to client requests and handles them
1567
-- Returns false if no socket-data was handled, or true if there was data
1568
-- handled (or nil + error message)
1569
-------------------------------------------------------------------------------
1570

1571
local copas_stats
1572
local min_ever, max_ever
1573

1574
local _select = _select_plain
146✔
1575

1576
-- instrumented version of _select() to collect stats
1577
local _select_instrumented = function(timeout)
1578
  if copas_stats then
×
1579
    local step_duration = gettime() - copas_stats.step_start
×
1580
    copas_stats.duration_max = math.max(copas_stats.duration_max, step_duration)
×
1581
    copas_stats.duration_min = math.min(copas_stats.duration_min, step_duration)
×
1582
    copas_stats.duration_tot = copas_stats.duration_tot + step_duration
×
1583
    copas_stats.steps = copas_stats.steps + 1
×
1584
  else
1585
    copas_stats = {
×
1586
      duration_max = -1,
1587
      duration_min = 999999,
1588
      duration_tot = 0,
1589
      steps = 0,
1590
    }
1591
  end
1592

1593
  local err = _select_plain(timeout)
×
1594

1595
  local now = gettime()
×
1596
  copas_stats.time_start = copas_stats.time_start or now
×
1597
  copas_stats.step_start = now
×
1598

1599
  return err
×
1600
end
1601

1602

1603
function copas.step(timeout)
146✔
1604
  -- Need to wake up the select call in time for the next sleeping event
1605
  if not _resumable:done() then
241,153✔
1606
    timeout = 0
176,510✔
1607
  else
1608
    timeout = math.min(_sleeping:getnext(), timeout or math.huge)
5,136✔
1609
  end
1610

1611
  local err = _select(timeout)
180,801✔
1612

1613
  for _, tsk in ipairs(_tasks) do
903,994✔
1614
    tsk:step()
723,198✔
1615
  end
1616

1617
  if err then
180,796✔
1618
    if err == "timeout" then
179,999✔
1619
      if timeout + 0.01 > TIMEOUT_PRECISION and math.random(100) > 90 then
179,999✔
1620
        -- we were idle, so occasionally do a GC sweep to ensure lingering
1621
        -- sockets are closed, and we don't accidentally block the loop from
1622
        -- exiting
1623
        collectgarbage()
324✔
1624
      end
1625
      return false
179,999✔
1626
    end
1627
    return nil, err
×
1628
  end
1629

1630
  return true
797✔
1631
end
1632

1633

1634
-------------------------------------------------------------------------------
1635
-- Check whether there is something to do.
1636
-- returns false if there are no sockets for read/write nor tasks scheduled
1637
-- (which means Copas is in an empty spin)
1638
-------------------------------------------------------------------------------
1639
function copas.finished()
146✔
1640
  return #_reading == 0 and #_writing == 0 and _resumable:done() and _sleeping:done(copas.gettimeouts())
182,088✔
1641
end
1642

1643
local _getstats do
146✔
1644
  local _getstats_instrumented, _getstats_plain
1645

1646

1647
  function _getstats_plain(enable)
118✔
1648
    -- this function gets hit if turned off, so turn on if true
1649
    if enable == true then
×
1650
      _select = _select_instrumented
×
1651
      _getstats = _getstats_instrumented
×
1652
      -- reset stats
1653
      min_ever = nil
×
1654
      max_ever = nil
×
1655
      copas_stats = nil
×
1656
    end
1657
    return {}
×
1658
  end
1659

1660

1661
  -- convert from seconds to millisecs, with microsec precision
1662
  local function useconds(t)
1663
    return math.floor((t * 1000000) + 0.5) / 1000
×
1664
  end
1665
  -- convert from seconds to seconds, with millisec precision
1666
  local function mseconds(t)
1667
    return math.floor((t * 1000) + 0.5) / 1000
×
1668
  end
1669

1670

1671
  function _getstats_instrumented(enable)
118✔
1672
    if enable == false then
×
1673
      _select = _select_plain
×
1674
      _getstats = _getstats_plain
×
1675
      -- instrumentation disabled, so switch to the plain implementation
1676
      return _getstats(enable)
×
1677
    end
1678
    if (not copas_stats) or (copas_stats.step == 0) then
×
1679
      return {}
×
1680
    end
1681
    local stats = copas_stats
×
1682
    copas_stats = nil
×
1683
    min_ever = math.min(min_ever or 9999999, stats.duration_min)
×
1684
    max_ever = math.max(max_ever or 0, stats.duration_max)
×
1685
    stats.duration_min_ever = min_ever
×
1686
    stats.duration_max_ever = max_ever
×
1687
    stats.duration_avg = stats.duration_tot / stats.steps
×
1688
    stats.step_start = nil
×
1689
    stats.time_end = gettime()
×
1690
    stats.time_tot = stats.time_end - stats.time_start
×
1691
    stats.time_avg = stats.time_tot / stats.steps
×
1692

1693
    stats.duration_avg = useconds(stats.duration_avg)
×
1694
    stats.duration_max = useconds(stats.duration_max)
×
1695
    stats.duration_max_ever = useconds(stats.duration_max_ever)
×
1696
    stats.duration_min = useconds(stats.duration_min)
×
1697
    stats.duration_min_ever = useconds(stats.duration_min_ever)
×
1698
    stats.duration_tot = useconds(stats.duration_tot)
×
1699
    stats.time_avg = useconds(stats.time_avg)
×
1700
    stats.time_start = mseconds(stats.time_start)
×
1701
    stats.time_end = mseconds(stats.time_end)
×
1702
    stats.time_tot = mseconds(stats.time_tot)
×
1703
    return stats
×
1704
  end
1705

1706
  _getstats = _getstats_plain
146✔
1707
end
1708

1709

1710
function copas.status(enable_stats)
146✔
1711
  local res = _getstats(enable_stats)
×
1712
  res.running = not not copas.running
×
1713
  res.timeout = copas.gettimeouts()
×
1714
  res.timer, res.inactive = _sleeping:status()
×
1715
  res.read = #_reading
×
1716
  res.write = #_writing
×
1717
  res.active = _resumable:count()
×
1718
  return res
×
1719
end
1720

1721

1722
-------------------------------------------------------------------------------
1723
-- Dispatcher endless loop.
1724
-- Listen to client requests and handles them forever
1725
-------------------------------------------------------------------------------
1726
function copas.loop(initializer, timeout)
146✔
1727
  if type(initializer) == "function" then
221✔
1728
    copas.addnamedthread("copas_initializer", initializer)
96✔
1729
  else
1730
    timeout = initializer or timeout
140✔
1731
  end
1732

1733
  copas.running = true
221✔
1734
  while not copas.finished() do copas.step(timeout) end
301,762✔
1735
  copas.running = false
216✔
1736
end
1737

1738

1739
-------------------------------------------------------------------------------
1740
-- Naming sockets and coroutines.
1741
-------------------------------------------------------------------------------
1742
do
1743
  local function realsocket(skt)
1744
    local mt = getmetatable(skt)
75✔
1745
    if mt == _skt_mt_tcp or mt == _skt_mt_udp then
75✔
1746
      return skt.socket
75✔
1747
    else
1748
      return skt
×
1749
    end
1750
  end
1751

1752

1753
  function copas.setsocketname(name, skt)
146✔
1754
    assert(type(name) == "string", "expected arg #1 to be a string")
75✔
1755
    skt = assert(realsocket(skt), "expected arg #2 to be a socket")
90✔
1756
    object_names[skt] = name
75✔
1757
  end
1758

1759

1760
  function copas.getsocketname(skt)
146✔
1761
    skt = assert(realsocket(skt), "expected arg #1 to be a socket")
×
1762
    return object_names[skt]
×
1763
  end
1764
end
1765

1766

1767
function copas.setthreadname(name, coro)
146✔
1768
  assert(type(name) == "string", "expected arg #1 to be a string")
50✔
1769
  coro = coro or coroutine_running()
50✔
1770
  assert(type(coro) == "thread", "expected arg #2 to be a coroutine or nil")
50✔
1771
  object_names[coro] = name
50✔
1772
end
1773

1774

1775
function copas.getthreadname(coro)
146✔
1776
  coro = coro or coroutine_running()
31✔
1777
  assert(type(coro) == "thread", "expected arg #1 to be a coroutine or nil")
31✔
1778
  return object_names[coro]
33✔
1779
end
1780

1781
-------------------------------------------------------------------------------
1782
-- Debug functionality.
1783
-------------------------------------------------------------------------------
1784
do
1785
  copas.debug = {}
146✔
1786

1787
  local log_core    -- if truthy, the core-timer will also be logged
1788
  local debug_log   -- function used as logger
1789

1790

1791
  local debug_yield = function(skt, queue)
1792
    local name = object_names[coroutine_running()]
2,405✔
1793

1794
    if log_core or name ~= "copas_core_timer" then
2,405✔
1795
      if queue == _sleeping then
2,397✔
1796
        debug_log("yielding '", name, "' to SLEEP for ", skt," seconds")
2,377✔
1797

1798
      elseif queue == _writing then
20✔
1799
        debug_log("yielding '", name, "' to WRITE on '", object_names[skt], "'")
6✔
1800

1801
      elseif queue == _reading then
15✔
1802
        debug_log("yielding '", name, "' to READ on '", object_names[skt], "'")
16✔
1803

1804
      else
1805
        debug_log("thread '", name, "' yielding to unexpected queue; ", tostring(queue), " (", type(queue), ")", debug.traceback())
×
1806
      end
1807
    end
1808

1809
    return coroutine.yield(skt, queue)
2,405✔
1810
  end
1811

1812

1813
  local debug_resume = function(coro, skt, ...)
1814
    local name = object_names[coro]
2,415✔
1815

1816
    if skt then
2,415✔
1817
      debug_log("resuming '", name, "' for socket '", object_names[skt], "'")
20✔
1818
    else
1819
      if log_core or name ~= "copas_core_timer" then
2,395✔
1820
        debug_log("resuming '", name, "'")
2,387✔
1821
      end
1822
    end
1823
    return coroutine.resume(coro, skt, ...)
2,415✔
1824
  end
1825

1826

1827
  local debug_create = function(f)
1828
    local f_wrapped = function(...)
1829
      local results = pack(f(...))
12✔
1830
      debug_log("exiting '", object_names[coroutine_running()], "'")
10✔
1831
      return unpack(results)
10✔
1832
    end
1833

1834
    return coroutine.create(f_wrapped)
10✔
1835
  end
1836

1837

1838
  debug_log = fnil
146✔
1839

1840

1841
  -- enables debug output for all coroutine operations.
1842
  function copas.debug.start(logger, core)
292✔
1843
    log_core = core
5✔
1844
    debug_log = logger or print
5✔
1845
    coroutine_yield = debug_yield
5✔
1846
    coroutine_resume = debug_resume
5✔
1847
    coroutine_create = debug_create
5✔
1848
  end
1849

1850

1851
  -- disables debug output for coroutine operations.
1852
  function copas.debug.stop()
292✔
1853
    debug_log = fnil
×
1854
    coroutine_yield = coroutine.yield
×
1855
    coroutine_resume = coroutine.resume
×
1856
    coroutine_create = coroutine.create
×
1857
  end
1858

1859
  do
1860
    local call_id = 0
146✔
1861

1862
    -- Description table of socket functions for debug output.
1863
    -- each socket function name has TWO entries;
1864
    -- 'name_in' and 'name_out', each being an array of names/descriptions of respectively
1865
    -- input parameters and return values.
1866
    -- If either table has a 'callback' key, then that is a function that will be called
1867
    -- with the parameters/return-values for further inspection.
1868
    local args = {
146✔
1869
      settimeout_in = {
146✔
1870
        "socket ",
118✔
1871
        "seconds",
118✔
1872
        "mode   ",
1873
      },
146✔
1874
      settimeout_out = {
146✔
1875
        "success",
118✔
1876
        "error  ",
1877
      },
146✔
1878
      connect_in = {
146✔
1879
        "socket ",
118✔
1880
        "address",
118✔
1881
        "port   ",
1882
      },
146✔
1883
      connect_out = {
146✔
1884
        "success",
118✔
1885
        "error  ",
1886
      },
146✔
1887
      getfd_in = {
146✔
1888
        "socket ",
1889
        -- callback = function(...)
1890
        --   print(debug.traceback("called from:", 4))
1891
        -- end,
1892
      },
146✔
1893
      getfd_out = {
146✔
1894
        "fd",
1895
      },
146✔
1896
      send_in = {
146✔
1897
        "socket   ",
118✔
1898
        "data     ",
118✔
1899
        "idx-start",
118✔
1900
        "idx-end  ",
1901
      },
146✔
1902
      send_out = {
146✔
1903
        "last-idx-send    ",
118✔
1904
        "error            ",
118✔
1905
        "err-last-idx-send",
1906
      },
146✔
1907
      receive_in = {
146✔
1908
        "socket ",
118✔
1909
        "pattern",
118✔
1910
        "prefix ",
1911
      },
146✔
1912
      receive_out = {
146✔
1913
        "received    ",
118✔
1914
        "error       ",
118✔
1915
        "partial data",
1916
      },
146✔
1917
      dirty_in = {
146✔
1918
        "socket",
1919
        -- callback = function(...)
1920
        --   print(debug.traceback("called from:", 4))
1921
        -- end,
1922
      },
146✔
1923
      dirty_out = {
146✔
1924
        "data in read-buffer",
1925
      },
146✔
1926
      close_in = {
146✔
1927
        "socket",
1928
        -- callback = function(...)
1929
        --   print(debug.traceback("called from:", 4))
1930
        -- end,
1931
      },
146✔
1932
      close_out = {
146✔
1933
        "success",
118✔
1934
        "error",
1935
      },
146✔
1936
    }
1937
    local function print_call(func, msg, ...)
1938
      print(msg)
220✔
1939
      local arg = pack(...)
220✔
1940
      local desc = args[func] or {}
220✔
1941
      for i = 1, math.max(arg.n, #desc) do
500✔
1942
        local value = arg[i]
280✔
1943
        if type(value) == "string" then
280✔
1944
          local xvalue = value:sub(1,30)
15✔
1945
          if xvalue ~= value then
15✔
1946
            xvalue = xvalue .."(...truncated)"
×
1947
          end
1948
          print("\t"..(desc[i] or i)..": '"..tostring(xvalue).."' ("..type(value).." #"..#value..")")
15✔
1949
        else
1950
          print("\t"..(desc[i] or i)..": '"..tostring(value).."' ("..type(value)..")")
265✔
1951
        end
1952
      end
1953
      if desc.callback then
220✔
1954
        desc.callback(...)
×
1955
      end
1956
    end
1957

1958
    local debug_mt = {
146✔
1959
      __index = function(self, key)
1960
        local value = self.__original_socket[key]
110✔
1961
        if type(value) ~= "function" then
110✔
1962
          return value
×
1963
        end
1964
        return function(self2, ...)
1965
            local my_id = call_id + 1
110✔
1966
            call_id = my_id
110✔
1967
            local results
1968

1969
            if self2 ~= self then
110✔
1970
              -- there is no self
1971
              print_call(tostring(key).."_in", my_id .. "-calling '"..tostring(key) .. "' with; ", self, ...)
×
1972
              results = pack(value(self, ...))
×
1973
            else
1974
              print_call(tostring(key).."_in", my_id .. "-calling '" .. tostring(key) .. "' with; ", self.__original_socket, ...)
110✔
1975
              results = pack(value(self.__original_socket, ...))
130✔
1976
            end
1977
            print_call(tostring(key).."_out", my_id .. "-results '"..tostring(key) .. "' returned; ", unpack(results))
130✔
1978
            return unpack(results)
110✔
1979
          end
1980
      end,
1981
      __tostring = function(self)
1982
        return tostring(self.__original_socket)
20✔
1983
      end
1984
    }
1985

1986

1987
    -- wraps a socket (copas or luasocket) in a debug version printing all calls
1988
    -- and their parameters/return values. Extremely noisy!
1989
    -- returns the wrapped socket.
1990
    -- NOTE: only for plain sockets, will not support TLS
1991
    function copas.debug.socket(original_skt)
292✔
1992
      if (getmetatable(original_skt) == _skt_mt_tcp) or (getmetatable(original_skt) == _skt_mt_udp) then
5✔
1993
        -- already wrapped as Copas socket, so recurse with the original luasocket one
1994
        original_skt.socket = copas.debug.socket(original_skt.socket)
×
1995
        return original_skt
×
1996
      end
1997

1998
      local proxy = setmetatable({
10✔
1999
        __original_socket = original_skt
5✔
2000
      }, debug_mt)
5✔
2001

2002
      return proxy
5✔
2003
    end
2004
  end
2005
end
2006

2007

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

© 2026 Coveralls, Inc