updater 0.3.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ GEM_VERSION = File.read(VERSION_FILE).strip
9
9
  AUTHOR = "John F. Miller"
10
10
  EMAIL = "emperor@antarestrader.com"
11
11
  HOMEPAGE = "http://blog.antarestrader.com"
12
- SUMMARY = "Plugin for the delayed calling of methods particularly DataMapper model instance and class methods."
12
+ SUMMARY = "A Gem for queuing methods for later calling which is ORM Agnostic, and has advanced Error Handling"
13
13
 
14
14
  spec = Gem::Specification.new do |s|
15
15
  s.name = GEM_NAME
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.3.2
@@ -29,7 +29,8 @@ module Updater
29
29
  @logger = Logger.new(STDOUT)
30
30
  @logger.level = Logger::WARN
31
31
  end
32
- logger.info "***Setting Up Master Process***"
32
+ logger.warn "***Setting Up Master Process***"
33
+ logger.warn " Pid = #{Process.pid}"
33
34
  @max_workers = options[:workers] || 3
34
35
  logger.info "Max Workers set to #{@max_workers}"
35
36
  @timeout = options[:timeout] || 60
@@ -92,8 +93,6 @@ module Updater
92
93
  # * :sockets: 0 or more IO objects that should wake up master to alert it that new data is availible
93
94
 
94
95
  def start(stream,options = {})
95
- logger.info "=== ForkWorker Start ==="
96
- logger.info " Pid = #{Process.pid}"
97
96
  initial_setup(options) #need this for logger
98
97
  logger.info "*** Starting Master Process***"
99
98
  @stream = stream
@@ -318,7 +317,8 @@ module Updater
318
317
  @pipe.last.write '.'
319
318
  trap(:QUIT,"IGNORE")
320
319
  end
321
- trap(:TERM) { Update.clear_locks(self); exit }
320
+ trap(:TERM) { Update.clear_locks(self); Process.exit!(0) }
321
+ logger.info "#{name} is on-line"
322
322
  while @continue do
323
323
  heartbeat
324
324
  begin
@@ -25,7 +25,7 @@ module Updater
25
25
  property :finder_args, Yaml
26
26
  property :method, String
27
27
  property :method_args, Object, :lazy=>false
28
- property :name, String
28
+ property :name, String, :length=>255
29
29
  property :lock_name, String
30
30
  property :persistant, Boolean
31
31
 
@@ -68,6 +68,10 @@ module Updater
68
68
  target = target.orm if target.kind_of? Updater::Update
69
69
  chains.new(:target=>target,:occasion=>mode)
70
70
  end
71
+ when nil
72
+ chains=[]
73
+ else
74
+ raise ArgumentError
71
75
  end
72
76
  end
73
77
 
@@ -135,7 +139,7 @@ module Updater
135
139
  DMChained.all.destroy!
136
140
  end
137
141
 
138
- def for(mytarget, myfinder, myfinder_args, myname)
142
+ def for(mytarget, myfinder, myfinder_args, myname=nil)
139
143
  #TODO
140
144
  end
141
145
 
@@ -164,8 +168,8 @@ module Updater
164
168
  belongs_to :caller, :model=>Updater::ORM::DataMapper, :child_key=>[:caller_id]
165
169
  belongs_to :target, :model=>Updater::ORM::DataMapper, :child_key=>[:target_id]
166
170
 
167
- property :params, Yaml, :nullable=>true
168
- property :occasion, String, :nullable=>false
171
+ property :params, Object, :nullable=>true #:required=>false
172
+ property :occasion, String, :nullable=>false #:required=>true
169
173
  end
170
174
 
171
175
  end#ORM
