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 CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.2.2
@@ -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.desc]}.merge(options)
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
- if updates.empty?
212
- return queue_time
213
- end
214
-
215
- #concept copied form delayed_job. If there are a number of
216
- #different processes working on the queue, the niave approch
217
- #would result in every instance trying to lock the same record.
218
- #by shuffleing our results we greatly reduce the chances that
219
- #multilpe workers try to lock the same process
220
- updates = updates.to_a.sort_by{rand()}
221
- updates.each do |u|
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.desc])
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
@@ -19,24 +19,19 @@ module Updater
19
19
 
20
20
  def start
21
21
  say "*** Starting job worker #{@name}"
22
- t = Thread.new do
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
- say "Wakeup Signal Caught"
37
- t.run
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.1
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-21 00:00:00 -07:00
12
+ date: 2009-10-24 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency