tupelo 0.18 → 0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/example/lease.rb +1 -2
- data/example/observer.rb +3 -0
- data/example/riemann/event-subspace.rb +60 -0
- data/example/riemann/expirer-v1.rb +25 -0
- data/example/riemann/producer.rb +32 -0
- data/example/riemann/riemann-v1.rb +61 -0
- data/example/riemann/riemann-v2.rb +72 -0
- data/lib/tupelo/client/scheduler.rb +56 -0
- data/lib/tupelo/client/worker.rb +6 -5
- data/lib/tupelo/version.rb +1 -1
- metadata +8 -4
- data/example/subspaces/riemann.rb +0 -103
- data/lib/tupelo/client/atdo.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8a67711763af58aa46979622519450ba198e603
|
4
|
+
data.tar.gz: fc399bd00b0b69ec5938a0d3ffae68155c186ae7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f3570788a47744267fc361e25bd50b56bf327177764fb428317cb51ed1870c7e86acc8df53ef28d60278f9c902057b2a5275f86eba29189ea80f28259ddfc9f
|
7
|
+
data.tar.gz: 85a3be5a56f49d9518c2ed1409d992654e3f36f80847b441d7f4d84bf29afba541c11164c4052f97e208916e9a2a4cbf57aeef04736207160d4c6b4cd2d69e25
|
data/example/lease.rb
CHANGED
@@ -52,9 +52,8 @@ Tupelo.application do
|
|
52
52
|
# one worker lives. This demonstrates how to recover from worker failure
|
53
53
|
# and prevent "lost tuples".
|
54
54
|
child passive: true do
|
55
|
-
require 'tupelo/client/atdo'
|
56
|
-
|
57
55
|
scheduler = make_scheduler
|
56
|
+
|
58
57
|
alive_until = Hash.new(0)
|
59
58
|
|
60
59
|
loop do
|
data/example/observer.rb
CHANGED
@@ -32,6 +32,9 @@ Tupelo.application do
|
|
32
32
|
counter = t.read count: Numeric
|
33
33
|
log "entering new state: #{counter}"
|
34
34
|
t.wait
|
35
|
+
# If you prefer to check for failure periodically, rather than
|
36
|
+
# blocking with #wait, then simply call t.failed? You can call
|
37
|
+
# t.missing to see which tuples caused the failure.
|
35
38
|
rescue Tupelo::Client::TransactionFailure => ex
|
36
39
|
log "leaving old state: #{counter}"
|
37
40
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class Tupelo::Client
|
2
|
+
def define_event_subspace
|
3
|
+
define_subspace("event", {
|
4
|
+
# field type description
|
5
|
+
# (from http://riemann.io/concepts.html)
|
6
|
+
|
7
|
+
host: String, # A hostname, e.g. "api1", "foo.com"
|
8
|
+
|
9
|
+
service: String, # e.g. "API port 8000 reqs/sec"
|
10
|
+
|
11
|
+
state: String, # Any string less than 255 bytes, e.g. "ok",
|
12
|
+
# "warning", "critical"
|
13
|
+
|
14
|
+
time: Numeric, # The time of the event, in unix epoch seconds
|
15
|
+
|
16
|
+
description: String, # Freeform text
|
17
|
+
|
18
|
+
tags: Array, # Freeform list of strings,
|
19
|
+
# e.g. ["rate", "fooproduct", "transient"]
|
20
|
+
|
21
|
+
metric: Numeric, # A number associated with this event,
|
22
|
+
# e.g. the number of reqs/sec.
|
23
|
+
|
24
|
+
ttl: Numeric, # A floating-point time, in seconds, that this
|
25
|
+
# event is considered valid for. Expired states
|
26
|
+
# may be removed from the index.
|
27
|
+
|
28
|
+
custom: nil # Any data
|
29
|
+
# (not quite same as riemann's custom event attrs,
|
30
|
+
# which are just arbitrary key-value pairs;
|
31
|
+
# tupelo does not permit wildcards in keys)
|
32
|
+
})
|
33
|
+
end
|
34
|
+
|
35
|
+
# This could be a subspace of the event subspace, but for now, we can just
|
36
|
+
# use it as a template to select critical events out of the event subspace.
|
37
|
+
CRITICAL_EVENT = {
|
38
|
+
host: nil,
|
39
|
+
service: nil,
|
40
|
+
state: /\A(?:critical|fatal)\z/i,
|
41
|
+
time: nil,
|
42
|
+
description: nil,
|
43
|
+
tags: nil,
|
44
|
+
metric: nil,
|
45
|
+
ttl: nil,
|
46
|
+
custom: nil
|
47
|
+
}.freeze
|
48
|
+
|
49
|
+
EXPIRED_EVENT = {
|
50
|
+
host: nil,
|
51
|
+
service: nil,
|
52
|
+
state: /\Aexpired\z/i,
|
53
|
+
time: nil,
|
54
|
+
description: nil,
|
55
|
+
tags: nil,
|
56
|
+
metric: nil,
|
57
|
+
ttl: nil,
|
58
|
+
custom: nil
|
59
|
+
}.freeze
|
60
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Tupelo::Client
|
2
|
+
# Expire old events.
|
3
|
+
def run_expirer_v1
|
4
|
+
scheduler = make_scheduler
|
5
|
+
|
6
|
+
read subspace("event") do |event|
|
7
|
+
next if event["state"] == "expired"
|
8
|
+
|
9
|
+
event_exp = event["time"] + event["ttl"]
|
10
|
+
scheduler.at event_exp do
|
11
|
+
transaction do
|
12
|
+
take event
|
13
|
+
pulse event.merge("state" => "expired")
|
14
|
+
# Not sure if this is riemann semantics. Using #pulse rather
|
15
|
+
# that #write means that the expired event exists in the
|
16
|
+
# space only while the transaction is executing, but that is
|
17
|
+
# enough to trigger any client that is waiting on a template
|
18
|
+
# that matches the event. Use the --debug-expiration switch
|
19
|
+
# to see this happening (and use -v to make log messages
|
20
|
+
# verbose, showing timestamps).
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Tupelo::Client
|
2
|
+
# Generate some events.
|
3
|
+
def run_producer i
|
4
|
+
event = {
|
5
|
+
host: `hostname`.chomp,
|
6
|
+
service: "service #{i}",
|
7
|
+
state: "",
|
8
|
+
time: 0,
|
9
|
+
description: "",
|
10
|
+
tags: [],
|
11
|
+
metric: 0,
|
12
|
+
ttl: 0,
|
13
|
+
custom: nil
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
e_ok = event.merge(
|
17
|
+
state: "ok",
|
18
|
+
time: Time.now.to_f,
|
19
|
+
ttl: 0.2
|
20
|
+
)
|
21
|
+
|
22
|
+
if e_ok[:ttl] == 0.0
|
23
|
+
pulse e_ok # no need to bother with expiration
|
24
|
+
else
|
25
|
+
write e_ok
|
26
|
+
end
|
27
|
+
|
28
|
+
log "created event #{e_ok}"
|
29
|
+
|
30
|
+
sleep 0.5 # Let it expire
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# A toy implementation of Riemann (http://riemann.io).
|
2
|
+
#
|
3
|
+
# Version 1 uses the default tuplespace for all subspaces, which is inefficient
|
4
|
+
# for searching.
|
5
|
+
|
6
|
+
require 'tupelo/app'
|
7
|
+
require_relative 'event-subspace'
|
8
|
+
require_relative 'producer'
|
9
|
+
require_relative 'expirer-v1'
|
10
|
+
|
11
|
+
N_PRODUCERS = 3
|
12
|
+
N_CONSUMERS = 2
|
13
|
+
|
14
|
+
Tupelo.application do
|
15
|
+
|
16
|
+
local do
|
17
|
+
use_subspaces!
|
18
|
+
define_event_subspace
|
19
|
+
end
|
20
|
+
|
21
|
+
N_PRODUCERS.times do |i|
|
22
|
+
child subscribe: [] do # N.b., no subscriptions
|
23
|
+
log.progname = "producer #{i}"
|
24
|
+
run_producer i
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
N_CONSUMERS.times do |i|
|
29
|
+
# stores events
|
30
|
+
child subscribe: "event", passive: true do
|
31
|
+
log.progname = "consumer #{i}"
|
32
|
+
read subspace("event") do |event|
|
33
|
+
log event ### need filtering, actions, etc.
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# critical event alerter
|
39
|
+
child subscribe: "event", passive: true do
|
40
|
+
log.progname = "alerter"
|
41
|
+
read Tupelo::Client::CRITICAL_EVENT do |event|
|
42
|
+
log.error event
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
if argv.include?("--debug-expiration")
|
47
|
+
# expired event debugger
|
48
|
+
child subscribe: "event", passive: true do
|
49
|
+
log.progname = "expiration debugger"
|
50
|
+
read Tupelo::Client::EXPIRED_EVENT do |event|
|
51
|
+
log event
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# expirer: stores current events and looks for events that can be expired.
|
57
|
+
child subscribe: "event", passive: true do
|
58
|
+
log.progname = "expirer"
|
59
|
+
run_expirer_v1
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# A toy implementation of Riemann (http://riemann.io).
|
2
|
+
#
|
3
|
+
# Version 2 stores the event subspace using different data
|
4
|
+
# structures in different clients, depending on needs:
|
5
|
+
#
|
6
|
+
# * generic consumers need to index by host and service
|
7
|
+
#
|
8
|
+
# * the expiration manager need to sort by expiration time
|
9
|
+
#
|
10
|
+
# * the critical event alerter doesn't need to sort at all.
|
11
|
+
|
12
|
+
abort "work in progress"
|
13
|
+
|
14
|
+
require 'tupelo/app'
|
15
|
+
require_relative 'event-subspace'
|
16
|
+
require_relative 'producer'
|
17
|
+
require_relative 'expirer-v2'
|
18
|
+
|
19
|
+
N_PRODUCERS = 3
|
20
|
+
N_CONSUMERS = 2
|
21
|
+
|
22
|
+
Tupelo.application do
|
23
|
+
|
24
|
+
local do
|
25
|
+
use_subspaces!
|
26
|
+
define_event_subspace
|
27
|
+
end
|
28
|
+
|
29
|
+
N_PRODUCERS.times do |i|
|
30
|
+
child subscribe: [] do # N.b., no subscriptions
|
31
|
+
log.progname = "producer #{i}"
|
32
|
+
run_producer i ### V2: manual tagging
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
N_CONSUMERS.times do |i|
|
37
|
+
# stores events indexed by host, service
|
38
|
+
child subscribe: "event", passive: true do ### tuplespace: sqlite
|
39
|
+
log.progname = "consumer #{i}"
|
40
|
+
read subspace("event") do |event|
|
41
|
+
log event ### need filtering, actions, etc.
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# critical event alerter
|
47
|
+
child subscribe: "event", passive: true do ### tuplespace: bag?
|
48
|
+
log.progname = "alerter"
|
49
|
+
read Tupelo::Client::CRITICAL_EVENT do |event|
|
50
|
+
log.error event
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
if argv.include?("--debug-expiration")
|
55
|
+
# expired event debugger
|
56
|
+
child subscribe: "event", passive: true do
|
57
|
+
log.progname = "expiration debugger"
|
58
|
+
read Tupelo::Client::EXPIRED_EVENT do |event|
|
59
|
+
log event
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# expirer: stores current events and looks for events that can be expired.
|
65
|
+
child subscribe: "event", passive: true do
|
66
|
+
log.progname = "expirer"
|
67
|
+
run_expirer_v2
|
68
|
+
### use rbtree
|
69
|
+
end
|
70
|
+
|
71
|
+
### Add sinatra app.
|
72
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'atdo'
|
2
|
+
|
3
|
+
module Tupelo
|
4
|
+
class Client
|
5
|
+
class Scheduler < AtDo
|
6
|
+
DEFAULT_STORAGE =
|
7
|
+
begin
|
8
|
+
require 'rbtree'
|
9
|
+
MultiRBTree
|
10
|
+
rescue LoadError
|
11
|
+
Array
|
12
|
+
end
|
13
|
+
|
14
|
+
# Instead of calling this method, call Client#make_scheduler.
|
15
|
+
def initialize client, **opts
|
16
|
+
@client = client
|
17
|
+
super **opts
|
18
|
+
end
|
19
|
+
|
20
|
+
# Accepts numeric +time+ or Time instance. Logs errors that occur
|
21
|
+
# in +action+. Otherwise, same as AtDo#at from the atdo gem.
|
22
|
+
def at time, &action
|
23
|
+
time = Time.at(time) if time.kind_of? Numeric
|
24
|
+
super time do
|
25
|
+
begin
|
26
|
+
action.call
|
27
|
+
rescue => ex
|
28
|
+
@client.log.error "error in action scheduled for #{time}:" +
|
29
|
+
" #{ex.class}: #{ex}\n #{ex.backtrace.join("\n ")}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a scheduler, which has a method that you can call to schedule
|
36
|
+
# an action at a time:
|
37
|
+
#
|
38
|
+
# s = make_scheduler # or client.make_scheduler
|
39
|
+
# s.at t do ... end
|
40
|
+
#
|
41
|
+
# where t can be either a Time or Numeric seconds.
|
42
|
+
#
|
43
|
+
# The scheduler runs in its own thread. It uses a red-black tree to store
|
44
|
+
# the actions, if the rbtree gem is installed and you do not specify
|
45
|
+
# 'storage: Array'. Otherwise it uses a sorted Array, which is fine for
|
46
|
+
# small schedules.
|
47
|
+
#
|
48
|
+
# Scheduler is used internally by Worker to manage transaction timeouts,
|
49
|
+
# but client code may create its own scheduler -- see example/lease.rb.
|
50
|
+
#
|
51
|
+
def make_scheduler **opts
|
52
|
+
opts[:storage] ||= Scheduler::DEFAULT_STORAGE
|
53
|
+
Scheduler.new self, **opts
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/tupelo/client/worker.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'thread'
|
2
2
|
require 'tupelo/client/reader'
|
3
3
|
require 'tupelo/client/transaction'
|
4
|
+
require 'tupelo/client/scheduler'
|
4
5
|
require 'object-template'
|
5
|
-
require 'atdo'
|
6
6
|
|
7
7
|
class Tupelo::Client
|
8
8
|
class Worker
|
@@ -70,6 +70,7 @@ class Tupelo::Client
|
|
70
70
|
@seq = nil
|
71
71
|
@arc = nil
|
72
72
|
@log = client.log
|
73
|
+
@scheduler = nil
|
73
74
|
|
74
75
|
@client_id = nil
|
75
76
|
@global_tick = nil
|
@@ -142,19 +143,19 @@ class Tupelo::Client
|
|
142
143
|
cmd_queue << :stop
|
143
144
|
worker_thread.join if worker_thread ## join(limit)?
|
144
145
|
msg_reader_thread.kill if msg_reader_thread
|
145
|
-
@
|
146
|
+
@scheduler.stop if @scheduler
|
146
147
|
end
|
147
148
|
|
148
149
|
# stop without any remote handshaking
|
149
150
|
def stop!
|
150
151
|
@msg_reader_thread.kill if msg_reader_thread
|
151
152
|
@worker_thread.kill if worker_thread
|
152
|
-
@
|
153
|
+
@scheduler.stop if @scheduler
|
153
154
|
end
|
154
155
|
|
155
156
|
def at time, &action
|
156
|
-
@
|
157
|
-
@
|
157
|
+
@scheduler ||= client.make_scheduler
|
158
|
+
@scheduler.at time do
|
158
159
|
cmd_queue << action
|
159
160
|
end
|
160
161
|
end
|
data/lib/tupelo/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tupelo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.19'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel VanderWerf
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-02-
|
11
|
+
date: 2014-02-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: atdo
|
@@ -145,6 +145,11 @@ files:
|
|
145
145
|
- example/pulse.rb
|
146
146
|
- example/read-in-trans.rb
|
147
147
|
- example/remote.rb
|
148
|
+
- example/riemann/event-subspace.rb
|
149
|
+
- example/riemann/expirer-v1.rb
|
150
|
+
- example/riemann/producer.rb
|
151
|
+
- example/riemann/riemann-v1.rb
|
152
|
+
- example/riemann/riemann-v2.rb
|
148
153
|
- example/small-simplified.rb
|
149
154
|
- example/small.rb
|
150
155
|
- example/socket-broker.rb
|
@@ -153,7 +158,6 @@ files:
|
|
153
158
|
- example/subspaces/addr-book.rb
|
154
159
|
- example/subspaces/pubsub.rb
|
155
160
|
- example/subspaces/ramp.rb
|
156
|
-
- example/subspaces/riemann.rb
|
157
161
|
- example/subspaces/shop/shop-v1.rb
|
158
162
|
- example/subspaces/shop/shop-v2.rb
|
159
163
|
- example/subspaces/simple.rb
|
@@ -186,9 +190,9 @@ files:
|
|
186
190
|
- lib/tupelo/archiver/tuplespace.rb
|
187
191
|
- lib/tupelo/archiver/worker.rb
|
188
192
|
- lib/tupelo/client.rb
|
189
|
-
- lib/tupelo/client/atdo.rb
|
190
193
|
- lib/tupelo/client/common.rb
|
191
194
|
- lib/tupelo/client/reader.rb
|
195
|
+
- lib/tupelo/client/scheduler.rb
|
192
196
|
- lib/tupelo/client/subspace.rb
|
193
197
|
- lib/tupelo/client/transaction.rb
|
194
198
|
- lib/tupelo/client/tuplespace.rb
|
@@ -1,103 +0,0 @@
|
|
1
|
-
# A toy implementation of Riemann (http://riemann.io)
|
2
|
-
#
|
3
|
-
# Also, this is an example of storing a subspace using different data
|
4
|
-
# structures in different clients, depending on needs: some clients (generic
|
5
|
-
# consumers) need to index by host and service, and others (expiration manager)
|
6
|
-
# need to sort by expiration time.
|
7
|
-
|
8
|
-
require 'tupelo/app'
|
9
|
-
|
10
|
-
N_PRODUCERS = 3
|
11
|
-
N_CONSUMERS = 2
|
12
|
-
|
13
|
-
Tupelo.application do
|
14
|
-
|
15
|
-
local do
|
16
|
-
use_subspaces!
|
17
|
-
|
18
|
-
define_subspace("event", {
|
19
|
-
# field type description
|
20
|
-
# (from http://riemann.io/concepts.html)
|
21
|
-
|
22
|
-
host: String, # A hostname, e.g. "api1", "foo.com"
|
23
|
-
|
24
|
-
service: String, # e.g. "API port 8000 reqs/sec"
|
25
|
-
|
26
|
-
state: String, # Any string less than 255 bytes, e.g. "ok",
|
27
|
-
# "warning", "critical"
|
28
|
-
|
29
|
-
time: Numeric, # The time of the event, in unix epoch seconds
|
30
|
-
|
31
|
-
description: String, # Freeform text
|
32
|
-
|
33
|
-
tags: Array, # Freeform list of strings,
|
34
|
-
# e.g. ["rate", "fooproduct", "transient"]
|
35
|
-
|
36
|
-
metric: Numeric, # A number associated with this event,
|
37
|
-
# e.g. the number of reqs/sec.
|
38
|
-
|
39
|
-
ttl: Numeric # A floating-point time, in seconds, that this
|
40
|
-
# event is considered valid for. Expired states
|
41
|
-
# may be removed from the index.
|
42
|
-
})
|
43
|
-
end
|
44
|
-
|
45
|
-
N_PRODUCERS.times do
|
46
|
-
child subscribe: [] do # N.b., no subscriptions
|
47
|
-
event = {
|
48
|
-
host: `hostname`.chomp,
|
49
|
-
service: "service #{client_id}", # placeholder
|
50
|
-
state: "",
|
51
|
-
time: 0,
|
52
|
-
description: "",
|
53
|
-
tags: [],
|
54
|
-
metric: 0,
|
55
|
-
ttl: 0
|
56
|
-
}.freeze
|
57
|
-
|
58
|
-
e_ok = event.merge(
|
59
|
-
state: "ok",
|
60
|
-
time: Time.now.to_f,
|
61
|
-
ttl: 1.0
|
62
|
-
)
|
63
|
-
|
64
|
-
if e_ok[:ttl] == 0.0
|
65
|
-
pulse e_ok # no need to bother with expiration
|
66
|
-
else
|
67
|
-
write e_ok
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
N_CONSUMERS.times do
|
73
|
-
# stores events indexed by host, service
|
74
|
-
child subscribe: "event", passive: true do ### tuplespace: sqlite
|
75
|
-
read subspace("event") do |event|
|
76
|
-
log event ### need filtering, actions, etc.
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# This could be a subspace of the event subspace.
|
82
|
-
critical_event = {
|
83
|
-
host: nil,
|
84
|
-
service: nil,
|
85
|
-
state: /critical|fatal/i,
|
86
|
-
time: nil,
|
87
|
-
description: nil,
|
88
|
-
tags: nil,
|
89
|
-
metric: nil,
|
90
|
-
ttl: nil
|
91
|
-
}
|
92
|
-
|
93
|
-
child subscribe: "event", passive: true do
|
94
|
-
read critical_event do |event|
|
95
|
-
log.error event
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# expirer: stores current events in expiration order
|
100
|
-
child subscribe: "event", passive: true do
|
101
|
-
### use rbtree
|
102
|
-
end
|
103
|
-
end
|
data/lib/tupelo/client/atdo.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
require 'tupelo/client'
|
2
|
-
require 'atdo'
|
3
|
-
|
4
|
-
module Tupelo
|
5
|
-
class Client
|
6
|
-
class AtDo < ::AtDo
|
7
|
-
def initialize client, **opts
|
8
|
-
@client = client
|
9
|
-
super **opts
|
10
|
-
end
|
11
|
-
|
12
|
-
# Accepts numeric +time+. Logs errors in +action+. Otherwise, same
|
13
|
-
# as ::AtDo.
|
14
|
-
def at time, &action
|
15
|
-
time = Time.at(time) if time.kind_of? Numeric
|
16
|
-
super time do
|
17
|
-
begin
|
18
|
-
action.call
|
19
|
-
rescue => ex
|
20
|
-
@client.log.error "error in action scheduled for #{time}:" +
|
21
|
-
" #{ex.class}: #{ex}\n #{ex.backtrace.join("\n ")}"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def make_scheduler **opts
|
28
|
-
AtDo.new self, **opts
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|