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

lunarmodules / copas / 22974920245

11 Mar 2026 09:17PM UTC coverage: 84.693% (+0.06%) from 84.635%
22974920245

push

github

web-flow
fix(lock/semaphore): use proper coroutine.running with Lua 5.1 (#180)

With Lua 5.1 the modified version from coxpcall should be used, if
not there might be rare race-conditions.

7 of 9 new or added lines in 2 files covered. (77.78%)

6 existing lines in 3 files now uncovered.

1339 of 1581 relevant lines covered (84.69%)

78921.05 hits per line

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

98.33
/src/copas/timer.lua
1
local copas = require("copas")
21✔
2

3
local xpcall = xpcall
21✔
4
local coroutine_running = coroutine.running
21✔
5

6
if _VERSION=="Lua 5.1" and not jit then     -- obsolete: only for Lua 5.1 compatibility
21✔
UNCOV
7
  xpcall = require("coxpcall").xpcall
3✔
UNCOV
8
  coroutine_running = require("coxpcall").running
3✔
9
end
10

11

12
local timer = {}
21✔
13
timer.__index = timer
21✔
14

15

16
local new_name do
21✔
17
  local count = 0
21✔
18

19
  function new_name()
18✔
20
    count = count + 1
49✔
21
    return "copas_timer_" .. count
49✔
22
  end
23
end
24

25

26
do
27
  local function expire_func(self, initial_delay)
28
    if self.errorhandler then
63✔
29
      copas.seterrorhandler(self.errorhandler)
7✔
30
    end
31
    copas.pause(initial_delay)
63✔
32
    while true do
33
      if not self.cancelled then
133✔
34
        if not self.recurring then
133✔
35
          -- non-recurring timer
36
          self.cancelled = true
28✔
37
          self.co = nil
28✔
38

39
          self:callback(self.params)
28✔
40
          return
28✔
41

42
        else
43
          -- recurring timer
44
          self:callback(self.params)
105✔
45
        end
46
      end
47

48
      if self.cancelled then
105✔
49
        -- clean up and exit the thread
50
        self.co = nil
21✔
51
        self.cancelled = true
21✔
52
        return
21✔
53
      end
54

55
      copas.pause(self.delay)
106✔
56
    end
57
  end
58

59

60
  --- Arms the timer object.
61
  -- @param initial_delay (optional) the first delay to use, if not provided uses the timer delay
62
  -- @return timer object, nil+error, or throws an error on bad input
63
  function timer:arm(initial_delay)
21✔
64
    assert(initial_delay == nil or initial_delay >= 0, "delay must be greater than or equal to 0")
70✔
65
    if self.co then
70✔
66
      return nil, "already armed"
7✔
67
    end
68

69
    self.cancelled = false
63✔
70
    self.co = copas.addnamedthread(self.name, expire_func, self, initial_delay or self.delay)
81✔
71
    return self
63✔
72
  end
73
end
74

75

76

77
--- Cancels a running timer.
78
-- @return timer object, or nil+error
79
function timer:cancel()
21✔
80
  if not self.co then
49✔
81
    return nil, "not armed"
14✔
82
  end
83

84
  if self.cancelled then
35✔
85
    return nil, "already cancelled"
×
86
  end
87

88
  self.cancelled = true
35✔
89
  copas.wakeup(self.co)       -- resume asap
35✔
90
  copas.removethread(self.co) -- will immediately drop the thread upon resuming
35✔
91
  self.co = nil
35✔
92
  return self
35✔
93
end
94

95

96
do
97
  -- xpcall error handler that forwards to the copas errorhandler
98
  local ehandler = function(err_obj)
99
    return copas.geterrorhandler()(err_obj, coroutine_running(), nil)
18✔
100
  end
101

102

103
  --- Creates a new timer object.
104
  -- Note: the callback signature is: `function(timer_obj, params)`.
105
  -- @param opts (table) `opts.delay` timer delay in seconds, `opts.callback` function to execute, `opts.recurring` boolean
106
  -- `opts.params` (optional) this value will be passed to the timer callback, `opts.initial_delay` (optional) the first delay to use, defaults to `delay`.
107
  -- @return timer object, or throws an error on bad input
108
  function timer.new(opts)
21✔
109
    assert((opts.delay or -1) >= 0, "delay must be greater than or equal to 0")
56✔
110
    assert(type(opts.callback) == "function", "expected callback to be a function")
56✔
111

112
    local callback = function(timer_obj, params)
113
      xpcall(opts.callback, ehandler, timer_obj, params)
133✔
114
    end
115

116
    return setmetatable({
128✔
117
      name = opts.name or new_name(),
70✔
118
      delay = opts.delay,
56✔
119
      callback = callback,
56✔
120
      recurring = not not opts.recurring,
56✔
121
      params = opts.params,
56✔
122
      cancelled = false,
40✔
123
      errorhandler = opts.errorhandler,
56✔
124
    }, timer):arm(opts.initial_delay)
112✔
125
  end
126
end
127

128

129

130
return timer
21✔
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