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

lunarmodules / copas / 14306023696

07 Apr 2025 09:44AM UTC coverage: 84.571% (-0.03%) from 84.596%
14306023696

push

github

web-flow
Merge dd45c8ac4 into 6369440fd

4 of 4 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

1332 of 1575 relevant lines covered (84.57%)

32559.99 hits per line

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

80.52
/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-2025 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
122✔
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
122✔
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
122✔
26
  if pcall(require, "socket") then
122✔
27
    -- found LuaSocket
28
    socket = require "socket"
118✔
29
  end
30

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

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

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

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

51

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

58

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

64

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

71

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

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

91
  function socket.protect(func)
118✔
92
    return function (...)
93
            return statusHandler(pcall(func, ...))
56✔
94
          end
95
  end
96

97
  function socket.newtry(finalizer)
118✔
98
    return function (...)
99
            local status = (...)
876✔
100
            if not status then
876✔
101
              pcall(finalizer or fnil, select(2, ...))
20✔
102
              error(setmetatable({ (select(2, ...)) }, err_mt), 0)
20✔
103
            end
104
            return ...
856✔
105
          end
106
  end
107

108
  socket.try = socket.newtry()
118✔
109
end
110

111

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

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

134

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

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

143
-- indicator for the loop running
144
copas.running = false
122✔
145

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

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

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

168
local function newsocketset()
169
  local set = {}
366✔
170

171
  do  -- set implementation
172
    local reverse = {}
366✔
173

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

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

200
  end
201

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

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

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

224
  end
225

226
  return set
366✔
227
end
228

229

230

231
-- Threads immediately resumable
232
local _resumable = {} do
122✔
233
  local resumelist = {}
122✔
234

235
  function _resumable:push(co)
