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

lunarmodules / copas / 30401607642

28 Jul 2026 09:40PM UTC coverage: 85.624% (-0.1%) from 85.764%
30401607642

push

github

Tieske
fix(http): do not send literal IP addresses as the TLS SNI name

RFC 6066 forbids IP literals in the SNI extension, but the SNI
fallback used u.host unconditionally, so connecting (directly or via
redirect) to a bare IPv4/IPv6 address sent that address as the SNI
name instead of a real hostname.

Added ishostliteral() to detect IPv4/IPv6 literals and skip the
fallback for them. When no usable name is available, ssl_params.sni
is collapsed to false so copas.lua's connect() omits the SNI
extension entirely rather than send a bogus name.

5 of 8 new or added lines in 1 file covered. (62.5%)

24 existing lines in 6 files now uncovered.

1495 of 1746 relevant lines covered (85.62%)

55712.12 hits per line

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

94.68
/src/copas/lock.lua
1
local copas = require("copas")
12✔
2
local gettime = copas.gettime
12✔
3

4
local coroutine_running = coroutine.running
12✔
5
if _VERSION=="Lua 5.1" and not jit then     -- obsolete: only for Lua 5.1 compatibility
12✔
UNCOV
6
  coroutine_running = require("coxpcall").running
2✔
7
end
8

9
local DEFAULT_TIMEOUT = 10
12✔
10

11
local lock = {}
12✔
12
lock.__index = lock
12✔
13

14

15
-- registry, locks indexed by the coroutines using them.
16
local registry = setmetatable({}, { __mode="kv" })
12✔
17

18

19

20
--- Creates a new lock.
21
-- @param seconds (optional) default timeout in seconds when acquiring the lock (defaults to 10),
22
-- set to `math.huge` to have no timeout.
23
-- @param not_reentrant (optional) if truthy the lock will not allow a coroutine to grab the same lock multiple times
24
-- @return the lock object
25
function lock.new(seconds, not_reentrant)
12✔
26
  local timeout = tonumber(seconds or DEFAULT_TIMEOUT) or -1
66✔
27
  if timeout < 0 then
66✔
28
    error("expected timeout (1st argument) to be a number greater than or equal to 0, got: " .. tostring(seconds), 2)
×
29
  end
30
  return setmetatable({
121✔
31
            timeout = timeout,
66✔
32
            not_reentrant = not_reentrant,
66✔
33
            queue = {},
66✔
34
            q_tip = 0,  -- index of the first in line waiting
55✔
35
            q_tail = 0, -- index where the next one will be inserted
55✔
36
            owner = nil, -- coroutine holding lock currently
55✔
37
            call_count = nil, -- recursion call count
55✔
38
            errors = setmetatable({}, { __mode = "k" }), -- error indexed by coroutine
66✔
39
          }, lock)
99✔
40
end
41

42

43

44
do
45
  local destroyed_func = function()
46
    return nil, "destroyed"
24✔
47
  end
48

49
  local destroyed_lock_mt = {
12✔
50
    __index = function()
51
      return destroyed_func
24✔
52
    end
53
  }
54

55
  --- destroy a lock.
56
  -- Releases all waiting threads with `nil+"destroyed"`
57
  function lock:destroy()
12✔
58
    --print("destroying ",self)
59
    for i = self.q_tip, self.q_tail do
1,596✔
60
      local co = self.queue[i]
1,548✔
61
      self.queue[i] = nil
1,548✔
62

63
      if co then
1,548✔
64
        self.errors[co] = "destroyed"
1,494✔
65
        --print("marked destroyed ", co)
66
        copas.wakeup(co)
1,494✔
67
      end
68
    end
69

70
    if self.owner then
48✔
71
      self.errors[self.owner] = "destroyed"
48✔
72
      --print("marked destroyed ", co)
73
    end
74
    self.queue = {}
48✔
75
    self.q_tip = 0
48✔
76
    self.q_tail = 0
48✔
77
    self.destroyed = true
48✔
78

79
    setmetatable(self, destroyed_lock_mt)
48✔
80
    return true
48✔
81
  end
82
end
83

84

85
local function timeout_handler(co)
86
  local self = registry[co]
1,512✔
87
  if not self then
1,512✔
88
    return
