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

lunarmodules / copas / 23646679499

27 Mar 2026 12:41PM UTC coverage: 85.443%. Remained the same
23646679499

push

github

Tieske
chore(docs): update release instructions

1397 of 1635 relevant lines covered (85.44%)

77046.68 hits per line

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

97.12
/src/copas/semaphore.lua
1
local copas = require("copas")
175✔
2

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

8
local DEFAULT_TIMEOUT = 10
175✔
9

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

13

14
-- registry, semaphore indexed by the coroutines using them.
15
local registry = setmetatable({}, { __mode="kv" })
175✔
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)
175✔
23
  local timeout = tonumber(seconds or DEFAULT_TIMEOUT) or -1
533✔
24
  if timeout < 0 then
533✔
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
533✔
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,066✔
32
      count = start or 0,
533✔
33
      max = max,
533✔
34
      timeout = timeout,
533✔
35
      q_tip = 1,    -- position of next entry waiting
383✔
36
      q_tail = 1,   -- position where next one will be inserted
383✔
37
      queue = {},
533✔
38
      to_flags = setmetatable({}, { __mode = "k" }), -- timeout flags indexed by coroutine
533✔
39
    }, semaphore)
533✔
40

41
  return self
533✔
42
end
43

44

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

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

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

66

67
-- Gives resources.
68
-- @param given (optional, default 1) number of resources to return. If more
69
-- than the maximum are returned then it will be capped at the maximum and
70
-- error "too many" will be returned.
71
function semaphore:give(given)
175✔
72
  local err
73
  given = given or 1
694✔
74
  local count = self.count + given
694✔
75
  --print("now at",count, ", after +"..given)
76
  if count > self.max then
694✔
77
    count = self.max
428✔
78
    err = "too many"
428✔
79
  end
80

81
  while self.q_tip < self.q_tail do
918✔
82
    local i = self.q_tip
231✔
83
    local nxt = self.queue[i] -- there can be holes, so nxt might be nil
231✔
84
    if not nxt then
231✔
85
      self.q_tip = i + 1
14✔
86
    else
87
      if count >= nxt.requested then
217✔
88
        -- release it
89
        self.queue[i] = nil
210✔
90
        self.to_flags[nxt.co] = nil
210✔
91
        count = count - nxt.requested
210✔
92
        self.q_tip = i + 1
210✔
93
        copas.wakeup(nxt.co)
210✔
94
        nxt.co = nil
210✔
95
      else
96
        break -- we ran out of resources
97
      end
98
    end
99
  end
100

101
  if self.q_tip == self.q_tail then  -- reset queue
694✔
102
    self.queue = {}
687✔
103
    self.q_tip = 1
687✔
104
    self.q_tail = 1
687✔
105
  end
106

107
  self.count = count
694✔
108
  if err then
694✔
109
    return nil, err
428✔
110
  end
111
  return true
266✔
112
end
113

114

115

116
local function timeout_handler(co)
117
  local self = registry[co]
14✔
118
  --print("checking timeout ", co)
119
  if not self then
14✔
120
    return
×
121
  end
122

123
  for i = self.q_tip, self.q_tail do
14✔
124
    local item = self.queue[i]
14✔
125
    if item and co == item.co then
14✔
126
      self.queue[i] = nil
14✔
127
      self.to_flags[co] = true
14✔
128
      --print("marked timeout ", co)
129
      copas.wakeup(co)
14✔
130
      return
14✔
131
    end
132
  end
133
  -- nothing to do here...
134
end
135

136

137
-- Requests resources from the semaphore.
138
-- Waits if there are not enough resources available before returning.
139
-- @param requested (optional, default 1) the number of resources requested
140
-- @param timeout (optional, defaults to semaphore timeout) timeout in
141
-- seconds. If 0 it will either succeed or return immediately with error "timeout".
142
-- If `math.huge` it will wait forever.
143
-- @return true, or nil+"destroyed"
144
function semaphore:take(requested, timeout)
175✔
145
  requested = requested or 1
371✔
146
  if self.q_tail == 1 and self.count >= requested then
371✔
147
    -- nobody is waiting before us, and there is enough in store
148
    self.count = self.count - requested
126✔
149
    return true
126✔
150
  end
151

152
  if requested > self.max then
245✔
153
    return nil, "too many"
7✔
154
  end
155

156
  local to = timeout or self.timeout
238✔
157
  if to == 0 then
238✔
158
    return nil, "timeout"
14✔
159
  end
160

161
  -- get in line
162
  local co = coroutine_running()
224✔
163
  self.to_flags[co] = nil
224✔
164
  registry[co] = self
224✔
165
  copas.timeout(to, timeout_handler)
224✔
166

167
  self.queue[self.q_tail] = {
224✔
168
    co = co,
224✔
169
    requested = requested,
224✔
170
    --timeout = nil, -- flag indicating timeout
171
  }
224✔
172
  self.q_tail = self.q_tail + 1
224✔
173

174
  copas.pauseforever() -- block until woken
224✔
175
  registry[co] = nil
224✔
176

177
  if self.to_flags[co] then
224✔
178
    -- a timeout happened
179
    self.to_flags[co] = nil
14✔
180
    return nil, "timeout"
14✔
181
  end
182

183
  copas.timeout(0)
210✔
184

185
  if self.destroyed then
210✔
186
    return nil, "destroyed"
63✔
187
  end
188

189
  return true
147✔
190
end
191

192
-- returns current available resources
193
function semaphore:get_count()
175✔
194
  return self.count
63✔
195
end
196

197
-- returns total shortage for requested resources
198
function semaphore:get_wait()
175✔
199
  local wait = 0
84✔
200
  for i = self.q_tip, self.q_tail - 1 do
182✔
201
    wait = wait + ((self.queue[i] or {}).requested or 0)
98✔
202
  end
203
  return wait - self.count
84✔
204
end
205

206

207
return semaphore
175✔
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