updater 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/updater/update.rb +47 -17
- data/lib/updater/worker.rb +46 -14
- data/spec/worker_spec.rb +39 -0
- metadata +2 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.2
|
data/lib/updater/update.rb
CHANGED
@@ -12,7 +12,7 @@ module Updater
|
|
12
12
|
property :ident, Yaml
|
13
13
|
property :method, String
|
14
14
|
property :finder, String
|
15
|
-
property :args, Object
|
15
|
+
property :args, Object, :lazy=>false
|
16
16
|
property :time, Integer
|
17
17
|
property :name, String
|
18
18
|
property :lock_name, String
|
@@ -130,7 +130,13 @@ module Updater
|
|
130
130
|
hash[:ident] = ident_for(target,finder,finder_args)
|
131
131
|
hash[:finder] = finder || :get
|
132
132
|
hash[:time] = time
|
133
|
-
create(hash.merge(options))
|
133
|
+
ret = create(hash.merge(options))
|
134
|
+
Process.kill('USR1',pid) if pid
|
135
|
+
ret
|
136
|
+
rescue Errno::ESRCH
|
137
|
+
@pid = nil
|
138
|
+
puts "PID invalid"
|
139
|
+
#log this as well
|
134
140
|
end
|
135
141
|
|
136
142
|
# like +at+ but with time as time.now. Generally this will be used to run a long running operation in
|
@@ -192,12 +198,32 @@ module Updater
|
|
192
198
|
all(:time.gt=>time.now.to_i)
|
193
199
|
end
|
194
200
|
|
201
|
+
#Sets the process id of the worker process if known. If this
|
202
|
+
#is set then an attempt will be made to signal the worker any
|
203
|
+
#time a new update is made.
|
204
|
+
#
|
205
|
+
#If pid is not set, or is set to nil then the scheduleing program
|
206
|
+
#is responcible for waking-up a potentially sleeping worker process
|
207
|
+
#in another way.
|
208
|
+
def pid=(p)
|
209
|
+
return @pid = nil unless p #tricky assignment in return
|
210
|
+
@pid = Integer("#{p}")
|
211
|
+
Process::kill 0, @pid
|
212
|
+
@pid
|
213
|
+
rescue Errno::ESRCH, ArgumentError
|
214
|
+
raise ArgumentError "PID was invalid"
|
215
|
+
end
|
216
|
+
|
217
|
+
def pid
|
218
|
+
@pid
|
219
|
+
end
|
220
|
+
|
195
221
|
#This returns a set of update requests.
|
196
222
|
#The first parameter is the maximum number to return (get a few other workers may be in compitition)
|
197
223
|
#The second optional parameter is a list of options to be past to DataMapper.
|
198
224
|
def worker_set(limit = 5, options={})
|
199
225
|
#TODO: add priority to this.
|
200
|
-
options = {:limit=>limit, :order=>[:time.
|
226
|
+
options = {:lock_name=>nil,:limit=>limit, :order=>[:time.asc]}.merge(options)
|
201
227
|
current.all(options)
|
202
228
|
end
|
203
229
|
|
@@ -208,24 +234,28 @@ module Updater
|
|
208
234
|
#Gets a single gob form the queue, locks and runs it.
|
209
235
|
def work_off(worker)
|
210
236
|
updates = worker_set
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
t = u.run_with_lock(worker)
|
223
|
-
return queue_time unless nil == t
|
237
|
+
unless updates.empty?
|
238
|
+
#concept copied form delayed_job. If there are a number of
|
239
|
+
#different processes working on the queue, the niave approch
|
240
|
+
#would result in every instance trying to lock the same record.
|
241
|
+
#by shuffleing our results we greatly reduce the chances that
|
242
|
+
#multilpe workers try to lock the same process
|
243
|
+
updates = updates.to_a.sort_by{rand()}
|
244
|
+
updates.each do |u|
|
245
|
+
t = u.run_with_lock(worker)
|
246
|
+
break unless nil == t
|
247
|
+
end
|
224
248
|
end
|
249
|
+
rescue DataObjects::ConnectionError
|
250
|
+
sleep 0.1
|
251
|
+
retry
|
252
|
+
ensure
|
253
|
+
worker.clear_locks
|
254
|
+
return queue_time
|
225
255
|
end
|
226
256
|
|
227
257
|
def queue_time
|
228
|
-
nxt = self.first(:time.not=>nil, :order=>[:time.
|
258
|
+
nxt = self.first(:time.not=>nil,:lock_name=>nil, :order=>[:time.asc])
|
229
259
|
return nil unless nxt
|
230
260
|
return 0 if nxt.time <= time.now.to_i
|
231
261
|
return nxt.time - time.now.to_i
|
data/lib/updater/worker.rb
CHANGED
@@ -19,24 +19,19 @@ module Updater
|
|
19
19
|
|
20
20
|
def start
|
21
21
|
say "*** Starting job worker #{@name}"
|
22
|
-
t =
|
23
|
-
loop do
|
24
|
-
delay = Update.work_off(self)
|
25
|
-
break if $exit
|
26
|
-
sleep delay
|
27
|
-
break if $exit
|
28
|
-
end
|
29
|
-
clear_locks
|
30
|
-
end
|
22
|
+
@t = run_job_loop
|
31
23
|
|
32
|
-
trap('TERM') { terminate_with t }
|
33
|
-
trap('INT') { terminate_with t }
|
24
|
+
trap('TERM') { terminate_with @t }
|
25
|
+
trap('INT') { terminate_with @t }
|
34
26
|
|
35
27
|
trap('USR1') do
|
36
|
-
|
37
|
-
|
28
|
+
old_proc = trap('USR1','IGNORE')
|
29
|
+
run_loop
|
30
|
+
trap('USR1',old_proc)
|
38
31
|
end
|
39
32
|
|
33
|
+
Thread.pass
|
34
|
+
|
40
35
|
sleep unless $exit
|
41
36
|
end
|
42
37
|
|
@@ -49,12 +44,49 @@ module Updater
|
|
49
44
|
Update.all(:lock_name=>@name).update(:lock_name=>nil)
|
50
45
|
end
|
51
46
|
|
47
|
+
def stop
|
48
|
+
raise RuntimeError unless @t
|
49
|
+
terminate_with @t
|
50
|
+
end
|
51
|
+
|
52
|
+
def run_loop
|
53
|
+
if @t.alive?
|
54
|
+
@t.wakeup #calling run here is a Bad Idea
|
55
|
+
else
|
56
|
+
say " ~~ Restarting Job Loop"
|
57
|
+
@t = run_job_loop
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
52
61
|
private
|
53
62
|
|
63
|
+
def run_job_loop
|
64
|
+
Thread.new do
|
65
|
+
loop do
|
66
|
+
begin
|
67
|
+
delay = Update.work_off(self)
|
68
|
+
break if $exit
|
69
|
+
if delay
|
70
|
+
sleep delay
|
71
|
+
else
|
72
|
+
sleep
|
73
|
+
end
|
74
|
+
break if $exit
|
75
|
+
rescue
|
76
|
+
say "Caught exception in Job Loop"
|
77
|
+
sleep 0.1
|
78
|
+
retry
|
79
|
+
end
|
80
|
+
end
|
81
|
+
say "Worker thread exiting!"
|
82
|
+
clear_locks
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
54
86
|
def terminate_with(t)
|
55
87
|
say "Exiting..."
|
56
88
|
$exit = true
|
57
|
-
t.run
|
89
|
+
t.run if t.alive?
|
58
90
|
say "Forcing Shutdown" unless status = t.join(15) #Nasty inline assignment
|
59
91
|
clear_locks
|
60
92
|
exit status ? 0 : 1
|
data/spec/worker_spec.rb
CHANGED
@@ -17,6 +17,43 @@ describe Worker do
|
|
17
17
|
Worker.new.name.should be_a String
|
18
18
|
end
|
19
19
|
|
20
|
+
describe "loop thread control:" do
|
21
|
+
|
22
|
+
class Foo
|
23
|
+
include DataMapper::Resource
|
24
|
+
|
25
|
+
property :id, Serial
|
26
|
+
property :name, String
|
27
|
+
|
28
|
+
def bar(*args)
|
29
|
+
Foo.bar(:instance,*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
Foo.auto_migrate!
|
35
|
+
|
36
|
+
specify "The loop should run when the worker is started" do
|
37
|
+
pending
|
38
|
+
worker = Worker.new(:quiet=>true, :name=>"testing")
|
39
|
+
Update.should_receive(:work_off).with(worker).once.and_return(nil)
|
40
|
+
t = Thread.new do
|
41
|
+
worker.start
|
42
|
+
end
|
43
|
+
t.run
|
44
|
+
worker.stop
|
45
|
+
t.kill unless t.join(0.1) #you've got 0.1 seconds to finish or die
|
46
|
+
end
|
47
|
+
|
48
|
+
specify "The loop should run with USR1 signal" do
|
49
|
+
pending
|
50
|
+
t = Thread.new do
|
51
|
+
worker.start
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
20
57
|
end
|
21
58
|
|
22
59
|
describe "working off jobs:" do
|
@@ -63,6 +100,8 @@ describe "working off jobs:" do
|
|
63
100
|
it "should return the number of seconds till the next job if there are no jobs to be run" do
|
64
101
|
Timecop.freeze(Time.now)
|
65
102
|
u1 = Update.at(Time.now + 30, Foo,:bar,[:arg1])
|
103
|
+
Update.at(Time.now + 35, Foo,:bar,[:arg1])
|
104
|
+
Update.at(Time.now + 1000, Foo,:bar,[:arg1])
|
66
105
|
Update.work_off(Worker.new(:name=>"first", :quiet=>true)).should == 30
|
67
106
|
end
|
68
107
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: updater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John F. Miller
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-10-
|
12
|
+
date: 2009-10-24 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|