×
89
  end
90

91
  for i = self.q_tip, self.q_tail do
188,262✔
92
    if co == self.queue[i] then
188,262✔
93
      self.queue[i] = nil
1,512✔
94
      self.errors[co] = "timeout"
1,512✔
95
      --print("marked timeout ", co)
96
      copas.wakeup(co)
1,512✔
97
      return
1,512✔
98
    end
99
  end
100
  -- if we get here, we own it currently, or we finished it by now, or
101
  -- the lock was destroyed. Anyway, nothing to do here...
102
end
103

104

105
--- Acquires the lock.
106
-- If the lock is owned by another thread, this will yield control, until the
107
-- lock becomes available, or it times out.
108
-- If `timeout == 0` then it will immediately return (without yielding).
109
-- @param timeout (optional) timeout in seconds, defaults to the timeout passed to `new` (use `math.huge` to have no timeout).
110
-- @return wait-time on success, or nil+error+wait_time on failure. Errors can be "timeout", "destroyed", or "lock is not re-entrant"
111
function lock:get(timeout)
12✔
112
  local co = coroutine_running()
4,614✔
113
  local start_time
114

115
  -- is the lock already taken?
116
  if self.owner then
4,614✔
117
    -- are we re-entering?
118
    if co == self.owner and not self.not_reentrant then
3,036✔
119
      self.call_count = self.call_count + 1
×
120
      return 0
×
121
    end
122

123
    timeout = timeout or self.timeout
3,036✔
124
    if timeout == 0 then
3,036✔
125
      return nil, "timeout", 0
12✔
126
    end
127

128
    self.queue[self.q_tail] = co
3,024✔
129
    self.q_tail = self.q_tail + 1
3,024✔
130

131
    -- set up timeout
132
    registry[co] = self
3,024✔
133
    copas.timeout(timeout, timeout_handler)
3,024✔
134

135
    start_time = gettime()
3,024✔
136
    copas.pauseforever()
3,024✔
137

138
    local err = self.errors[co]
3,018✔
139
    self.errors[co] = nil
3,018✔
140
    registry[co] = nil
3,018✔
141

142
    --print("released ", co, err)
143
    if err ~= "timeout" then
3,018✔
144
      copas.timeout(0)
1,506✔
145
    end
146
    if err then
3,018✔
147
      return nil, err, gettime() - start_time
3,012✔
148
    end
149
  end
150

151
  -- it's ours to have
152
  self.owner = co
1,584✔
153
  self.call_count = 1
1,584✔
154
  return start_time and (gettime() - start_time) or 0
1,584✔
155
end
156

157

158
--- Releases the lock currently held.
159
-- Releasing a lock that is not owned by the current co-routine will return
160
-- an error.
161
-- returns true, or nil+err on an error
162
function lock:release()
12✔
163
  local co = coroutine_running()
1,536✔
164

165
  if co ~= self.owner then
1,536✔
166
    return nil, "cannot release a lock not owned"
6✔
167
  end
168

169
  self.call_count = self.call_count - 1
1,530✔
170
  if self.call_count > 0 then
1,530✔
171
    -- same coro is still holding it
172
    return true
×
173
  end
174

175
  -- need a loop, since individual coroutines might have been removed
176
  -- (or canceled externally, eg. via `copas.removethread`/`future:cancel`)
177
  -- so there might be holes, or stale entries whose wakeup will fail
178
  while self.q_tip < self.q_tail do
3,042✔
179
    local next_up = self.queue[self.q_tip]
1,524✔
180
    self.queue[self.q_tip] = nil
1,524✔
181
    self.q_tip = self.q_tip + 1
1,524✔
182
    if next_up and copas.wakeup(next_up) then
1,527✔
183
      self.owner = next_up
12✔
184
      return true
12✔
185
    end
186
    -- either an empty hole, or 'next_up' was canceled and will never resume
187
    -- to release the lock itself, so move on to the next waiter in line
188
  end
189
  -- queue is empty, reset pointers
190
  self.owner = nil
1,518✔
191
  self.q_tip = 0
1,518✔
192
  self.q_tail = 0
1,518✔
193
  return true
1,518✔
194
end
195

196

197

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

© 2026 Coveralls, Inc