@@ -0,0 +1,239 @@
1
+ module Updater
2
+ module ORM
3
+
4
+ # This is the root class for ORM inplimentations. It provides some very
5
+ # basicfunctionality that may be useful for implimenting actuall ORM's
6
+ # but cannot itself be run or instantiated. The documentation for this
7
+ # class also serves as the cannonical reference for the ORM API.
8
+ #
9
+ # for purposes of this documentation instances of a class inheriting form
10
+ # this class will be refered to as 'jobs.'
11
+ #
12
+ # In addation to the methods listed below, it MUST provide accessors for the
13
+ # 12 fields below. Most ORMs will add these when the fields are setup
14
+ #
15
+ # == Fields
16
+ #
17
+ # These fields with thier getters and setters (except id which is read only) are expected
18
+ # to be implimented in every ORM implimention. Other fields may be implimented as
19
+ # well, but clients SHOULD NOT depend on or manipulate them. ORM will need to
20
+ # impliment some persistant way to lock records, and should do so in such a way as the
21
+ # name of the worker can be tracked, and jobs cleared for that worker should it crach
22
+ # if the underlying datastore allows.
23
+ #
24
+ # id: a unique value asigned to each job. For purposes of the API it is a black box
25
+ # which should be paseed to the ClassMethods#get method to retrieve the job. If
26
+ # the ORM uses a different name for this value such as _id or key, a reader method
27
+ # must be implimented as id.
28
+ #
29
+ # time [Integer]: This is the time in seconds at which the job should be run. It is
30
+ # always in reference to Updater::Update.time (by default Time). This value will be
31
+ # nil for chained methods.
32
+ #
33
+ # target [Class]: The class of the target for this job. the API spesifies that it must be
34
+ # a Ruby class currently in scope in both the workers' and clients' frames of reference.
35
+ # (see configuration documentation for how to achieve this.) The writer must accept an
36
+ # actual class, which it may store in the datastore as a string (by calling to_s on it). The
37
+ # reader method must return the actual class by if ecessary calling Object.const_get(str)
38
+ # with the stored value.
39
+ #
40
+ # finder [String]: A method to call on the Target in orderto get the target instance.
41
+ # The API limits its length to no more then 50 charactors. If the class itself is the
42
+ # target, this value will either not be set or set to nil. The reader should MUST return
43
+ # nil in this case.
44
+ #
45
+ # finder_args [Array]: A possibly complex array of valuse that will be paseed to the
46
+ # finder method in order to retrieve the target instance. The API spesifies that the
47
+ # array and all subelements must impliment the #to_yaml and #to_json method in
48
+ # addation to being Marshalable. If the class itself is the
49
+ # target, this value will either not be set or set to nil. The reader should MUST return
50
+ # nil in this case.
51
+ #
52
+ # method [String]: The method to be sent to the target instance. The API limits this value
53
+ # to 50 charictars. It MAY NOT be nil or empty.
54
+ #
55
+ # method_args [Array]: A possibly complex array of values to pass to the spesified method of
56
+ # the target instance. It must be marshalable. The ORM layer is responcible to Marshal.dump
57
+ # and Marshal.load these values.
58
+ #
59
+ # name [String]: If the ORM impliments the +for+ method, then it MUST store a name which the
60
+ # API spesifies SHOULD be unique per target. The ORM SHOULD NOT enforce this restriction, but
61
+ # MAY assume it. ORM's that do not impliment +for+ must none the less have a #name= method
62
+ # that returns the value passed to it (as is normal with setter methods) and must not raise an error
63
+ # when a hash of values includes name. It must also respond to the name method with nil. When
64
+ # inplimented, name may be no longer then 255 characters.
65
+ #
66
+ # persistant [Boolean]: if this value is set to true a worker will not call destroy after running the job.
67
+ # If it is nil or not set it may be assumed to be false, and ORM may return nil instead of false in this
68
+ # case.
69
+ #
70
+ # == Chained Jobs
71
+ #
72
+ # Chained Jobs are run after a job and allow for various function to take place such as logging and
73
+ # error handleing. There are three(3) categories for chaining :failure, :success, and :ensure. The
74
+ # ORM must impliment a getter and setter for each as described below. This version does not
75
+ # inpliment it, but ORMs should be be designed in such a way that :prior and :instead chains can be
76
+ # added in future version of this API.
77
+ #
78
+ # === Getters:
79
+ # getters should return an array of structures (Struct or equivelent) representing the chained jobs
80
+ # for this job. The structure should have three(3) members and MAY be read-only.
81
+ #
82
+ # caller: An instance of the ORM class for the job in question (i.e. self)
83
+ #
84
+ # target: An instance of the ORM class for the chained job
85
+ #
86
+ # params: a Hash or other object that will be substituted for the special value '__params__' when calling
87
+ # the target job.
88
+ #
89
+ # The object returned may have other methods and functionality. Clients SHOULD NOT antisipate or use
90
+ # these methods.
91
+ #
92
+ # === Setters:
93
+ # setters must accept five(5) differnt types of input. Except as described below setters are NOT distructive,
94
+ # that is job.failure=logme adds logme to the list of failure jobs and does not remove jobs that may have previously
95
+ # been chained. Clients should call save after using the setter to write the changes to disk
96
+ #
97
+ # ORM or Updater::Update: Add this job to the chain, with no parameters. Updater::Update#orm will give the job.
98
+ #
99
+ # Array<ORM or Updater::Update>: Add each job in the array to the chain
100
+ #
101
+ # Hash(<ORM or Updater::Update>, params): Add the keys to the chain with the valuse as params. Clients
102
+ # should note that it is not possible to add multiple calls to the same job using this method.
103
+ #
104
+ # nil: remove all jobs from this chain. Clients Note, that this is the only way to remove a previously added
105
+ # job from a chain.
106
+ class Base
107
+
108
+ # Every ORM should set this constant to a symbol that matches the most
109
+ # obvious method used to retrive a known instance. :get or:find are likely
110
+ # candidates.
111
+ FINDER= nil
112
+
113
+ # every ORM should set this to a method when called on an object producted by
114
+ # that ORM will give a value that can be passed to the FINDER method to retrieve
115
+ # the object from the datastore. :id, _id, or :key are likely candidates
116
+ ID = nil
117
+
118
+ # Workers will call this method on a job before running it to insure that in the case
119
+ # of multiple workers hiting the same queue only one will run the job. The worker
120
+ # MUST pass itself to Lock, and the implimentation MAY use the name of the worker
121
+ # to identify who has locked this job.
122
+ #
123
+ # If a worker is successfully able to lock this job, or has already locked the Job, this
124
+ # method MUST return a true value. If a lock was unsuccessful, it MUST return the
125
+ # value false, and MAY use the 'say' method of the suplied worker to explain why a lock
126
+ # could not be aquired.
127
+ def lock(worker)
128
+ NotImplementedError
129
+ end
130
+
131
+ #write any changes made to the job back to the datastore.
132
+ def save
133
+ NotImplementedError
134
+ end
135
+
136
+ #Remove this job from the datastore.
137
+ def destroy
138
+ NotImplementedError
139
+ end
140
+
141
+ end
142
+
143
+ class ClassMethods
144
+
145
+ # When passed the value returned by the #id method of a job, this method must return
146
+ # that job from the datastore.
147
+ def get(id)
148
+ NotImplementedError
149
+ end
150
+
151
+ # The hash keys are symbols for the one of the 12 field values listed in the intro to the
152
+ # ORM::Base class. The values are the actual values that should be returned by the
153
+ # accessor methods. Depending on the datastore some values may need to be marshaled
154
+ # converted, etc.. before being written to the datastore.
155
+ def create(hash)
156
+ NotImplementedError
157
+ end
158
+
159
+ # This method returns all jobs that are now ready to run, that is thier time valuse is less
160
+ # then or equal to the value returned by calling now on the registered time class (tnow).
161
+ def current
162
+ NotImplementedError
163
+ end
164
+
165
+ # Returns a count of how many jobs are currently ready to run.
166
+ def current_load
167
+ NotImplementedError
168
+ end
169
+
170
+ # Runurns a count of the number of jobs scheduled to run at a later time, that is there
171
+ # time value is strictly greater then the value returned by calling now on the registered
172
+ # time class(tnow)
173
+ def delayed
174
+ NotImplementedError
175
+ end
176
+
177
+ # Returns a count of how may jobs are curently scheduled between start and finish seconds
178
+ # from now. e.g future(0,60) would tell you how many jobs will run in the next minute. This
179
+ # function is used to adjust the number of workers needed as well as for monitering.
180
+ def future(start, finish)
181
+ NotImplementedError
182
+ end
183
+
184
+ # Returns the number os seconds until the next job will be ready to run. If there are no
185
+ # Jobs in the queue it returns nil, if there is at least one job ready to run it MUST return
186
+ # 0. This may be an apporximation or the value may be cached for brief periods to improve
187
+ # datastore performance.
188
+ def queue_time
189
+ NotImplementedError
190
+ end
191
+
192
+ # Locks to a worker and returns a job that is ready to run. Workers will call this when they are
193
+ # ready for another job. In general it should lock jobs in the order they were recieved or scheduled,
194
+ # but strict ordering is not a requirement. (c.f. delayed_job). If there are current jobs, this method
195
+ # MUST return one which has been locked successfully, internally trying successive current jobs if
196
+ # the first one fails to lock. It MUST NOT raise an error or return nil if the datastore is temerarly
197
+ # busy. Instead it must wait until it can either get access to and lock a record, or prove that no jobs
198
+ # are current.
199
+ #
200
+ # In the event that there are no current jobs left in the datastore this method should retunr nil. The
201
+ # should inturperate this as a sign that the queue is empty and consult +queue_time+ to determine
202
+ # how long to wait for the next job.
203
+ def lock_next(worker)
204
+ NotImplementedError
205
+ end
206
+
207
+ # This method unlocks and makes availible any and all jobs which have been locked by the worker.
208
+ # Workers are uniquely identified by the +name+ method. This is an indication that the worker has
209
+ # died or been killed and cannot complete its job.
210
+ def clear_locks(worker)
211
+ NotImplementedError
212
+ end
213
+
214
+ # Compleatly remove all jobs and associeted data from the datastore including chained
215
+ # Methods.
216
+ def clear_all
217
+ NotImplementedError
218
+ end
219
+
220
+ # Optional, but strongly recomended.
221
+ #
222
+ # For any datastore that permits, return and Array of all delayed, chained, and current but not locked jobs that reference
223
+ # mytarget, myfinder, and myfinder_args, that is they clearly have the spesified Target. Optionally, limit the result to
224
+ # return the first job that also has a name value of myname. The name value is spesified as unique per target so the
225
+ # which record is returned in the case that multiple jobs fro the same target share the same name is undefined.
226
+ def for(mytarget, myfinder, myfinder_args, myname=nil)
227
+ NotImplementedError
228
+ end
229
+
230
+ private
231
+
232
+ #Short hand method that retruns the current time value that this queue is using.
233
+ def tnow
234
+ Updater::Update.time.now.to_i
235
+ end
236
+
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,113 @@
1
+ require 'logger'
2
+ require 'yaml'
3
+ require 'socket'
4
+ require 'erb'
5
+
6
+ module Updater
7
+ class Setup
8
+ class << self
9
+ def start
10
+ new(config_file).start
11
+ end
12
+
13
+ def stop
14
+ new(config_file).stop
15
+ end
16
+
17
+ def monitor
18
+
19
+ end
20
+
21
+ def config_file
22
+ if ENV['UPDATE_CONFIG'] && File.exists(ENV['UPDATE_CONFIG'])
23
+ ENV['UPDATE_CONFIG']
24
+ else
25
+ (Dir.glob('{config,.}/updater.config') + Dir.glob('.updater')).first
26
+ end
27
+ end
28
+ end
29
+
30
+ ROOT = File.dirname(self.config_file)
31
+
32
+ def initialize(file_or_hash)
33
+ @options = file_or_hash.kind_of?(Hash) ? file_or_hash : load_file(file_or_hash)
34
+ @options[:pid_file] ||= File.join(ROOT,'updater.pid')
35
+ @options[:host] ||= "localhost"
36
+ @logger = Logger.new(@options[:log_file] || STDOUT)
37
+ level = Logger::SEV_LABEL.index(@options[:log_level].upcase) if @options[:log_level]
38
+ @logger.level = level || Logger::WARN
39
+ end
40
+
41
+ def start
42
+ @logger.warn "Starting Loop"
43
+ pid = Process.fork do
44
+ _start
45
+ end
46
+ @logger.warn "Rake Successfully started Master Loop at pid #{pid}"
47
+ end
48
+
49
+ def stop
50
+ Process.kill("TERM",File.read(@options[:pid_file]).to_i)
51
+ end
52
+
53
+ def client
54
+
55
+ end
56
+
57
+ private
58
+
59
+ def _start
60
+ #set ORM
61
+ require 'updater/orm/datamapper'
62
+ Updater::Update.orm = ORM::DataMapper
63
+
64
+ #init DataStore
65
+ DataMapper.logger = @logger
66
+ DataMapper.setup(:default, :adapter=>'sqlite3', :database=>'./simulated.db')
67
+
68
+ #load Models
69
+
70
+ models = @options[:models] || Dir.glob('./app/models/**/*.rb')
71
+ models.each do |file|
72
+ require file
73
+ end
74
+
75
+ #establish Connections
76
+ #Unix Socket -- name at @options[:socket]
77
+ if @options[:socket]
78
+ File.unlink @options[:socket] if File.exists? @options[:socket]
79
+ @options[:sockets] ||= []
80
+ @options[:sockets] << UNIXServer.new(@options[:socket])
81
+ end
82
+
83
+ #UDP potentially unsafe user monitor server for Authenticated Connections (TODO)
84
+ if @options[:udp]
85
+ @options[:sockets] ||= []
86
+ udp = UDPSocket.new
87
+ udp.bind(@options[:host],@options[:udp])
88
+ @options[:sockets] << udp
89
+ end
90
+
91
+ #TCP Unsafe user monitor server for Authenticated Connections (TODO)
92
+ if @options[:tcp]
93
+ @options[:sockets] ||= []
94
+ @options[:sockets] << TCPServer.new(@options[:host],@options[:tcp])
95
+ end
96
+
97
+ #Log PID
98
+ File.open(@options[:pid_file],'w') { |f| f.write(Process.pid.to_s)}
99
+ #start Worker
100
+ require 'updater/fork_worker'
101
+ worker_class = ForkWorker
102
+ worker_class.logger = @logger
103
+ worker_class.start(@options)
104
+ end
105
+
106
+ def load_file(file)
107
+ return {} if file.nil?
108
+ file = File.open(file) if file.kind_of?(String)
109
+ @config_file = File.expand_path(file.path)
110
+ YAML.load(ERB.new(File.read(file)).result(binding)) || {}
111
+ end
112
+ end
113
+ end
Binary file
data/lib/updater/tasks.rb CHANGED
@@ -1,6 +1,18 @@
1
+ require 'updater'
2
+ require 'updater/setup'
1
3
  namespace :updater do
