• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

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

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

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

9
local DEFAULT_TIMEOUT = 10
14✔
10

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

14

15
-- registry, locks indexed by the coroutines using them.
16
local registry = setmetatable({}, { __mode="kv" })
14✔
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)
14✔
26
  local timeout = tonumber(seconds or DEFAULT_TIMEOUT) or -1
56✔
27
  if timeout < 0 then
56✔
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({
96✔
31
            timeout = timeout,
56✔
32
            not_reentrant = not_reentrant,
56✔
33
            queue = {},
56✔
34
            q_tip = 0,  -- index of the first in line waiting
40✔
35
            q_tail = 0, -- index where the next one will be inserted
40✔
36
            owner = nil, -- coroutine holding lock currently
40✔
37
            call_count = nil, -- recursion call count
40✔
38
            errors = setmetatable({}, { __mode = "k" }), -- error indexed by coroutine
56✔
39
          }, lock)
80✔
40
end
41

42

43

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

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

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

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

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

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

84

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

91
  for i = self.q_tip, self.q_tail do
219,639✔
92
    if co == self.queue[i] then
219,639✔
93
      self.queue[i] = nil
1,764✔
94
      self.errors[co] = "timeout"
1,764✔
95
      --print("marked timeout ", co)
96
      copas.wakeup(co)
1,764✔
97
      return
1,764✔
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)
14✔
112
  local co = coroutine_running()
5,327✔
113
  local start_time
114

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

123
    self.queue[self.q_tail] = co
3,514✔
124
    self.q_tail = self.q_tail + 1
3,514✔
125
    timeout = timeout or self.timeout
3,514✔
126
    if timeout == 0 then
3,514✔
127
      return nil, "timeout", 0
×
128
    end
129

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

134
    start_time = gettime()
3,514✔
135
    copas.pauseforever()
3,514✔
136

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

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

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

156

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

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

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

174
  -- need a loop, since individual coroutines might have been removed
175
  -- so there might be holes
176
  while self.q_tip < self.q_tail do
3,521✔
177
    local next_up = self.queue[self.q_tip]
1,764✔
178
    if next_up then
1,764✔
179
      self.owner = next_up
7✔
180
      self.queue[self.q_tip] = nil
7✔
181
      self.q_tip = self.q_tip + 1
7✔
182
      copas.wakeup(next_up)
7✔
183
      return true
7✔
184
    end
185
    self.q_tip = self.q_tip + 1
1,757✔
186
  end
187
  -- queue is empty, reset pointers
188
  self.owner = nil
1,757✔
189
  self.q_tip = 0
1,757✔
190
  self.q_tail = 0
1,757✔
191
  return true
1,757✔
192
end
193

194

195

196
return lock
14✔
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