updater 0.2.1 → 0.2.2

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.
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