2
- desc "Do something for updater"
3
- task :default do
4
- puts "updater doesn't do anything"
4
+ desc "Start processing jobs using the settings in updater.config"
5
+ task :start do
6
+ Updater::Setup.start
7
+ end
8
+
9
+ desc "Stop procedssing jobs"
10
+ task :stop do
11
+ Updater::Setup.stop
12
+ end
13
+
14
+ desc "Start monitering the Job queue <Planed>"
15
+ task :monitor do
16
+ puts "No Implimentation (yet). This feature will be comming \"any day now.\""
5
17
  end
6
18
  end
@@ -1,4 +1,6 @@
1
1
  module Updater
2
+ class TargetMissingError < StandardError
3
+ end
2
4
 
3
5
  #the basic class that drives updater
4
6
  class Update
@@ -9,9 +11,9 @@ module Updater
9
11
  #Run the action on this traget compleating any chained actions
10
12
  def run(job=nil,params=nil)
11
13
  ret = true #put return in scope
12
- t = target #do not trap errors here
13
- final_args = sub_args(job,params,@orm.method_args)
14
14
  begin
15
+ t = target
16
+ final_args = sub_args(job,params,@orm.method_args)
15
17
  t.send(@orm.method.to_sym,*final_args)
16
18
  rescue => e
