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

lunarmodules / copas / 30428364546

29 Jul 2026 06:30AM UTC coverage: 85.665% (+0.07%) from 85.6%
30428364546

push

github

web-flow
Merge add60ae75 into b980a28cf

1 of 2 new or added lines in 1 file covered. (50.0%)

62 existing lines in 5 files now uncovered.

1494 of 1744 relevant lines covered (85.67%)

55635.72 hits per line

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

93.75
/src/copas/timer.lua
1
local copas = require("copas")
18✔
2

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

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

11

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

15

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

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

25

26
do
27
  local function expire_func(self, initial_delay)
28
    local my_co = coroutine_running()
102✔
29
    if self.errorhandler then
102✔
30
      copas.seterrorhandler(self.errorhandler)
6✔
31
    end
32
    copas.pause(initial_delay)
102✔
33
    while true do
34
      if self.co ~= my_co then
174✔
35
        -- This generation was replaced (eg. the callback cancelled and
36
        -- rearmed the timer, synchronously, before returning). `self.co`
37
        -- and `self.cancelled` now belong to the new generation, so this
38
        -- coroutine must exit without touching them.
39
        return
18✔
40
      end
41

42
      if not self.cancelled then
156✔
43
        if not self.recurring then
156✔
44
          -- non-recurring timer
45
          self.cancelled = true
24✔
46
          self.co = nil
24✔
47

48
          self:callback(self.params)
24✔
49
          return
24✔
50

51
        else
52
          -- recurring timer
53
          self:callback(self.params)
132✔
54
        end
55
      end
56

57
      if self.co ~= my_co then
132✔
58
        -- replaced while the callback was running, see above
59
        return
60✔
60
      end
61

62
      if self.cancelled then
72✔
63
        -- clean up and exit the thread
64
        self.co = nil
×
65
        self.cancelled = true
×
66
        return
×
67
      end
68

69
      copas.pause(self.delay)
84✔
70
    end
71
  end
72

73

74
  --- Arms the timer object.
75
  -- @param initial_delay (optional) the first delay to use, if not provided uses the timer delay
76
  -- @return timer object, nil+error, or throws an error on bad input
77
  function timer:arm(initial_delay)
18✔
78
    assert(initial_delay == nil or initial_delay >= 0, "delay must be greater than or equal to 0")
108✔
79
    if self.co then
108✔
80
      return nil, "already armed"
6✔
81
    end
82

83
    self.cancelled = false
102✔
84
    self.co = copas.addnamedthread(self.name, expire_func, self, initial_delay or self.delay)
119✔
85
    return self
102✔
86
  end
87
end
88

89

90

91
--- Cancels a running timer.
92
-- If the timer callback is currently in progress (eg. yielded on socket I/O),
93
-- it is allowed to run to completion, it just won't be rescheduled. Only a
94
-- timer that is idle, waiting for its next recurrence, is woken up and
95
-- stopped immediately.
96
-- @return timer object, or nil+error
97
function timer:cancel()
18✔
98
  if not self.co then
90✔
99
    return nil, "not armed"
12✔
100
  end
101

102
  if self.cancelled then
78✔
103
    return nil, "already cancelled"
×
104
  end
105

106
  self.cancelled = true
78✔
107
  copas.wakeup(self.co) -- in case it's idle between recurrences, exit immediately
78✔
108
  self.co = nil
78✔
109
  return self
78✔
110
end
111

112

113
do
114
  -- xpcall error handler that forwards to the copas errorhandler
115
  local ehandler = function(err_obj)
116
    return copas.geterrorhandler()(err_obj, coroutine_running(), nil)
14✔
117
  end
118

119

120
  --- Creates a new timer object.
121
  -- Note: the callback signature is: `function(timer_obj, params)`.
122
  -- @param opts (table) `opts.delay` timer delay in seconds, `opts.callback` function to execute, `opts.recurring` boolean
123
  -- `opts.params` (optional) this value will be passed to the timer callback, `opts.initial_delay` (optional) the first delay to use, defaults to `delay`.
124
  -- @return timer object, or throws an error on bad input
125
  function timer.new(opts)
18✔
126
    assert((opts.delay or -1) >= 0, "delay must be greater than or equal to 0")
66✔
127
    assert(type(opts.callback) == "function", "expected callback to be a function")
66✔
128

129
    local callback = function(timer_obj, params)
130
      xpcall(opts.callback, ehandler, timer_obj, params)
156✔
131
    end
132

133
    return setmetatable({
154✔
134
      name = opts.name or new_name(),
76✔
135
      delay = opts.delay,
66✔
136
      callback = callback,
66✔
137
      recurring = not not opts.recurring,
66✔
138
      params = opts.params,
66✔
139
      cancelled = false,
55✔
140
      errorhandler = opts.errorhandler,
66✔
141
    }, timer):arm(opts.initial_delay)
132✔
142
  end
143
end
144

145

146

147
return timer
18✔
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