122✔
236
    resumelist[#resumelist + 1] = co
120,507✔
237
  end
238

239
  function _resumable:clear_resumelist()
122✔
240
    local lst = resumelist
114,335✔
241
    resumelist = {}
114,335✔
242
    return lst
114,335✔
243
  end
244

245
  function _resumable:done()
122✔
246
    return resumelist[1] == nil
116,445✔
247
  end
248

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

253
end
254

255

256

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

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

264

265
  -- Required base implementation
266
  -----------------------------------------
267
  _sleeping.insert = fnil
122✔
268
  _sleeping.remove = fnil
122✔
269

270
  -- push a new timer on the heap
271
  function _sleeping:push(sleeptime, co)
122✔
272
    if sleeptime < 0 then
120,598✔
273
      lethargy[co] = true
2,092✔
274
    elseif sleeptime == 0 then
118,506✔
275
      _resumable:push(co)
113,726✔
276
    else
277
      heap:insert(gettime() + sleeptime, co)
4,780✔
278
    end
279
  end
280

281
  -- find the thread that should wake up to the time, if any
282
  function _sleeping:pop(time)
122✔
283
    if time < (heap:peekValue() or math.huge) then
118,980✔
284
      return
114,335✔
285
    end
286
    return heap:pop()
4,645✔
287
  end
288

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

299
  function _sleeping:wakeup(co)
122✔
300
    if lethargy[co] then
2,100✔
301
      lethargy[co] = nil
2,080✔
302
      _resumable:push(co)
2,080✔
303
      return
2,080✔
304
    end
305
    if heap:remove(co) then
20✔
306
      _resumable:push(co)
8✔
307
    end
308
  end
309

310
  function _sleeping:cancel(co)
122✔
311
    lethargy[co] = nil
28✔
312
    heap:remove(co)
28✔
313
  end
314

315
  -- @param tos number of timeouts running
316
  function _sleeping:done(tos)
122✔
317
    -- return true if we have nothing more to do
318
    -- the timeout task doesn't qualify as work (fallbacks only),
319
    -- the lethargy also doesn't qualify as work ('dead' tasks),
320
    -- but the combination of a timeout + a lethargy can be work
321
    return heap:size() == 1       -- 1 means only the timeout-timer task is running
1,731✔
322
           and not (tos > 0 and next(lethargy))
1,731✔
323
  end
324

325
  -- gets number of threads in binaryheap and lethargy
326
  function _sleeping:status()
122✔
327
    local c = 0
×
328
    for _ in pairs(lethargy) do c = c + 1 end
×
329

330
    return heap:size(), c
×
331
  end
332

333
end   -- _sleeping
334

335

336

337
-------------------------------------------------------------------------------
338
-- Tracking coroutines and sockets
339
-------------------------------------------------------------------------------
340

341
local _servers = newsocketset() -- servers being handled
122✔
342
local _threads = setmetatable({}, {__mode = "k"})  -- registered threads added with addthread()
122✔
343
local _canceled = setmetatable({}, {__mode = "k"}) -- threads that are canceled and pending removal
122✔
344
local _autoclose = setmetatable({}, {__mode = "kv"}) -- sockets (value) to close when a thread (key) exits
122✔
345
local _autoclose_r = setmetatable({}, {__mode = "kv"}) -- reverse: sockets (key) to close when a thread (value) exits
122✔
346

347

348
-- for each socket we log the last read and last write times to enable the
349
-- watchdog to follow up if it takes too long.
350
-- tables contain the time, indexed by the socket
351
local _reading_log = {}
122✔
352
local _writing_log = {}
122✔
353

354
local _closed = {} -- track sockets that have been closed (list/array)
122✔
355

356
local _reading = newsocketset() -- sockets currently being read
122✔
357
local _writing = newsocketset() -- sockets currently being written
122✔
358
local _isSocketTimeout = { -- set of errors indicating a socket-timeout
122✔
359
  ["timeout"] = true,      -- default LuaSocket timeout
122✔
360
  ["wantread"] = true,     -- LuaSec specific timeout
122✔
361
  ["wantwrite"] = true,    -- LuaSec specific timeout
122✔
362
}
363

364
-------------------------------------------------------------------------------
365
-- Coroutine based socket timeouts.
366
-------------------------------------------------------------------------------
367
local user_timeouts_connect
368
local user_timeouts_send
369
local user_timeouts_receive
370
do
371
  local timeout_mt = {
122✔
372
    __mode = "k",
122✔
373
    __index = function(self, skt)
374
      -- if there is no timeout found, we insert one automatically, to block forever
375
      self[skt] = math.huge
276✔
376
      return self[skt]
276✔
377
    end,
378
  }
379

380
  user_timeouts_connect = setmetatable({}, timeout_mt)
122✔
381
  user_timeouts_send = setmetatable({}, timeout_mt)
122✔
382
  user_timeouts_receive = setmetatable({}, timeout_mt)
122✔
383
end
384

385
local useSocketTimeoutErrors = setmetatable({},{ __mode = "k" })
122✔
386

387

388
-- sto = socket-time-out
389
local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
122✔
390

391
  local socket_register = setmetatable({}, { __mode = "k" })    -- socket by coroutine
122✔
392
  local operation_register = setmetatable({}, { __mode = "k" }) -- operation "read"/"write" by coroutine
122✔
393
  local timeout_flags = setmetatable({}, { __mode = "k" })      -- true if timedout, by coroutine
122✔
394

395

396
  local function socket_callback(co)
397
    local skt = socket_register[co]
48✔
398
    local queue = operation_register[co]
48✔
399

400
    -- flag the timeout and resume the coroutine
401
    timeout_flags[co] = true
48✔
402
    _resumable:push(co)
48✔
403

404
    -- clear the socket from the current queue
405
    if queue == "read" then
48✔
406
      _reading:remove(skt)
40✔
407
    elseif queue == "write" then
8✔
408
      _writing:remove(skt)
8✔
409
    else
410
      error("bad queue name; expected 'read'/'write', got: "..tostring(queue))
×
411
    end
412
  end
413

414

415
  -- Sets a socket timeout.
416
  -- Calling it as `sto_timeout()` will cancel the timeout.
417
  -- @param queue (string) the queue the socket is currently in, must be either "read" or "write"
418
  -- @param skt (socket) the socket on which to operate
419
  -- @param use_connect_to (bool) timeout to use is determined based on queue (read/write) or if this
420
  -- is truthy, it is the connect timeout.
421
  -- @return true
422
  function sto_timeout(skt, queue, use_connect_to)
93✔
423
    local co = coroutine_running()
2,131,662✔
424
    socket_register[co] = skt
2,131,662✔
425
    operation_register[co] = queue
2,131,662✔
426
    timeout_flags[co] = nil
2,131,662✔
427
    if skt then
2,131,662✔
428
      local to = (use_connect_to and user_timeouts_connect[skt]) or
1,065,855✔
429
                 (queue == "read" and user_timeouts_receive[skt]) or
1,065,651✔
430
                 user_timeouts_send[skt]
9,836✔
431
      copas.timeout(to, socket_callback)
1,065,855✔
432
    else
433
      copas.timeout(0)
1,065,807✔
434
    end
435
    return true
2,131,662✔
436
  end
437

438

439
  -- Changes the timeout to a different queue (read/write).
440
  -- Only usefull with ssl-handshakes and "wantread", "wantwrite" errors, when
441
  -- the queue has to be changed, so the timeout handler knows where to find the socket.
442
  -- @param queue (string) the new queue the socket is in, must be either "read" or "write"
443
  -- @return true
444
  function sto_change_queue(queue)
93✔
445
    operation_register[coroutine_running()] = queue
556✔
446
    return true
556✔
447
  end
448

449

450
  -- Responds with `true` if the operation timed-out.
451
  function sto_timed_out()
93✔
452
    return timeout_flags[coroutine_running()]
700✔
453
  end
454

455

456
  -- Returns the proper timeout error
457
  function sto_error(err)
93✔
458
    return useSocketTimeoutErrors[coroutine_running()] and err or "timeout"
48✔
459
  end
460
end
461

462

463

464
-------------------------------------------------------------------------------
465
-- Coroutine based socket I/O functions.
466
-------------------------------------------------------------------------------
467

468
-- Returns "tcp"" for plain TCP and "ssl" for ssl-wrapped sockets, so truthy
469
-- for tcp based, and falsy for udp based.
470
local isTCP do
122✔
471
  local lookup = {
122✔
472
    tcp = "tcp",
122✔
473
    SSL = "ssl",
122✔
474
  }
475

476
  function isTCP(socket)
93✔
477
    return lookup[tostring(socket):sub(1,3)]
412✔
478
  end
479
end
480

481
function copas.close(skt, ...)
122✔
482
  _closed[#_closed+1] = skt
140✔
483
  return skt:close(...)
140✔
484
end
485

486

487

488
-- nil or negative is indefinitly
489
function copas.settimeout(skt, timeout)
122✔
490
  timeout = timeout or -1
136✔
491
  if type(timeout) ~= "number" then
136✔
492
    return nil, "timeout must be 'nil' or a number"
12✔
493
  end
494

495
  return copas.settimeouts(skt, timeout, timeout, timeout)
124✔
496
end
497

498
-- negative is indefinitly, nil means do not change
499
function copas.settimeouts(skt, connect, send, read)
122✔
500

501
  if connect ~= nil and type(connect) ~= "number" then
272✔
502
    return nil, "connect timeout must be 'nil' or a number"
×
503
  end
504
  if connect then
272✔
505
    if connect < 0 then
272✔
506
      connect = nil
×
507
    end
508
    user_timeouts_connect[skt] = connect
272✔
509
  end
510

511

512
  if send ~= nil and type(send) ~= "number" then
272✔
513
    return nil, "send timeout must be 'nil' or a number"
×
514
  end
515
  if send then
272✔
516
    if send < 0 then
272✔
517
      send = nil
×
518
    end
519
    user_timeouts_send[skt] = send
272✔
520
  end
521

522

523
  if read ~= nil and type(read) ~= "number" then
272✔
524
    return nil, "read timeout must be 'nil' or a number"
×
525
  end
526
  if read then
272✔
527
    if read < 0 then
272✔
528
      read = nil
×
529
    end
530
    user_timeouts_receive[skt] = read
272✔
531
  end
532

533

534
  return true
272✔
535
end
536

537
-- reads a pattern from a client and yields to the reading set on timeouts
538
-- UDP: a UDP socket expects a second argument to be a number, so it MUST
539
-- be provided as the 'pattern' below defaults to a string. Will throw a
540
-- 'bad argument' error if omitted.
541
function copas.receive(client, pattern, part)
122✔
542
  local s, err
543
  pattern = pattern or "*l"
1,055,791✔
544
  local current_log = _reading_log
1,055,791✔
545
  sto_timeout(client, "read")
1,055,791✔
546

547
  repeat
548
    s, err, part = client:receive(pattern, part)
1,056,130✔
549

550
    -- guarantees that high throughput doesn't take other threads to starvation
551
    if (math.random(100) > 90) then
1,056,130✔
552
      copas.pause()
105,565✔
553
    end
554

555
    if s then
1,056,130✔
556
      current_log[client] = nil
1,055,723✔
557
      sto_timeout()
1,055,723✔
558
      return s, err, part
1,055,723✔
559

560
    elseif not _isSocketTimeout[err] then
407✔
561
      current_log[client] = nil
32✔
562
      sto_timeout()
32✔
563
      return s, err, part
32✔
564

565
    elseif sto_timed_out() then
375✔
566
      current_log[client] = nil
36✔
567
      return nil, sto_error(err), part
36✔
568
    end
569

570
    if err == "wantwrite" then -- wantwrite may be returned during SSL renegotiations
339✔
571
      current_log = _writing_log
×
572
      current_log[client] = gettime()
×
573
      sto_change_queue("write")
×
574
      coroutine_yield(client, _writing)
×
575
    else
576
      current_log = _reading_log
339✔
577
      current_log[client] = gettime()
339✔
578
      sto_change_queue("read")
339✔
579
      coroutine_yield(client, _reading)
339✔
580
    end
581
  until false
339✔
582
end
583

584
-- receives data from a client over UDP. Not available for TCP.
585
-- (this is a copy of receive() method, adapted for receivefrom() use)
586
function copas.receivefrom(client, size)
122✔
587
  local s, err, port
588
  size = size or UDP_DATAGRAM_MAX
16✔
589
  sto_timeout(client, "read")
16✔
590

591
  repeat
592
    s, err, port = client:receivefrom(size) -- upon success err holds ip address
32✔
593

594
    -- garantees that high throughput doesn't take other threads to starvation
595
    if (math.random(100) > 90) then
32✔
596
      copas.pause()
4✔
597
    end
598

599
    if s then
32✔
600
      _reading_log[client] = nil
12✔
601
      sto_timeout()
12✔
602
      return s, err, port
12✔
603

604
    elseif err ~= "timeout" then
20✔
605
      _reading_log[client] = nil
×
606
      sto_timeout()
×
607
      return s, err, port
×
608

609
    elseif sto_timed_out() then
20✔
610
      _reading_log[client] = nil
4✔
611
      return nil, sto_error(err), port
4✔
612
    end
613

614
    _reading_log[client] = gettime()
16✔
615
    coroutine_yield(client, _reading)
16✔
616
  until false
16✔
617
end
618

619
-- same as above but with special treatment when reading chunks,
620
-- unblocks on any data received.
621
function copas.receivepartial(client, pattern, part)
122✔
622
  local s, err
623
  pattern = pattern or "*l"
8✔
624
  local orig_size = #(part or "")
8✔
625
  local current_log = _reading_log
8✔
626
  sto_timeout(client, "read")
8✔
627

628
  repeat
629
    s, err, part = client:receive(pattern, part)
8✔
630

631
    -- guarantees that high throughput doesn't take other threads to starvation
632
    if (math.random(100) > 90) then
8✔
UNCOV
633
      copas.pause()
×
634
    end
635

636
    if s or (type(part) == "string" and #part > orig_size) then
8✔
637
      current_log[client] = nil
8✔
638
      sto_timeout()
8✔
639
      return s, err, part
8✔
640

641
    elseif not _isSocketTimeout[err] then
×
642
      current_log[client] = nil
×
643
      sto_timeout()
×
644
      return s, err, part
×
645

646
    elseif sto_timed_out() then
×
647
      current_log[client] = nil
×
648
      return nil, sto_error(err), part
×
649
    end
650

651
    if err == "wantwrite" then
×
652
      current_log = _writing_log
×
653
      current_log[client] = gettime()
×
654
      sto_change_queue("write")
×
655
      coroutine_yield(client, _writing)
×
656
    else
657
      current_log = _reading_log
×
658
      current_log[client] = gettime()
×
659
      sto_change_queue("read")
×
660
      coroutine_yield(client, _reading)
×
661
    end
662
  until false
×
663
end
664
copas.receivePartial = copas.receivepartial  -- compat: receivePartial is deprecated
122✔
665

666
-- sends data to a client. The operation is buffered and
667
-- yields to the writing set on timeouts
668
-- Note: from and to parameters will be ignored by/for UDP sockets
669
function copas.send(client, data, from, to)
122✔
670
  local s, err
671
  from = from or 1
9,836✔
672
  local lastIndex = from - 1
9,836✔
673
  local current_log = _writing_log
9,836✔
674
  sto_timeout(client, "write")
9,836✔
675

676
  repeat
677
    s, err, lastIndex = client:send(data, lastIndex + 1, to)
9,953✔
678

679
    -- guarantees that high throughput doesn't take other threads to starvation
680
    if (math.random(100) > 90) then
9,953✔
681
      copas.pause()
910✔
682
    end
683

684
    if s then
9,953✔
685
      current_log[client] = nil
9,820✔
686
      sto_timeout()
9,820✔
687
      return s, err, lastIndex
9,820✔
688

689
    elseif not _isSocketTimeout[err] then
133✔
690
      current_log[client] = nil
16✔
691
      sto_timeout()
16✔
692
      return s, err, lastIndex
16✔
693

694
    elseif sto_timed_out() then
117✔
695
      current_log[client] = nil
×
696
      return nil, sto_error(err), lastIndex
×
697
    end
698

699
    if err == "wantread" then
117✔
700
      current_log = _reading_log
×
701
      current_log[client] = gettime()
×
702
      sto_change_queue("read")
×
703
      coroutine_yield(client, _reading)
×
704
    else
705
      current_log = _writing_log
117✔
706
      current_log[client] = gettime()
117✔
707
      sto_change_queue("write")
117✔
708
      coroutine_yield(client, _writing)
117✔
709
    end
710
  until false
117✔
711
end
712

713
function copas.sendto(client, data, ip, port)
122✔
714
  -- deprecated; for backward compatibility only, since UDP doesn't block on sending
715
  return client:sendto(data, ip, port)
×
716
end
717

718
-- waits until connection is completed
719
function copas.connect(skt, host, port)
122✔
720
  skt:settimeout(0)
136✔
721
  local ret, err, tried_more_than_once
722
  sto_timeout(skt, "write", true)
136✔
723

724
  repeat
725
    ret, err = skt:connect(host, port)
216✔
726

727
    -- non-blocking connect on Windows results in error "Operation already
728
    -- in progress" to indicate that it is completing the request async. So essentially
729
    -- it is the same as "timeout"
730
    if ret or (err ~= "timeout" and err ~= "Operation already in progress") then
216✔
731
      _writing_log[skt] = nil
128✔
732
      sto_timeout()
128✔
733
      -- Once the async connect completes, Windows returns the error "already connected"
734
      -- to indicate it is done, so that error should be ignored. Except when it is the
735
      -- first call to connect, then it was already connected to something else and the
736
      -- error should be returned
737
      if (not ret) and (err == "already connected" and tried_more_than_once) then
128✔
738
        return 1
×
739
      end
740
      return ret, err
128✔
741

742
    elseif sto_timed_out() then
88✔
743
      _writing_log[skt] = nil
8✔
744
      return nil, sto_error(err)
8✔
745
    end
746

747
    tried_more_than_once = tried_more_than_once or true
80✔
748
    _writing_log[skt] = gettime()
80✔
749
    coroutine_yield(skt, _writing)
80✔
750
  until false
80✔
751
end
752

753

754
-- Wraps a tcp socket in an ssl socket and configures it. If the socket was
755
-- already wrapped, it does nothing and returns the socket.
756
-- @param wrap_params the parameters for the ssl-context
757
-- @return wrapped socket, or throws an error
758
local function ssl_wrap(skt, wrap_params)
759
  if isTCP(skt) == "ssl" then return skt end -- was already wrapped
120✔
760
  if not wrap_params then
68✔
761
    error("cannot wrap socket into a secure socket (using 'ssl.wrap()') without parameters/context")
×
762
  end
763

764
  ssl = ssl or require("ssl")
68✔
765
  local nskt = assert(ssl.wrap(skt, wrap_params)) -- assert, because we do not want to silently ignore this one!!
68✔
766

767
  nskt:settimeout(0)  -- non-blocking on the ssl-socket
68✔
768
  copas.settimeouts(nskt, user_timeouts_connect[skt],
136✔
769
    user_timeouts_send[skt], user_timeouts_receive[skt]) -- copy copas user-timeout to newly wrapped one
68✔
770

771
  local co = _autoclose_r[skt]
68✔
772
  if co then
68✔
773
    -- socket registered for autoclose, move registration to wrapped one
774
    _autoclose[co] = nskt
16✔
775
    _autoclose_r[skt] = nil
16✔
776
    _autoclose_r[nskt] = co
16✔
777
  end
778

779
  local sock_name = object_names[skt]
68✔
780
  if sock_name ~= tostring(skt) then
68✔
781
    -- socket had a custom name, so copy it over
782
    object_names[nskt] = sock_name
24✔
783
  end
784
  return nskt
68✔
785
end
786

787

788
-- For each luasec method we have a subtable, allows for future extension.
789
-- Required structure:
790
-- {
791
--   wrap = ... -- parameter to 'wrap()'; the ssl parameter table, or the context object
792
--   sni = {                  -- parameters to 'sni()'
793
--     names = string | table -- 1st parameter
794
--     strict = bool          -- 2nd parameter
795
--   }
796
-- }
797
local function normalize_sslt(sslt)
798
  local t = type(sslt)
216✔
799
  local r = setmetatable({}, {
432✔
800
    __index = function(self, key)
801
      -- a bug if this happens, here as a sanity check, just being careful since
802
      -- this is security stuff
803
      error("accessing unknown 'ssl_params' table key: "..tostring(key))
×
804
    end,
805
  })
806
  if t == "nil" then
216✔
807
    r.wrap = false
148✔
808
    r.sni = false
148✔
809

810
  elseif t == "table" then
68✔
811
    if sslt.mode or sslt.protocol then
68✔
812
      -- has the mandatory fields for the ssl-params table for handshake
813
      -- backward compatibility
814
      r.wrap = sslt
16✔
815
      r.sni = false
16✔
816
    else
817
      -- has the target definition, copy our known keys
818
      r.wrap = sslt.wrap or false -- 'or false' because we do not want nils
52✔
819
      r.sni = sslt.sni or false -- 'or false' because we do not want nils
52✔
820
    end
821

822
  elseif t == "userdata" then
×
823
    -- it's an ssl-context object for the handshake
824
    -- backward compatibility
825
    r.wrap = sslt
×
826
    r.sni = false
×
827

828
  else
829
    error("ssl parameters; did not expect type "..tostring(sslt))
×
830
  end
831

832
  return r
216✔
833
end
834

835

836
---
837
-- Peforms an (async) ssl handshake on a connected TCP client socket.
838
-- NOTE: if not ssl-wrapped already, then replace all previous socket references, with the returned new ssl wrapped socket
839
-- Throws error and does not return nil+error, as that might silently fail
840
-- in code like this;
841
--   copas.addserver(s1, function(skt)
842
--       skt = copas.wrap(skt, sparams)
843
--       skt:dohandshake()   --> without explicit error checking, this fails silently and
844
--       skt:send(body)      --> continues unencrypted
845
-- @param skt Regular LuaSocket CLIENT socket object
846
-- @param wrap_params Table with ssl parameters
847
-- @return wrapped ssl socket, or throws an error
848
function copas.dohandshake(skt, wrap_params)
122✔
849
  ssl = ssl or require("ssl")
68✔
850

851
  local nskt = ssl_wrap(skt, wrap_params)
68✔
852

853
  sto_timeout(nskt, "write", true)
68✔
854
  local queue
855

856
  repeat
857
    local success, err = nskt:dohandshake()
168✔
858

859
    if success then
168✔
860
      sto_timeout()
60✔
861
      return nskt
60✔
862

863
    elseif not _isSocketTimeout[err] then
108✔
864
      sto_timeout()
8✔
865
      error("TLS/SSL handshake failed: " .. tostring(err))
8✔
866

867
    elseif sto_timed_out() then
100✔
868
      return nil, sto_error(err)
×
869

870
    elseif err == "wantwrite" then
100✔
871
      sto_change_queue("write")
×
872
      queue = _writing
×
873

874
    elseif err == "wantread" then
100✔
875
      sto_change_queue("read")
100✔
876
      queue = _reading
100✔
877

878
    else
879
      error("TLS/SSL handshake failed: " .. tostring(err))
×
880
    end
881

882
    coroutine_yield(nskt, queue)
100✔
883
  until false
100✔
884
end
885

886
-- flushes a client write buffer (deprecated)
887
function copas.flush()
122✔
888
end
889

890
-- wraps a TCP socket to use Copas methods (send, receive, flush and settimeout)
891
local _skt_mt_tcp = {
122✔
892
      __tostring = function(self)
893
        return tostring(self.socket).." (copas wrapped)"
12✔
894
      end,
895

896
      __index = {
122✔
897
        send = function (self, data, from, to)
898
          return copas.send (self.socket, data, from, to)
9,832✔
899
        end,
900

901
        receive = function (self, pattern, prefix)
902
          if user_timeouts_receive[self.socket] == 0 then
1,055,787✔
903
            return copas.receivepartial(self.socket, pattern, prefix)
8✔
904
          end
905
          return copas.receive(self.socket, pattern, prefix)
1,055,779✔
906
        end,
907

908
        receivepartial = function (self, pattern, prefix)
909
          return copas.receivepartial(self.socket, pattern, prefix)
×
910
        end,
911

912
        flush = function (self)
913
          return copas.flush(self.socket)
×
914
        end,
915

916
        settimeout = function (self, time)
917
          return copas.settimeout(self.socket, time)
124✔
918
        end,
919

920
        settimeouts = function (self, connect, send, receive)
921
          return copas.settimeouts(self.socket, connect, send, receive)
×
922
        end,
923

924
        -- TODO: socket.connect is a shortcut, and must be provided with an alternative
925
        -- if ssl parameters are available, it will also include a handshake
926
        connect = function(self, ...)
927
          local res, err = copas.connect(self.socket, ...)
136✔
928
          if res then
136✔
929
            if self.ssl_params.sni then self:sni() end
124✔
930
            if self.ssl_params.wrap then res, err = self:dohandshake() end
124✔
931
          end
932
          return res, err
132✔
933
        end,
934

935
        close = function(self, ...)
936
          return copas.close(self.socket, ...)
140✔
937
        end,
938

939
        -- TODO: socket.bind is a shortcut, and must be provided with an alternative
940
        bind = function(self, ...) return self.socket:bind(...) end,
122✔
941

942
        -- TODO: is this DNS related? hence blocking?
943
        getsockname = function(self, ...)
944
          local ok, ip, port, family = pcall(self.socket.getsockname, self.socket, ...)
×
945
          if ok then
×
946
            return ip, port, family
×
947
          else
948
            return nil, "not implemented by LuaSec"
×
949
          end
950
        end,
951

952
        getstats = function(self, ...) return self.socket:getstats(...) end,
122✔
953

954
        setstats = function(self, ...) return self.socket:setstats(...) end,
122✔
955

956
        listen = function(self, ...) return self.socket:listen(...) end,
122✔
957

958
        accept = function(self, ...) return self.socket:accept(...) end,
122✔
959

960
        setoption = function(self, ...)
961
          local ok, res, err = pcall(self.socket.setoption, self.socket, ...)
×
962
          if ok then
×
963
            return res, err
×
964
          else
965
            return nil, "not implemented by LuaSec"
×
966
          end
967
        end,
968

969
        getoption = function(self, ...)
970
          local ok, val, err = pcall(self.socket.getoption, self.socket, ...)
×
971
          if ok then
×
972
            return val, err
×
973
          else
974
            return nil, "not implemented by LuaSec"
×
975
          end
976
        end,
977

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

988
        shutdown = function(self, ...) return self.socket:shutdown(...) end,
122✔
989

990
        sni = function(self, names, strict)
991
          local sslp = self.ssl_params
52✔
992
          self.socket = ssl_wrap(self.socket, sslp.wrap)
52✔
993
          if names == nil then
52✔
994
            names = sslp.sni.names
44✔
995
            strict = sslp.sni.strict
44✔
996
          end
997
          return self.socket:sni(names, strict)
52✔
998
        end,
999

1000
        dohandshake = function(self, wrap_params)
1001
          local nskt, err = copas.dohandshake(self.socket, wrap_params or self.ssl_params.wrap)
68✔
1002
          if not nskt then return nskt, err end
60✔
1003
          self.socket = nskt  -- replace internal socket with the newly wrapped ssl one
60✔
1004
          return self
60✔
1005
        end,
1006

1007
        getalpn = function(self, ...)
1008
          local ok, proto, err = pcall(self.socket.getalpn, self.socket, ...)
×
1009
          if ok then
×
1010
            return proto, err
×
1011
          else
1012
            return nil, "not a tls socket"
×
1013
          end
1014
        end,
1015

1016
        getsniname = function(self, ...)
1017
          local ok, name, err = pcall(self.socket.getsniname, self.socket, ...)
×
1018
          if ok then
×
1019
            return name, err
×
1020
          else
1021
            return nil, "not a tls socket"
×
1022
          end
1023
        end,
1024
      }
122✔
1025
}
1026

1027
-- wraps a UDP socket, copy of TCP one adapted for UDP.
1028
local _skt_mt_udp = {__index = { }}
122✔
1029
for k,v in pairs(_skt_mt_tcp) do _skt_mt_udp[k] = _skt_mt_udp[k] or v end
366✔
1030
for k,v in pairs(_skt_mt_tcp.__index) do _skt_mt_udp.__index[k] = v end
2,806✔
1031

1032
_skt_mt_udp.__index.send        = function(self, ...) return self.socket:send(...) end
126✔
1033

1034
_skt_mt_udp.__index.sendto      = function(self, ...) return self.socket:sendto(...) end
134✔
1035

1036

1037
_skt_mt_udp.__index.receive =     function (self, size)
122✔
1038
                                    return copas.receive (self.socket, (size or UDP_DATAGRAM_MAX))
8✔
1039
                                  end
1040

1041
_skt_mt_udp.__index.receivefrom = function (self, size)
122✔
1042
                                    return copas.receivefrom (self.socket, (size or UDP_DATAGRAM_MAX))
16✔
1043
                                  end
1044

1045
                                  -- TODO: is this DNS related? hence blocking?
1046
_skt_mt_udp.__index.setpeername = function(self, ...) return self.socket:setpeername(...) end
126✔
1047

1048
_skt_mt_udp.__index.setsockname = function(self, ...) return self.socket:setsockname(...) end
122✔
1049

1050
                                    -- do not close client, as it is also the server for udp.
1051
_skt_mt_udp.__index.close       = function(self, ...) return true end
130✔
1052

1053
_skt_mt_udp.__index.settimeouts = function (self, connect, send, receive)
122✔
1054
                                    return copas.settimeouts(self.socket, connect, send, receive)
×
1055
                                  end
1056

1057

1058

1059
---
1060
-- Wraps a LuaSocket socket object in an async Copas based socket object.
1061
-- @param skt The socket to wrap
1062
-- @sslt (optional) Table with ssl parameters, use an empty table to use ssl with defaults
1063
-- @return wrapped socket object
1064
function copas.wrap (skt, sslt)
122✔
1065
  if (getmetatable(skt) == _skt_mt_tcp) or (getmetatable(skt) == _skt_mt_udp) then
232✔
1066
    return skt -- already wrapped
×
1067
  end
1068

1069
  skt:settimeout(0)
232✔
1070

1071
  if isTCP(skt) then
232✔
1072
    return setmetatable ({socket = skt, ssl_params = normalize_sslt(sslt)}, _skt_mt_tcp)
216✔
1073
  else
1074
    return setmetatable ({socket = skt}, _skt_mt_udp)
16✔
1075
  end
1076
end
1077

1078
--- Wraps a handler in a function that deals with wrapping the socket and doing the
1079
-- optional ssl handshake.
1080
function copas.handler(handler, sslparams)
122✔
1081
  -- TODO: pass a timeout value to set, and use during handshake
1082
  return function (skt, ...)
1083
    skt = copas.wrap(skt, sslparams) -- this call will normalize the sslparams table
64✔
1084
    local sslp = skt.ssl_params
64✔
1085
    if sslp.sni then skt:sni(sslp.sni.names, sslp.sni.strict) end
64✔
1086
    if sslp.wrap then skt:dohandshake(sslp.wrap) end
64✔
1087
    return handler(skt, ...)
60✔
1088
  end
1089
end
1090

1091

1092
--------------------------------------------------
1093
-- Error handling
1094
--------------------------------------------------
1095

1096
local _errhandlers = setmetatable({}, { __mode = "k" })   -- error handler per coroutine
122✔
1097

1098

1099
function copas.gettraceback(msg, co, skt)
122✔
1100
  local co_str = co == nil and "nil" or copas.getthreadname(co)
26✔
1101
  local skt_str = skt == nil and "nil" or copas.getsocketname(skt)
26✔
1102
  local msg_str = msg == nil and "" or tostring(msg)
26✔
1103
  if msg_str == "" then
26✔
1104
    msg_str = ("(coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str)
×
1105
  else
1106
    msg_str = ("%s (coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str)
26✔
1107
  end
1108

1109
  if type(co) == "thread" then
26✔
1110
    -- regular Copas coroutine
1111
    return debug.traceback(co, msg_str)
26✔
1112
  end
1113
  -- not a coroutine, but the main thread, this happens if a timeout callback
1114
  -- (see `copas.timeout` causes an error (those callbacks run on the main thread).
1115
  return debug.traceback(msg_str, 2)
×
1116
end
1117

1118

1119
local function _deferror(msg, co, skt)
1120
  print(copas.gettraceback(msg, co, skt))
18✔
1121
end
1122

1123

1124
function copas.seterrorhandler(err, default)
122✔
1125
  assert(err == nil or type(err) == "function", "Expected the handler to be a function, or nil")
40✔
1126
  if default then
40✔
1127
    assert(err ~= nil, "Expected the handler to be a function when setting the default")
28✔
1128
    _deferror = err
28✔
1129
  else
1130
    _errhandlers[coroutine_running()] = err
12✔
1131
  end
1132
end
1133
copas.setErrorHandler = copas.seterrorhandler  -- deprecated; old casing
122✔
1134

1135

1136
function copas.geterrorhandler(co)
122✔
1137
  co = co or coroutine_running()
8✔
1138
  return _errhandlers[co] or _deferror
8✔
1139
end
1140

1141

1142
-- if `bool` is truthy, then the original socket errors will be returned in case of timeouts;
1143
-- `timeout, wantread, wantwrite, Operation already in progress`. If falsy, it will always
1144
-- return `timeout`.
1145
function copas.useSocketTimeoutErrors(bool)
122✔
1146
  useSocketTimeoutErrors[coroutine_running()] = not not bool -- force to a boolean
4✔
1147
end
1148

1149
-------------------------------------------------------------------------------
1150
-- Thread handling
1151
-------------------------------------------------------------------------------
1152

1153
local function _doTick (co, skt, ...)
1154
  if not co then return end
124,668✔
1155

1156
  -- if a coroutine was canceled/removed, don't resume it
1157
  if _canceled[co] then
124,668✔
1158
    _canceled[co] = nil -- also clean up the registry
8✔
1159
    _threads[co] = nil
8✔
1160
    return
8✔
1161
  end
1162

1163
  -- res: the socket (being read/write on) or the time to sleep
1164
  -- new_q: either _writing, _reading, or _sleeping
1165
  -- local time_before = gettime()
1166
  local ok, res, new_q = coroutine_resume(co, skt, ...)
124,660✔
1167
  -- local duration = gettime() - time_before
1168
  -- if duration > 1 then
1169
  --   duration = math.floor(duration * 1000)
1170
  --   pcall(_errhandlers[co] or _deferror, "task ran for "..tostring(duration).." milliseconds.", co, skt)
1171
  -- end
1172

1173
  if new_q == _reading or new_q == _writing or new_q == _sleeping then
124,656✔
1174
    -- we're yielding to a new queue
1175
    new_q:insert (res)
121,250✔
1176
    new_q:push (res, co)
121,250✔
1177
    return
121,250✔
1178
  end
1179

1180
  -- coroutine is terminating
1181

1182
  if ok and coroutine_status(co) ~= "dead" then
3,406✔
1183
    -- it called coroutine.yield from a non-Copas function which is unexpected
1184
    ok = false
4✔
1185
    res = "coroutine.yield was called without a resume first, user-code cannot yield to Copas"
4✔
1186
  end
1187

1188
  if not ok then
3,406✔
1189
    local k, e = pcall(_errhandlers[co] or _deferror, res, co, skt)
31✔
1190
    if not k then
31✔
1191
      print("Failed executing error handler: " .. tostring(e))
×
1192
    end
1193
  end
1194

1195
  local skt_to_close = _autoclose[co]
3,406✔
1196
  if skt_to_close then
3,406✔
1197
    skt_to_close:close()
76✔
1198
    _autoclose[co] = nil
76✔
1199
    _autoclose_r[skt_to_close] = nil
76✔
1200
  end
1201

1202
  _errhandlers[co] = nil
3,406✔
1203
end
1204

1205

1206
local _accept do
122✔
1207
  local client_counters = setmetatable({}, { __mode = "k" })
122✔
1208

1209
  -- accepts a connection on socket input
1210
  function _accept(server_skt, handler)
93✔
1211
    local client_skt = server_skt:accept()
80✔
1212
    if client_skt then
80✔
1213
      local count = (client_counters[server_skt] or 0) + 1
80✔
1214
      client_counters[server_skt] = count
80✔
1215
      object_names[client_skt] = object_names[server_skt] .. ":client_" .. count
80✔
1216

1217
      client_skt:settimeout(0)
80✔
1218
      copas.settimeouts(client_skt, user_timeouts_connect[server_skt],  -- copy server socket timeout settings
160✔
1219
        user_timeouts_send[server_skt], user_timeouts_receive[server_skt])
80✔
1220

1221
      local co = coroutine_create(handler)
80✔
1222
      object_names[co] = object_names[server_skt] .. ":handler_" .. count
80✔
1223

1224
      if copas.autoclose then
80✔
1225
        _autoclose[co] = client_skt
80✔
1226
        _autoclose_r[client_skt] = co
80✔
1227
      end
1228

1229
      _doTick(co, client_skt)
80✔
1230
    end
1231
  end
1232
end
1233

1234
-------------------------------------------------------------------------------
1235
-- Adds a server/handler pair to Copas dispatcher
1236
-------------------------------------------------------------------------------
1237

1238
do
1239
  local function addTCPserver(server, handler, timeout, name)
1240
    server:settimeout(0)
60✔
1241
    if name then
60✔
1242
      object_names[server] = name
×
1243
    end
1244
    _servers[server] = handler
60✔
1245
    _reading:insert(server)
60✔
1246
    if timeout then
60✔
1247
      copas.settimeout(server, timeout)
12✔
1248
    end
1249
  end
1250

1251
  local function addUDPserver(server, handler, timeout, name)
1252
    server:settimeout(0)
×
1253
    local co = coroutine_create(handler)
×
1254
    if name then
×
1255
      object_names[server] = name
×
1256
    end
1257
    object_names[co] = object_names[server]..":handler"
×
1258
    _reading:insert(server)
×
1259
    if timeout then
×
1260
      copas.settimeout(server, timeout)
×
1261
    end
1262
    _doTick(co, server)
×
1263
  end
1264

1265

1266
  function copas.addserver(server, handler, timeout, name)
122✔
1267
    if isTCP(server) then
60✔
1268
      addTCPserver(server, handler, timeout, name)
60✔
1269
    else
1270
      addUDPserver(server, handler, timeout, name)
×
1271
    end
1272
  end
1273
end
1274

1275

1276
function copas.removeserver(server, keep_open)
122✔
1277
  local skt = server
56✔
1278
  local mt = getmetatable(server)
56✔
1279
  if mt == _skt_mt_tcp or mt == _skt_mt_udp then
56✔
1280
    skt = server.socket
×
1281
  end
1282

1283
  _servers:remove(skt)
56✔
1284
  _reading:remove(skt)
56✔
1285

1286
  if keep_open then
56✔
1287
    return true
12✔
1288
  end
1289
  return server:close()
44✔
1290
end
1291

1292

1293

1294
-------------------------------------------------------------------------------
1295
-- Adds an new coroutine thread to Copas dispatcher
1296
-------------------------------------------------------------------------------
1297
function copas.addnamedthread(name, handler, ...)
122✔
1298
  if type(name) == "function" and type(handler) == "string" then
3,484✔
1299
    -- old call, flip args for compatibility
1300
    name, handler = handler, name
×
1301
  end
1302

1303
  -- create a coroutine that skips the first argument, which is always the socket
1304
  -- passed by the scheduler, but `nil` in case of a task/thread
1305
  local thread = coroutine_create(function(_, ...)
6,968✔
1306
    copas.pause()
3,484✔
1307
    return handler(...)
3,480✔
1308
  end)
1309
  if name then
3,484✔
1310
    object_names[thread] = name
296✔
1311
  end
1312

1313
  _threads[thread] = true -- register this thread so it can be removed
3,484✔
1314
  _doTick (thread, nil, ...)
3,484✔
1315
  return thread
3,484✔
1316
end
1317

1318

1319
function copas.addthread(handler, ...)
122✔
1320
  return copas.addnamedthread(nil, handler, ...)
3,188✔
1321
end
1322

1323

1324
function copas.removethread(thread)
122✔
1325
  -- if the specified coroutine is registered, add it to the canceled table so
1326
  -- that next time it tries to resume it exits.
1327
  _canceled[thread] = _threads[thread or 0]
28✔
1328
  _sleeping:cancel(thread)
28✔
1329
end
1330

1331

1332

1333
-------------------------------------------------------------------------------
1334
-- Sleep/pause management functions
1335
-------------------------------------------------------------------------------
1336

1337
-- yields the current coroutine and wakes it after 'sleeptime' seconds.
1338
-- If sleeptime < 0 then it sleeps until explicitly woken up using 'wakeup'
1339
-- TODO: deprecated, remove in next major
1340
function copas.sleep(sleeptime)
122✔
1341
  coroutine_yield((sleeptime or 0), _sleeping)
×
1342
end
1343

1344

1345
-- yields the current coroutine and wakes it after 'sleeptime' seconds.
1346
-- if sleeptime < 0 then it sleeps 0 seconds.
1347
function copas.pause(sleeptime)
122✔
1348
  local s = gettime()
118,506✔
1349
  if sleeptime and sleeptime > 0 then
118,506✔
1350
    coroutine_yield(sleeptime, _sleeping)
4,780✔
1351
  else
1352
    coroutine_yield(0, _sleeping)
113,726✔
1353
  end
1354
  return gettime() - s
118,364✔
1355
end
1356

1357

1358
-- yields the current coroutine until explicitly woken up using 'wakeup'
1359
function copas.pauseforever()
122✔
1360
  local s = gettime()
2,092✔
1361
  coroutine_yield(-1, _sleeping)
2,092✔
1362
  return gettime() - s
2,080✔
1363
end
1364

1365

1366
-- Wakes up a sleeping coroutine 'co'.
1367
function copas.wakeup(co)
122✔
1368
  _sleeping:wakeup(co)
2,100✔
1369
end
1370

1371

1372

1373
-------------------------------------------------------------------------------
1374
-- Timeout management
1375
-------------------------------------------------------------------------------
1376

1377
do
1378
  local timeout_register = setmetatable({}, { __mode = "k" })
122✔
1379
  local time_out_thread
1380
  local timerwheel = require("timerwheel").new({
244✔
1381
      now = gettime,
122✔
1382
      precision = TIMEOUT_PRECISION,
122✔
1383
      ringsize = math.floor(60*60*24/TIMEOUT_PRECISION),  -- ring size 1 day
122✔
1384
      err_handler = function(err)
1385
        return _deferror(err, time_out_thread)
11✔
1386
      end,
1387
    })
1388

1389
  time_out_thread = copas.addnamedthread("copas_core_timer", function()
244✔
1390
    while true do
1391
      copas.pause(TIMEOUT_PRECISION)
4,196✔
1392
      timerwheel:step()
4,078✔
1393
    end
1394
  end)
1395

1396
  -- get the number of timeouts running
1397
  function copas.gettimeouts()
122✔
1398
    return timerwheel:count()
1,731✔
1399
  end
1400

1401
  --- Sets the timeout for the current coroutine.
1402
  -- @param delay delay (seconds), use 0 (or math.huge) to cancel the timerout
1403
  -- @param callback function with signature: `function(coroutine)` where coroutine is the routine that timed-out
1404
  -- @return true
1405
  function copas.timeout(delay, callback)
122✔
1406
    local co = coroutine_running()
2,134,817✔
1407
    local existing_timer = timeout_register[co]
2,134,817✔
1408

1409
    if existing_timer then
2,134,817✔
1410
      timerwheel:cancel(existing_timer)
2,733✔
1411
    end
1412

1413
    if delay > 0 and delay ~= math.huge then
2,134,817✔
1414
      timeout_register[co] = timerwheel:set(delay, callback, co)
3,784✔
1415
    elseif delay == 0 or delay == math.huge then
2,131,033✔
1416
      timeout_register[co] = nil
2,131,033✔
1417
    else
1418
      error("timout value must be greater than or equal to 0, got: "..tostring(delay))
×
1419
    end
1420

1421
    return true
2,134,817✔
1422
  end
1423

1424
end
1425

1426

1427
-------------------------------------------------------------------------------
1428
-- main tasks: manage readable and writable socket sets
1429
-------------------------------------------------------------------------------
1430
-- a task is an object with a required method `step()` that deals with a
1431
-- single step for that task.
1432

1433
local _tasks = {} do
122✔
1434
  function _tasks:add(tsk)
122✔
1435
    _tasks[#_tasks + 1] = tsk
488✔
1436
  end
1437
end
1438

1439

1440
-- a task to check ready to read events
1441
local _readable_task = {} do
122✔
1442

1443
  _readable_task._events = {}
122✔
1444

1445
  local function tick(skt)
1446
    local handler = _servers[skt]
495✔
1447
    if handler then
495✔
1448
      _accept(skt, handler)
80✔
1449
    else
1450
      _reading:remove(skt)
415✔
1451
      _doTick(_reading:pop(skt), skt)
415✔
1452
    end
1453
  end
1454

1455
  function _readable_task:step()
122✔
1456
    for _, skt in ipairs(self._events) do
114,831✔
1457
      tick(skt)
495✔
1458
    end
1459
  end
1460

1461
  _tasks:add(_readable_task)
122✔
1462
end
1463

1464

1465
-- a task to check ready to write events
1466
local _writable_task = {} do
122✔
1467

1468
  _writable_task._events = {}
122✔
1469

1470
  local function tick(skt)
1471
    _writing:remove(skt)
189✔
1472
    _doTick(_writing:pop(skt), skt)
189✔
1473
  end
1474

1475
  function _writable_task:step()
122✔
1476
    for _, skt in ipairs(self._events) do
114,524✔
1477
      tick(skt)
189✔
1478
    end
1479
  end
1480

1481
  _tasks:add(_writable_task)
122✔
1482
end
1483

1484

1485

1486
-- sleeping threads task
1487
local _sleeping_task = {} do
122✔
1488

1489
  function _sleeping_task:step()
122✔
1490
    local now = gettime()
114,335✔
1491

1492
    local co = _sleeping:pop(now)
114,335✔
1493
    while co do
118,980✔
1494
      -- we're pushing them to _resumable, since that list will be replaced before
1495
      -- executing. This prevents tasks running twice in a row with pause(0) for example.
1496
      -- So here we won't execute, but at _resumable step which is next
1497
      _resumable:push(co)
4,645✔
1498
      co = _sleeping:pop(now)
4,645✔
1499
    end
1500
  end
1501

1502
  _tasks:add(_sleeping_task)
122✔
1503
end
1504

1505

1506

1507
-- resumable threads task
1508
local _resumable_task = {} do
122✔
1509

1510
  function _resumable_task:step()
122✔
1511
    -- replace the resume list before iterating, so items placed in there
1512
    -- will indeed end up in the next copas step, not in this one, and not
1513
    -- create a loop
1514
    local resumelist = _resumable:clear_resumelist()
114,335✔
1515

1516
    for _, co in ipairs(resumelist) do
234,832✔
1517
      _doTick(co)
120,500✔
1518
    end
1519
  end
1520

1521
  _tasks:add(_resumable_task)
122✔
1522
end
1523

1524

1525
-------------------------------------------------------------------------------
1526
-- Checks for reads and writes on sockets
1527
-------------------------------------------------------------------------------
1528
local _select_plain do
122✔
1529

1530
  local last_cleansing = 0
122✔
1531
  local duration = function(t2, t1) return t2-t1 end
114,398✔
1532

1533
  if not socket then
122✔
1534
    -- socket module unavailable, switch to luasystem sleep
1535
    _select_plain = block_sleep
4✔
1536
  else
1537
    -- use socket.select to handle socket-io
1538
    _select_plain = function(timeout)
1539
      local err
1540
      local now = gettime()
114,276✔
1541

1542
      -- remove any closed sockets to prevent select from hanging on them
1543
      if _closed[1] then
114,276✔
1544
        for i, skt in ipairs(_closed) do
280✔
1545
          _closed[i] = { _reading:remove(skt), _writing:remove(skt) }
140✔
1546
        end
1547
      end
1548

1549
      _readable_task._events, _writable_task._events, err = socket.select(_reading, _writing, timeout)
114,276✔
1550
      local r_events, w_events = _readable_task._events, _writable_task._events
114,276✔
1551

1552
      -- inject closed sockets in readable/writeable task so they can error out properly
1553
      if _closed[1] then
114,276✔
1554
        for i, skts in ipairs(_closed) do
280✔
1555
          _closed[i] = nil
140✔
1556
          r_events[#r_events+1] = skts[1]
140✔
1557
          w_events[#w_events+1] = skts[2]
140✔
1558
        end
1559
      end
1560

1561
      if duration(now, last_cleansing) > WATCH_DOG_TIMEOUT then
114,276✔
1562
        last_cleansing = now
114✔
1563

1564
        -- Check all sockets selected for reading, and check how long they have been waiting
1565
        -- for data already, without select returning them as readable
1566
        for skt,time in pairs(_reading_log) do
114✔
1567
          if not r_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
×
1568
            -- This one timedout while waiting to become readable, so move
1569
            -- it in the readable list and try and read anyway, despite not
1570
            -- having been returned by select
1571
            _reading_log[skt] = nil
×
1572
            r_events[#r_events + 1] = skt
×
1573
            r_events[skt] = #r_events
×
1574
          end
1575
        end
1576

1577
        -- Do the same for writing
1578
        for skt,time in pairs(_writing_log) do
114✔
1579
          if not w_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
×
1580
            _writing_log[skt] = nil
×
1581
            w_events[#w_events + 1] = skt
×
1582
            w_events[skt] = #w_events
×
1583
          end
1584
        end
1585
      end
1586

1587
      if err == "timeout" and #r_events + #w_events > 0 then
114,276✔
1588
        return nil
4✔
1589
      else
1590
        return err
114,272✔
1591
      end
1592
    end
1593
  end
1594
end
1595

1596

1597

1598
-------------------------------------------------------------------------------
1599
-- Dispatcher loop step.
1600
-- Listen to client requests and handles them
1601
-- Returns false if no socket-data was handled, or true if there was data
1602
-- handled (or nil + error message)
1603
-------------------------------------------------------------------------------
1604

1605
local copas_stats
1606
local min_ever, max_ever
1607

1608
local _select = _select_plain
122✔
1609

1610
-- instrumented version of _select() to collect stats
1611
local _select_instrumented = function(timeout)
1612
  if copas_stats then
×
1613
    local step_duration = gettime() - copas_stats.step_start
×
1614
    copas_stats.duration_max = math.max(copas_stats.duration_max, step_duration)
×
1615
    copas_stats.duration_min = math.min(copas_stats.duration_min, step_duration)
×
1616
    copas_stats.duration_tot = copas_stats.duration_tot + step_duration
×
1617
    copas_stats.steps = copas_stats.steps + 1
×
1618
  else
1619
    copas_stats = {
×
1620
      duration_max = -1,
1621
      duration_min = 999999,
1622
      duration_tot = 0,
1623
      steps = 0,
1624
    }
1625
  end
1626

1627
  local err = _select_plain(timeout)
×
1628

1629
  local now = gettime()
×
1630
  copas_stats.time_start = copas_stats.time_start or now
×
1631
  copas_stats.step_start = now
×
1632

1633
  return err
×
1634
end
1635

1636

1637
function copas.step(timeout)
122✔
1638
  -- Need to wake up the select call in time for the next sleeping event
1639
  if not _resumable:done() then
114,336✔
1640
    timeout = 0
110,487✔
1641
  else
1642
    timeout = math.min(_sleeping:getnext(), timeout or math.huge)
3,849✔
1643
  end
1644

1645
  local err = _select(timeout)
114,336✔
1646

1647
  for _, tsk in ipairs(_tasks) do
571,674✔
1648
    tsk:step()
457,342✔
1649
  end
1650

1651
  if err then
114,332✔
1652
    if err == "timeout" then
113,696✔
1653
      if timeout + 0.01 > TIMEOUT_PRECISION and math.random(100) > 90 then
113,636✔
1654
        -- we were idle, so occasionally do a GC sweep to ensure lingering
1655
        -- sockets are closed, and we don't accidentally block the loop from
1656
        -- exiting
1657
        collectgarbage()
296✔
1658
      end
1659
      return false
113,636✔
1660
    end
1661
    return nil, err
60✔
1662
  end
1663

1664
  return true
636✔
1665
end
1666

1667

1668
-------------------------------------------------------------------------------
1669
-- Check whether there is something to do.
1670
-- returns false if there are no sockets for read/write nor tasks scheduled
1671
-- (which means Copas is in an empty spin)
1672
-------------------------------------------------------------------------------
1673
function copas.finished()
122✔
1674
  return #_reading == 0 and #_writing == 0 and _resumable:done() and _sleeping:done(copas.gettimeouts())
114,332✔
1675
end
1676

1677

1678
local resetexit do
122✔
1679
  local exit_semaphore, exiting
1680

1681
  function resetexit()
93✔
1682
    exit_semaphore = copas.semaphore.new(1, 0, math.huge)
198✔
1683
    exiting = false
198✔
1684
  end
1685

1686
  -- Signals tasks to exit. But only if they check for it. By calling `copas.exiting`
1687
  -- they can check if they should exit. Or by calling `copas.waitforexit` they can
1688
  -- wait until the exit signal is given.
1689
  function copas.exit()
122✔
1690
    if exiting then return end
194✔
1691
    exiting = true
194✔
1692
    exit_semaphore:destroy()
194✔
1693
  end
1694

1695
  -- returns whether Copas is in the process of exiting. Exit can be started by
1696
  -- calling `copas.exit()`.
1697
  function copas.exiting()
122✔
1698
    return exiting
384✔
1699
  end
1700

1701
  -- Pauses the current coroutine until Copas is exiting. To be used as an exit
1702
  -- signal for tasks that need to clean up before exiting.
1703
  function copas.waitforexit()
122✔
1704
    exit_semaphore:take(1)
8✔
1705
  end
1706
end
1707

1708

1709
local _getstats do
122✔
1710
  local _getstats_instrumented, _getstats_plain
1711

1712

1713
  function _getstats_plain(enable)
93✔
1714
    -- this function gets hit if turned off, so turn on if true
1715
    if enable == true then
×
1716
      _select = _select_instrumented
×
1717
      _getstats = _getstats_instrumented
×
1718
      -- reset stats
1719
      min_ever = nil
×
1720
      max_ever = nil
×
1721
      copas_stats = nil
×
1722
    end
1723
    return {}
×
1724
  end
1725

1726

1727
  -- convert from seconds to millisecs, with microsec precision
1728
  local function useconds(t)
1729
    return math.floor((t * 1000000) + 0.5) / 1000
×
1730
  end
1731
  -- convert from seconds to seconds, with millisec precision
1732
  local function mseconds(t)
1733
    return math.floor((t * 1000) + 0.5) / 1000
×
1734
  end
1735

1736

1737
  function _getstats_instrumented(enable)
93✔
1738
    if enable == false then
×
1739
      _select = _select_plain
×
1740
      _getstats = _getstats_plain
×
1741
      -- instrumentation disabled, so switch to the plain implementation
1742
      return _getstats(enable)
×
1743
    end
1744
    if (not copas_stats) or (copas_stats.step == 0) then
×
1745
      return {}
×
1746
    end
1747
    local stats = copas_stats
×
1748
    copas_stats = nil
×
1749
    min_ever = math.min(min_ever or 9999999, stats.duration_min)
×
1750
    max_ever = math.max(max_ever or 0, stats.duration_max)
×
1751
    stats.duration_min_ever = min_ever
×
1752
    stats.duration_max_ever = max_ever
×
1753
    stats.duration_avg = stats.duration_tot / stats.steps
×
1754
    stats.step_start = nil
×
1755
    stats.time_end = gettime()
×
1756
    stats.time_tot = stats.time_end - stats.time_start
×
1757
    stats.time_avg = stats.time_tot / stats.steps
×
1758

1759
    stats.duration_avg = useconds(stats.duration_avg)
×
1760
    stats.duration_max = useconds(stats.duration_max)
×
1761
    stats.duration_max_ever = useconds(stats.duration_max_ever)
×
1762
    stats.duration_min = useconds(stats.duration_min)
×
1763
    stats.duration_min_ever = useconds(stats.duration_min_ever)
×
1764
    stats.duration_tot = useconds(stats.duration_tot)
×
1765
    stats.time_avg = useconds(stats.time_avg)
×
1766
    stats.time_start = mseconds(stats.time_start)
×
1767
    stats.time_end = mseconds(stats.time_end)
×
1768
    stats.time_tot = mseconds(stats.time_tot)
×
1769
    return stats
×
1770
  end
1771

1772
  _getstats = _getstats_plain
122✔
1773
end
1774

1775

1776
function copas.status(enable_stats)
122✔
1777
  local res = _getstats(enable_stats)
×
1778
  res.running = not not copas.running
×
1779
  res.timeout = copas.gettimeouts()
×
1780
  res.timer, res.inactive = _sleeping:status()
×
1781
  res.read = #_reading
×
1782
  res.write = #_writing
×
1783
  res.active = _resumable:count()
×
1784
  return res
×
1785
end
1786

1787

1788
-------------------------------------------------------------------------------
1789
-- Dispatcher endless loop.
1790
-- Listen to client requests and handles them forever
1791
-------------------------------------------------------------------------------
1792
function copas.loop(initializer, timeout)
122✔
1793
  if type(initializer) == "function" then
198✔
1794
    copas.addnamedthread("copas_initializer", initializer)
78✔
1795
  else
1796
    timeout = initializer or timeout
120✔
1797
  end
1798

1799
  resetexit()
198✔
1800
  copas.running = true
198✔
1801
  while true do
1802
    copas.step(timeout)
114,336✔
1803
    if copas.finished() then
114,332✔
1804
      if copas.exiting() then
384✔
1805
        break
47✔
1806
      end
1807
      copas.exit()
190✔
1808
    end
1809
  end
1810
  copas.running = false
194✔
1811
end
1812

1813

1814
-------------------------------------------------------------------------------
1815
-- Naming sockets and coroutines.
1816
-------------------------------------------------------------------------------
1817
do
1818
  local function realsocket(skt)
1819
    local mt = getmetatable(skt)
60✔
1820
    if mt == _skt_mt_tcp or mt == _skt_mt_udp then
60✔
1821
      return skt.socket
60✔
1822
    else
1823
      return skt
×
1824
    end
1825
  end
1826

1827

1828
  function copas.setsocketname(name, skt)
122✔
1829
    assert(type(name) == "string", "expected arg #1 to be a string")
60✔
1830
    skt = assert(realsocket(skt), "expected arg #2 to be a socket")
60✔
1831
    object_names[skt] = name
60✔
1832
  end
1833

1834

1835
  function copas.getsocketname(skt)
122✔
1836
    skt = assert(realsocket(skt), "expected arg #1 to be a socket")
×
1837
    return object_names[skt]
×
1838
  end
1839
end
1840

1841

1842
function copas.setthreadname(name, coro)
122✔
1843
  assert(type(name) == "string", "expected arg #1 to be a string")
40✔
1844
  coro = coro or coroutine_running()
40✔
1845
  assert(type(coro) == "thread", "expected arg #2 to be a coroutine or nil")
40✔
1846
  object_names[coro] = name
40✔
1847
end
1848

1849

1850
function copas.getthreadname(coro)
122✔
1851
  coro = coro or coroutine_running()
26✔
1852
  assert(type(coro) == "thread", "expected arg #1 to be a coroutine or nil")
26✔
1853
  return object_names[coro]
26✔
1854
end
1855

1856
-------------------------------------------------------------------------------
1857
-- Debug functionality.
1858
-------------------------------------------------------------------------------
1859
do
1860
  copas.debug = {}
122✔
1861

1862
  local log_core    -- if truthy, the core-timer will also be logged
1863
  local debug_log   -- function used as logger
1864

1865

1866
  local debug_yield = function(skt, queue)
1867
    local name = object_names[coroutine_running()]
1,838✔
1868

1869
    if log_core or name ~= "copas_core_timer" then
1,838✔
1870
      if queue == _sleeping then
1,826✔
1871
        debug_log("yielding '", name, "' to SLEEP for ", skt," seconds")
1,808✔
1872

1873
      elseif queue == _writing then
18✔
1874
        debug_log("yielding '", name, "' to WRITE on '", object_names[skt], "'")
4✔
1875

1876
      elseif queue == _reading then
14✔
1877
        debug_log("yielding '", name, "' to READ on '", object_names[skt], "'")
14✔
1878

1879
      else
1880
        debug_log("thread '", name, "' yielding to unexpected queue; ", tostring(queue), " (", type(queue), ")", debug.traceback())
×
1881
      end
1882
    end
1883

1884
    return coroutine.yield(skt, queue)
1,838✔
1885
  end
1886

1887

1888
  local debug_resume = function(coro, skt, ...)
1889
    local name = object_names[coro]
1,846✔
1890

1891
    if skt then
1,846✔
1892
      debug_log("resuming '", name, "' for socket '", object_names[skt], "'")
18✔
1893
    else
1894
      if log_core or name ~= "copas_core_timer" then
1,828✔
1895
        debug_log("resuming '", name, "'")
1,816✔
1896
      end
1897
    end
1898
    return coroutine.resume(coro, skt, ...)
1,846✔
1899
  end
1900

1901

1902
  local debug_create = function(f)
1903
    local f_wrapped = function(...)
1904
      local results = pack(f(...))
8✔
1905
      debug_log("exiting '", object_names[coroutine_running()], "'")
8✔
1906
      return unpack(results)
8✔
1907
    end
1908

1909
    return coroutine.create(f_wrapped)
8✔
1910
  end
1911

1912

1913
  debug_log = fnil
122✔
1914

1915

1916
  -- enables debug output for all coroutine operations.
1917
  function copas.debug.start(logger, core)
244✔
1918
    log_core = core
4✔
1919
    debug_log = logger or print
4✔
1920
    coroutine_yield = debug_yield
4✔
1921
    coroutine_resume = debug_resume
4✔
1922
    coroutine_create = debug_create
4✔
1923
  end
1924

1925

1926
  -- disables debug output for coroutine operations.
1927
  function copas.debug.stop()
244✔
1928
    debug_log = fnil
×
1929
    coroutine_yield = coroutine.yield
×
1930
    coroutine_resume = coroutine.resume
×
1931
    coroutine_create = coroutine.create
×
1932
  end
1933

1934
  do
1935
    local call_id = 0
122✔
1936

1937
    -- Description table of socket functions for debug output.
1938
    -- each socket function name has TWO entries;
1939
    -- 'name_in' and 'name_out', each being an array of names/descriptions of respectively
1940
    -- input parameters and return values.
1941
    -- If either table has a 'callback' key, then that is a function that will be called
1942
    -- with the parameters/return-values for further inspection.
1943
    local args = {
122✔
1944
      settimeout_in = {
122✔
1945
        "socket ",
122✔
1946
        "seconds",
122✔
1947
        "mode   ",
1948
      },
122✔
1949
      settimeout_out = {
122✔
1950
        "success",
122✔
1951
        "error  ",
1952
      },
122✔
1953
      connect_in = {
122✔
1954
        "socket ",
122✔
1955
        "address",
122✔
1956
        "port   ",
1957
      },
122✔
1958
      connect_out = {
122✔
1959
        "success",
122✔
1960
        "error  ",
1961
      },
122✔
1962
      getfd_in = {
122✔
1963
        "socket ",
1964
        -- callback = function(...)
1965
        --   print(debug.traceback("called from:", 4))
1966
        -- end,
1967
      },
122✔
1968
      getfd_out = {
122✔
1969
        "fd",
1970
      },
122✔
1971
      send_in = {
122✔
1972
        "socket   ",
122✔
1973
        "data     ",
122✔
1974
        "idx-start",
122✔
1975
        "idx-end  ",
1976
      },
122✔
1977
      send_out = {
122✔
1978
        "last-idx-send    ",
122✔
1979
        "error            ",
122✔
1980
        "err-last-idx-send",
1981
      },
122✔
1982
      receive_in = {
122✔
1983
        "socket ",
122✔
1984
        "pattern",
122✔
1985
        "prefix ",
1986
      },
122✔
1987
      receive_out = {
122✔
1988
        "received    ",
122✔
1989
        "error       ",
122✔
1990
        "partial data",
1991
      },
122✔
1992
      dirty_in = {
122✔
1993
        "socket",
1994
        -- callback = function(...)
1995
        --   print(debug.traceback("called from:", 4))
1996
        -- end,
1997
      },
122✔
1998
      dirty_out = {
122✔
1999
        "data in read-buffer",
2000
      },
122✔
2001
      close_in = {
122✔
2002
        "socket",
2003
        -- callback = function(...)
2004
        --   print(debug.traceback("called from:", 4))
2005
        -- end,
2006
      },
122✔
2007
      close_out = {
122✔
2008
        "success",
122✔
2009
        "error",
2010
      },
122✔
2011
    }
2012
    local function print_call(func, msg, ...)
2013
      print(msg)
186✔
2014
      local arg = pack(...)
186✔
2015
      local desc = args[func] or {}
186✔
2016
      for i = 1, math.max(arg.n, #desc) do
420✔
2017
        local value = arg[i]
234✔
2018
        if type(value) == "string" then
234✔
2019
          local xvalue = value:sub(1,30)
12✔
2020
          if xvalue ~= value then
12✔
2021
            xvalue = xvalue .."(...truncated)"
×
2022
          end
2023
          print("\t"..(desc[i] or i)..": '"..tostring(xvalue).."' ("..type(value).." #"..#value..")")
12✔
2024
        else
2025
          print("\t"..(desc[i] or i)..": '"..tostring(value).."' ("..type(value)..")")
222✔
2026
        end
2027
      end
2028
      if desc.callback then
186✔
2029
        desc.callback(...)
×
2030
      end
2031
    end
2032

2033
    local debug_mt = {
122✔
2034
      __index = function(self, key)
2035
        local value = self.__original_socket[key]
93✔
2036
        if type(value) ~= "function" then
93✔
2037
          return value
×
2038
        end
2039
        return function(self2, ...)
2040
            local my_id = call_id + 1
93✔
2041
            call_id = my_id
93✔
2042
            local results
2043

2044
            if self2 ~= self then
93✔
2045
              -- there is no self
2046
              print_call(tostring(key).."_in", my_id .. "-calling '"..tostring(key) .. "' with; ", self, ...)
×
2047
              results = pack(value(self, ...))
×
2048
            else
2049
              print_call(tostring(key).."_in", my_id .. "-calling '" .. tostring(key) .. "' with; ", self.__original_socket, ...)
93✔
2050
              results = pack(value(self.__original_socket, ...))
93✔
2051
            end
2052
            print_call(tostring(key).."_out", my_id .. "-results '"..tostring(key) .. "' returned; ", unpack(results))
93✔
2053
            return unpack(results)
93✔
2054
          end
2055
      end,
2056
      __tostring = function(self)
2057
        return tostring(self.__original_socket)
16✔
2058
      end
2059
    }
2060

2061

2062
    -- wraps a socket (copas or luasocket) in a debug version printing all calls
2063
    -- and their parameters/return values. Extremely noisy!
2064
    -- returns the wrapped socket.
2065
    -- NOTE: only for plain sockets, will not support TLS
2066
    function copas.debug.socket(original_skt)
244✔
2067
      if (getmetatable(original_skt) == _skt_mt_tcp) or (getmetatable(original_skt) == _skt_mt_udp) then
4✔
2068
        -- already wrapped as Copas socket, so recurse with the original luasocket one
2069
        original_skt.socket = copas.debug.socket(original_skt.socket)
×
2070
        return original_skt
×
2071
      end
2072

2073
      local proxy = setmetatable({
8✔
2074
        __original_socket = original_skt
4✔
2075
      }, debug_mt)
4✔
2076

2077
      return proxy
4✔
2078
    end
2079
  end
2080
end
2081

2082

2083
return copas
122✔
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