updater 0.3.0 → 0.3.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/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