updater 0.2.2 → 0.3.0
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/Rakefile +14 -0
- data/VERSION +1 -1
- data/lib/updater/fork_worker.rb +427 -0
- data/lib/updater/orm/datamapper.rb +172 -0
- data/lib/updater/{worker.rb → thread_worker.rb} +4 -8
- data/lib/updater/update.rb +170 -149
- data/lib/updater/update_dm.rb +298 -0
- data/lib/updater/util.rb +22 -0
- data/lib/updater.rb +0 -3
- data/spec/chained_spec.rb +81 -0
- data/spec/errors_spec.rb +31 -0
- data/spec/fooclass.rb +14 -0
- data/spec/fork_worker_instance_spec.rb +56 -0
- data/spec/fork_worker_spec.rb +290 -0
- data/spec/lock_spec.rb +18 -35
- data/spec/named_request_spec.rb +36 -0
- data/spec/params_sub_spec.rb +27 -0
- data/spec/schedule_spec.rb +89 -0
- data/spec/spec_helper.rb +6 -1
- data/spec/{worker_spec.rb → thread_worker_spec.rb} +11 -11
- data/spec/update_runner_spec.rb +48 -0
- data/spec/update_spec.rb +11 -173
- data/spec/util_spec.rb +11 -0
- metadata +18 -4
data/lib/updater/update.rb
CHANGED
@@ -2,81 +2,113 @@ module Updater
|
|
2
2
|
|
3
3
|
#the basic class that drives updater
|
4
4
|
class Update
|
5
|
-
# Contains the Error class after an error is caught in +run+. Not stored to the database
|
5
|
+
# Contains the Error class after an error is caught in +run+. Not stored to the database
|
6
6
|
attr_reader :error
|
7
|
+
attr_reader :orm
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
property :target, Class
|
12
|
-
property :ident, Yaml
|
13
|
-
property :method, String
|
14
|
-
property :finder, String
|
15
|
-
property :args, Object, :lazy=>false
|
16
|
-
property :time, Integer
|
17
|
-
property :name, String
|
18
|
-
property :lock_name, String
|
19
|
-
|
20
|
-
#will be called if an error occurs
|
21
|
-
belongs_to :failure, :model=>'Update', :child_key=>[:failure_id], :nullable=>true
|
22
|
-
|
23
|
-
# Returns the Class or instance that will recieve the method call. See +Updater.at+ for
|
24
|
-
# information about how a target is derived.
|
25
|
-
def target
|
26
|
-
return @target if @ident.nil?
|
27
|
-
@target.send(@finder||:get, @ident)
|
28
|
-
end
|
29
|
-
|
30
|
-
# Send the method with args to the target.
|
31
|
-
def run(job=nil)
|
9
|
+
#Run the action on this traget compleating any chained actions
|
10
|
+
def run(job=nil,params=nil)
|
11
|
+
ret = true #put return in scope
|
32
12
|
t = target #do not trap errors here
|
33
|
-
final_args =
|
13
|
+
final_args = sub_args(job,params,@orm.method_args)
|
34
14
|
begin
|
35
|
-
t.send(@method.to_sym,*final_args)
|
15
|
+
t.send(@orm.method.to_sym,*final_args)
|
36
16
|
rescue => e
|
37
17
|
@error = e
|
38
|
-
|
39
|
-
|
40
|
-
|
18
|
+
run_chain :failure
|
19
|
+
ret = false
|
20
|
+
ensure
|
21
|
+
run_chain :success if ret
|
22
|
+
run_chain :ensure
|
23
|
+
@orm.destroy! unless @orm.persistant
|
41
24
|
end
|
42
|
-
|
43
|
-
true
|
25
|
+
ret
|
44
26
|
end
|
45
27
|
|
46
|
-
def
|
47
|
-
|
28
|
+
def method_missing(method, *args)
|
29
|
+
@orm.send(method,*args)
|
48
30
|
end
|
49
31
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
32
|
+
def target
|
33
|
+
@orm.finder.nil? ? @orm.target : @orm.target.send(@orm.finder,@orm.finder_args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(orm_inst)
|
37
|
+
@orm = orm_inst
|
38
|
+
end
|
39
|
+
|
40
|
+
def name=(n)
|
41
|
+
@orm.name=n
|
42
|
+
end
|
43
|
+
|
44
|
+
def name
|
45
|
+
@orm.name
|
62
46
|
end
|
63
47
|
|
64
|
-
|
65
|
-
|
48
|
+
#This is the appropriate valut ot use for a chanable field value
|
49
|
+
def id
|
50
|
+
@orm.id
|
66
51
|
end
|
67
52
|
|
68
|
-
def
|
69
|
-
@
|
53
|
+
def persistant?
|
54
|
+
@orm.persistant
|
70
55
|
end
|
71
56
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
57
|
+
def inspect
|
58
|
+
"#<Updater::Update target=#{target.inspect} time=#{orm.time}>"
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def sub_args(job,params,a)
|
64
|
+
a.map do |e|
|
65
|
+
begin
|
66
|
+
case e.to_s
|
67
|
+
when '__job__'
|
68
|
+
job
|
69
|
+
when '__params__'
|
70
|
+
params
|
71
|
+
when '__self__'
|
72
|
+
self
|
73
|
+
else
|
74
|
+
e
|
75
|
+
end
|
76
|
+
# For the unfortunate case where e doesn't handle to_s nicely.
|
77
|
+
# On the other hand I dare someone to find something that can be marshaled,
|
78
|
+
# but doesn't do #to_s.
|
79
|
+
rescue NoMethodError=>err
|
80
|
+
raise err unless err.message =~ /\`to_s\'/
|
81
|
+
e
|
82
|
+
end #begin
|
83
|
+
end# map
|
84
|
+
end #def
|
85
|
+
|
86
|
+
def run_chain(name)
|
87
|
+
chains = @orm.send(name)
|
88
|
+
return unless chains
|
89
|
+
chains.each do |job|
|
90
|
+
Update.new(job.target).run(self,job.params)
|
91
|
+
end
|
76
92
|
end
|
77
93
|
|
78
94
|
class << self
|
79
95
|
|
96
|
+
#This attribute must be set to some ORM that will persist the data
|
97
|
+
attr_accessor :orm
|
98
|
+
|
99
|
+
#Gets a single job form the queue, locks and runs it. it returns the number of second
|
100
|
+
#Until the next job is scheduled, or 0 is there are more current jobs, or nil if there
|
101
|
+
#are no jobs scheduled.
|
102
|
+
def work_off(worker)
|
103
|
+
inst = @orm.lock_next(worker)
|
104
|
+
new(inst).run if inst
|
105
|
+
@orm.queue_time
|
106
|
+
ensure
|
107
|
+
clear_locks(worker)
|
108
|
+
end
|
109
|
+
|
110
|
+
def clear_locks(worker); @orm.clear_locks(worker); end
|
111
|
+
|
80
112
|
# Request that the target be sent the method with args at the given time.
|
81
113
|
#
|
82
114
|
# == Parameters
|
@@ -85,14 +117,21 @@ module Updater
|
|
85
117
|
#
|
86
118
|
# target <Class | instance> . If target is a class then 'method' will be sent to that class (unless the
|
87
119
|
# finder option is used. Otherwise, the target will be assumed to be the result of
|
88
|
-
# (target.class).get(target.id).
|
89
|
-
#
|
90
|
-
#
|
120
|
+
# (target.class).get(target.id). (note: The ORM can/should override #get and #id with the proper
|
121
|
+
# methods for it's storage model.) The finder method (:get by default) and the finder_args
|
122
|
+
# (target.id by default) can be set in the options. A ORM (eg DataMapper) instance passed as the target
|
123
|
+
# will "just work." Any object can be found in this mannor is known as a 'conforming instance'. TODO:
|
124
|
+
# make ORM finder and id constants overridable for times when one ORM is used for Updater and another
|
125
|
+
# is used by the model classes.
|
91
126
|
#
|
92
127
|
# method <Symbol>. The method that will be sent to the calculated target.
|
93
128
|
#
|
94
129
|
# args <Array> a list of arguments to be sent to with the method call. Note: 'args' must be seirialiable
|
95
|
-
# with Marshal.dump.
|
130
|
+
# with Marshal.dump. The special values '__job__', '__params__', and '__self__' are replaced they are found
|
131
|
+
# in this list. Defaults to []. (note: the #to_s method will be called on all args before variable substitution
|
132
|
+
# any arg that responds with one of the special values will be replaced as noted above. E.g :__job__ . If
|
133
|
+
# something is silly enough to respond to to_s with a non-pure method you *will* have problems.
|
134
|
+
# NoMethodError is caught and handled gracefully)
|
96
135
|
#
|
97
136
|
# options <Hash> Addational options that will be used to configure the request. see Options
|
98
137
|
# section below.
|
@@ -114,8 +153,12 @@ module Updater
|
|
114
153
|
# in conjunction with the +for+ method to manipulate requests effecting an object or class after
|
115
154
|
# they are set. See +for+ for examples
|
116
155
|
#
|
117
|
-
# :failure <Updater> an other request to be run
|
118
|
-
#
|
156
|
+
# :failure, :success,:ensure <Updater::Update instance> an other request to be run when the request compleste. Usually these
|
157
|
+
# valuses will be created with the +chained+ method. As an alternative a hash with keys of Updater::Update instances and
|
158
|
+
# values of Hash may be used. The hash will be substituted for the '__param__' argument if/when the chained method is called.
|
159
|
+
#
|
160
|
+
# :persistant <true|false> if true the object will not be destroyed after the completion of its run. By default
|
161
|
+
# this is false except when time is nil.
|
119
162
|
#
|
120
163
|
# == Examples
|
121
164
|
#
|
@@ -123,22 +166,45 @@ module Updater
|
|
123
166
|
#
|
124
167
|
# f = Foo.create
|
125
168
|
# u = Updater.at(Chronic.parse('2 hours form now'),f,:bar,[]) # will run Foo.get(f.id).bar in 2 hours
|
126
|
-
def at(
|
127
|
-
|
128
|
-
hash =
|
129
|
-
|
130
|
-
hash[:
|
131
|
-
hash[:finder] = finder || :
|
132
|
-
hash[:
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
169
|
+
def at(t,target,method = nil,args=[],options={})
|
170
|
+
hash = Hash.new
|
171
|
+
hash[:time] = t.to_i unless t.nil?
|
172
|
+
|
173
|
+
hash[:target],hash[:finder],hash[:finder_args] = target_for(target)
|
174
|
+
hash[:finder] = options[:finder] || hash[:finder]
|
175
|
+
hash[:finder_args] = options[:finder_args] || hash[:finder_args]
|
176
|
+
|
177
|
+
hash[:method] = method || :process
|
178
|
+
hash[:method_args] = args
|
179
|
+
|
180
|
+
[:name,:failure,:success,:ensure].each do |opt|
|
181
|
+
hash[opt] = options[opt] if options[opt]
|
182
|
+
end
|
183
|
+
|
184
|
+
hash[:persistant] = options[:persistant] || t.nil? ? true : false
|
185
|
+
|
186
|
+
schedule(hash)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Run this job in 'time' seconds from now. See +at+ for details on expected args.
|
190
|
+
def in(t,*args)
|
191
|
+
at(time.now+t,*args)
|
140
192
|
end
|
141
193
|
|
194
|
+
# Advanced: This method allows values to be passed directly to the ORM layer's create method.
|
195
|
+
# use +at+ and friends for everyday use cases.
|
196
|
+
def schedule(hash)
|
197
|
+
new(@orm.create(hash))
|
198
|
+
end
|
199
|
+
|
200
|
+
# Create a new job having the same charistics as the old, except that 'hash' will override the original.
|
201
|
+
def reschedule(update, hash={})
|
202
|
+
new_job = update.orm.dup
|
203
|
+
new_job.update_attributes(hash)
|
204
|
+
new_job.save
|
205
|
+
new(new_job)
|
206
|
+
end
|
207
|
+
|
142
208
|
# like +at+ but with time as time.now. Generally this will be used to run a long running operation in
|
143
209
|
# asyncronously in a differen process. See +at+ for details
|
144
210
|
def immidiate(*args)
|
@@ -166,16 +232,10 @@ module Updater
|
|
166
232
|
#
|
167
233
|
# <Array[Updater]> unless name is given then only a single [Updater] instance.
|
168
234
|
def for(target,name=nil)
|
169
|
-
|
170
|
-
target = target_for(target)
|
171
|
-
if name
|
172
|
-
first(:target=>target,:ident=>ident,:name=>name)
|
173
|
-
else
|
174
|
-
all(:target=>target,:ident=>ident)
|
175
|
-
end
|
235
|
+
#TODO
|
176
236
|
end
|
177
237
|
|
178
|
-
|
238
|
+
#The time class used by Updater. See time=
|
179
239
|
def time
|
180
240
|
@@time ||= Time
|
181
241
|
end
|
@@ -188,14 +248,33 @@ module Updater
|
|
188
248
|
@@time = klass
|
189
249
|
end
|
190
250
|
|
191
|
-
#A filter for all requests that are ready to run, that is they requested to be run before or at time.now
|
251
|
+
# A filter for all requests that are ready to run, that is they requested to be run before or at time.now
|
252
|
+
# and ar not being processed by another worker
|
192
253
|
def current
|
193
|
-
|
254
|
+
@orm.current
|
255
|
+
end
|
256
|
+
|
257
|
+
#The number of jobs currently backloged in the system
|
258
|
+
def load
|
259
|
+
@orm.current_load
|
194
260
|
end
|
195
261
|
|
196
|
-
#A
|
262
|
+
#A count of how many jobs are scheduled but not yet run
|
197
263
|
def delayed
|
198
|
-
|
264
|
+
@orm.delayed
|
265
|
+
end
|
266
|
+
|
267
|
+
#How many jobs will happen at least 'start' seconds from now, but not more then finish seconds from now.
|
268
|
+
#If the second parameter is nil then it is the number of jobbs between now and the first parameter.
|
269
|
+
def future(start,finish = nil)
|
270
|
+
start, finish = [0, start] unless finish
|
271
|
+
@orm.future(start,finish)
|
272
|
+
end
|
273
|
+
|
274
|
+
#Remove all scheduled jobs. Mostly intended for testing, but may also be useful in cases of crashes
|
275
|
+
#or system corruption
|
276
|
+
def clear_all
|
277
|
+
@orm.clear_all
|
199
278
|
end
|
200
279
|
|
201
280
|
#Sets the process id of the worker process if known. If this
|
@@ -218,73 +297,15 @@ module Updater
|
|
218
297
|
@pid
|
219
298
|
end
|
220
299
|
|
221
|
-
#This returns a set of update requests.
|
222
|
-
#The first parameter is the maximum number to return (get a few other workers may be in compitition)
|
223
|
-
#The second optional parameter is a list of options to be past to DataMapper.
|
224
|
-
def worker_set(limit = 5, options={})
|
225
|
-
#TODO: add priority to this.
|
226
|
-
options = {:lock_name=>nil,:limit=>limit, :order=>[:time.asc]}.merge(options)
|
227
|
-
current.all(options)
|
228
|
-
end
|
229
|
-
|
230
|
-
####################
|
231
|
-
# Worker Functions #
|
232
|
-
####################
|
233
|
-
|
234
|
-
#Gets a single gob form the queue, locks and runs it.
|
235
|
-
def work_off(worker)
|
236
|
-
updates = worker_set
|
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
|
248
|
-
end
|
249
|
-
rescue DataObjects::ConnectionError
|
250
|
-
sleep 0.1
|
251
|
-
retry
|
252
|
-
ensure
|
253
|
-
worker.clear_locks
|
254
|
-
return queue_time
|
255
|
-
end
|
256
|
-
|
257
|
-
def queue_time
|
258
|
-
nxt = self.first(:time.not=>nil,:lock_name=>nil, :order=>[:time.asc])
|
259
|
-
return nil unless nxt
|
260
|
-
return 0 if nxt.time <= time.now.to_i
|
261
|
-
return nxt.time - time.now.to_i
|
262
|
-
end
|
263
|
-
|
264
|
-
|
265
|
-
|
266
300
|
private
|
267
301
|
|
268
|
-
#
|
302
|
+
# Given some instance return the information needed to recreate that target
|
269
303
|
def target_for(inst)
|
270
|
-
return inst if inst.kind_of? Class
|
271
|
-
inst.class
|
304
|
+
return [inst, nil, nil] if inst.kind_of? Class
|
305
|
+
[inst.class,@orm::FINDER,inst.send(orm::ID)]
|
272
306
|
end
|
273
307
|
|
274
|
-
# Compute the agrument sent to the finder method
|
275
|
-
def ident_for(target,finder=nil,args=nil)
|
276
|
-
if !(target.kind_of?(Class)) || finder
|
277
|
-
args || target.id
|
278
|
-
end
|
279
|
-
#Otherwize the target is the class and ident should be nil
|
280
|
-
end
|
281
|
-
|
282
|
-
end
|
283
|
-
|
284
|
-
#:nodoc:
|
285
|
-
def inspect
|
286
|
-
"#<Updater id=#{id} target=#{target.inspect} time=#{time}>"
|
287
308
|
end
|
288
|
-
end
|
309
|
+
end #class Update
|
289
310
|
|
290
|
-
end
|
311
|
+
end #Module Updater
|