17
19
  @error = e
@@ -20,7 +22,7 @@ module Updater
20
22
  ensure
21
23
  run_chain :success if ret
22
24
  run_chain :ensure
23
- @orm.destroy! unless @orm.persistant
25
+ @orm.destroy unless @orm.persistant
24
26
  end
25
27
  ret
26
28
  end
@@ -30,7 +32,9 @@ module Updater
30
32
  end
31
33
 
32
34
  def target
33
- @orm.finder.nil? ? @orm.target : @orm.target.send(@orm.finder,@orm.finder_args)
35
+ target = @orm.finder.nil? ? @orm.target : @orm.target.send(@orm.finder,@orm.finder_args)
36
+ raise TargetMissingError, "Class:'#{@orm.target}' Finder:'#{@orm.finder}', Args:'#{@orm.finder_args.inspect}'" unless target
37
+ target
34
38
  end
35
39
 
36
40
  def initialize(orm_inst)
@@ -195,6 +199,7 @@ module Updater
195
199
  # use +at+ and friends for everyday use cases.
196
200
  def schedule(hash)
197
201
  new(@orm.create(hash))
202
+ signal_worker
198
203
  end
199
204
 
200
205
  # Create a new job having the same charistics as the old, except that 'hash' will override the original.
@@ -290,7 +295,7 @@ module Updater
290
295
  Process::kill 0, @pid
