• 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

97.54
/src/copas/semaphore.lua
1
local copas = require("copas")
162✔
2

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

8
local DEFAULT_TIMEOUT = 10
162✔
9

10
local semaphore = {}
162✔
11
semaphore.__index = semaphore
162✔
12

13

14
-- registry, semaphore indexed by the coroutines using them.
15
local registry = setmetatable({}, { __mode="kv" })
162✔
16

17

18
-- create a new semaphore
19
-- @param max maximum number of resources the semaphore can hold (this maximum does NOT include resources that have been given but not yet returned).
20
-- @param start (optional, default 0) the initial resources available
21
-- @param seconds (optional, default 10) default semaphore timeout in seconds, or `math.huge` to have no timeout.
22
function semaphore.new(max, start, seconds)
162✔
23
  local timeout = tonumber(seconds or DEFAULT_TIMEOUT) or -1
602✔
24
  if timeout < 0 then
602✔
25
    error("expected timeout (2nd argument) to be a number greater than or equal to 0, got: " .. tostring(seconds), 2)
×
26
  end
27
  if type(max) ~= "number" or max < 1 then
602✔
28
    error("expected max resources (1st argument) to be a number greater than 0, got: " .. tostring(max), 2)
×
29
  end
30

31
  local self = setmetatable({
1,204✔
32
      count = start or 0,
602✔
33
      max = max,
602✔
34
      timeout = timeout,
602✔
35
      q_tip = 1,    -- position of next entry waiting
503✔
36
      q_tail = 1,   -- position where next one will be inserted
503✔
37
      queue = {},
602✔
38
      to_flags = setmetatable({}, { __mode = "k" }), -- timeout flags indexed by coroutine
602✔
39
    }, semaphore)
602✔
40

41
  return self
602✔
42
end
43

44

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

50
  local destroyed_semaphore_mt = {
162✔
51
    __index = function()
52
      return destroyed_func
24✔
53
    end
54
  }
55

56
  -- destroy a semaphore.
57
  -- Releases all waiting threads with `nil+"destroyed"`
58
  function semaphore:destroy()
162✔
59
    self:release_all()
446✔
60
    self.destroyed = true
446✔
61
    setmetatable(self, destroyed_semaphore_mt)
446✔
62
    return true
446✔
63
  end
64
end
65

66

67
-- Gives resources.
68
-- @param given (optional, default 1) number of resources to return. Must be
69
-- a finite number greater than or equal to 0. If more than the maximum are
70
-- returned then it will be capped at the maximum and error "too many" will
71
-- be returned.
72
function semaphore:give(given)
162✔
73
  local err
74
  given = given or 1
372✔
75
  if given < 0 or given == math.huge or given ~= given then
372✔
76
    error("expected given resources (1st argument) to be a finite number greater than or equal to 0, got: " .. tostring(given), 2)
18✔
77
  end
78
  local count = self.count + given
354✔
79
  --print("now at",count, ", after +"..given)
80
  if count > self.max then
354✔
81
    count = self.max
6✔
82
    err = "too many"
6✔
83
  end
84

85
  while self.q_tip < self.q_tail do
660✔
86
    local i = self.q_tip
342✔
87
    local nxt = self.queue[i] -- there can be holes, so nxt might be nil
342✔
88
    if not nxt then
342✔
89
      self.q_tip = i + 1
12✔
90
    elseif not copas.issleeping(nxt.co) then
385✔
91
      -- stale entry; 'nxt.co' was canceled externally (eg. via
92
      -- copas.removethread/future:cancel) and will never resume to
93
      -- consume or give back resources. Drop it without touching 'count',
94
      -- and regardless of how much it had requested, so it doesn't block
95
      -- waiters behind it that request less than it did.
96
      self.queue[i] = nil
24✔
97
      self.to_flags[nxt.co] = nil
24✔
98
      self.q_tip = i + 1
24✔
99
      nxt.co = nil
24✔
100
    elseif count >= nxt.requested then
306✔
101
      -- release it
102
      self.queue[i] = nil
270✔
103
      self.to_flags[nxt.co] = nil
270✔
104
      count = count - nxt.requested
270✔
105
      self.q_tip = i + 1
270✔
106
      copas.wakeup(nxt.co)
270✔
107
      nxt.co = nil
270✔
108
    else
109
      break -- we ran out of resources
110
    end
111
  end
112