291
296
  @pid
292
297
  rescue Errno::ESRCH, ArgumentError
293
- raise ArgumentError "PID was invalid"
298
+ raise ArgumentError, "PID was invalid"
294
299
  end
295
300
 
296
301
  def pid
@@ -298,6 +303,11 @@ module Updater
298
303
  end
299
304
 
300
305
  private
306
+ def signal_worker
307
+ if @pid
308
+ Process::kill "USR2", @pid
309
+ end
310
+ end
301
311
 
302
312
  # Given some instance return the information needed to recreate that target
303
313
  def target_for(inst)
data/spec/fooclass.rb CHANGED
@@ -9,6 +9,10 @@
9
9
  Foo.bar(:instance,*args)
10
10
  end
11
11
 
12
+ def self.bar(*args)
13
+
14
+ end
15
+
12
16
  end
13
17
 
14
18
  Foo.auto_migrate!
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.3.0
4
+ version: 0.3.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: 2010-02-09 00:00:00 -08:00
12
+ date: 2010-02-18 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -52,7 +52,7 @@ dependencies:
52
52
  - !ruby/object:Gem::Version
53
53
  version: 0.2.3
54
54
  version:
55
- description: Plugin for the delayed calling of methods particularly DataMapper model instance and class methods.
55
+ description: A Gem for queuing methods for later calling which is ORM Agnostic, and has advanced Error Handling
56
56
  email: emperor@antarestrader.com
57
57
  executables: []
58
58
 
@@ -68,12 +68,15 @@ files:
68
68
  - Rakefile
69
69
  - VERSION
70
70
  - lib/updater.rb
71
+ - lib/updater/setup.rb
71
72
  - lib/updater/util.rb
72
73
  - lib/updater/update.rb
73
74
  - lib/updater/fork_worker.rb
74
75
  - lib/updater/update_dm.rb
76
+ - lib/updater/simulated.db
75
77
  - lib/updater/tasks.rb
76
78
  - lib/updater/thread_worker.rb
79
+ - lib/updater/orm/orm.rb
77
80
  - lib/updater/orm/datamapper.rb
78
81
  - spec/fork_worker_instance_spec.rb
79
82
  - spec/thread_worker_spec.rb
@@ -117,6 +120,6 @@ rubyforge_project:
117
120
  rubygems_version: 1.3.5
118
121
  signing_key:
119
122
  specification_version: 3
120
- summary: Plugin for the delayed calling of methods particularly DataMapper model instance and class methods.
123
+ summary: A Gem for queuing methods for later calling which is ORM Agnostic, and has advanced Error Handling
121
124
  test_files: []
122
125