113
  if self.q_tip == self.q_tail then  -- reset queue
354✔
114
    self.queue = {}
318✔
115
    self.q_tip = 1
318✔
116
    self.q_tail = 1
318✔
117
  end
118

119
  self.count = count
354✔
120
  if err then
354✔
121
    return nil, err
6✔
122
  end
123
  return true
348✔
124
end
125

126

127

128
local function timeout_handler(co)
129
  local self = registry[co]
48✔
130
  --print("checking timeout ", co)
131
  if not self then
48✔
132
    return
×
133
  end
134

135
  for i = self.q_tip, self.q_tail do
84✔
136
    local item = self.queue[i]
66✔
137
    if item and co == item.co then
66✔
138
      self.queue[i] = nil
30✔
139
      self.to_flags[co] = true
30✔
140
      --print("marked timeout ", co)
141
      copas.wakeup(co)
30✔
142
      return
30✔
143
    end
144
  end
145
  -- nothing to do here...
146
end
147

148

149
-- Requests resources from the semaphore.
150
-- Waits if there are not enough resources available before returning.
151
-- @param requested (optional, default 1) the number of resources requested.
152
-- Must be a number greater than or equal to 1, and not NaN.
153
-- @param timeout (optional, defaults to semaphore timeout) timeout in
154
-- seconds. If 0 it will either succeed or return immediately with error "timeout".
155
-- If `math.huge` it will wait forever.
156
-- @return true, or nil+"destroyed"
157
function semaphore:take(requested, timeout)
162✔
158
  requested = requested or 1
474✔
159
  if requested < 1 or requested ~= requested then
474✔
160
    error("expected requested resources (1st argument) to be a number greater than or equal to 1, got: " .. tostring(requested), 2)
18✔
161
  end
162
  if self.q_tail == 1 and self.count >= requested then
456✔
163
    -- nobody is waiting before us, and there is enough in store
164
    self.count = self.count - requested
114✔
165
    return true
114✔
166
  end
167

168
  if requested > self.max then
342✔
169
    return nil, "too many"
6✔
170
  end
171

172
  local to = timeout or self.timeout
336✔
173
  if to == 0 then
336✔
174
    return nil, "timeout"
12✔
175
  end
176

177
  -- get in line
178
  local co = coroutine_running()
324✔
179
  self.to_flags[co] = nil
324✔
180
  registry[co] = self
324✔
181
  copas.timeout(to, timeout_handler)
324✔
182

183
  self.queue[self.q_tail] = {
324✔
184
    co = co,
324✔
185
    requested = requested,
324✔
186
    --timeout = nil, -- flag indicating timeout
187
  }
324✔
188
  self.q_tail = self.q_tail + 1
324✔
189

190
  copas.pauseforever() -- block until woken
324✔
191
  registry[co] = nil
294✔
192

193
  if self.to_flags[co] then
294✔
194
    -- a timeout happened
195
    self.to_flags[co] = nil
24✔
196
    return nil, "timeout"
24✔
197
  end
198

199
  copas.timeout(0)
270✔
200

201
  if self.destroyed then
270✔
202
    return nil, "destroyed"
84✔
203
  end
204

205
  return true
186✔
206
end
207

208
-- returns current available resources
209
function semaphore:get_count()
162✔
210
  return self.count
138✔
211
end
212

213
-- returns total shortage for requested resources
214
function semaphore:get_wait()
162✔
215
  local wait = 0
548✔
216
  for i = self.q_tip, self.q_tail - 1 do
824✔
217
    local item = self.queue[i]
276✔
218
    if item and copas.issleeping(item.co) then
322✔
219
      wait = wait + item.requested
258✔
220
    end
221
  end
222
  return wait - self.count
548✔
223
end
224

225

226
-- Releases every currently queued waiter, regardless of `max`.
227
-- Feeds `get_wait()` into `give()` in `max`-sized chunks, since a single
228
-- `give()` call is capped at `max` and would otherwise strand waiters
229
-- beyond it.
230
function semaphore:release_all()
162✔
231
  local wait = self:get_wait()
530✔
232
  while wait > 0 do
686✔
233
    if wait > self.max then
156✔
234
      self:give(self.max)
35✔
235
    else
236
      self:give(wait)
126✔
237
    end
238
    wait = wait - self.max
156✔
239
  end
240
end
241

242

243
return semaphore
162✔
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