omg-monkeyjob 0.1.6

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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Igor Gunko
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,22 @@
1
+ === Introduction
2
+
3
+
4
+ === Installation
5
+ gem install omg-monkeyjob --source http://gems.github.com
6
+
7
+ === Usage
8
+
9
+ === More info in docs
10
+ http://rdoc.info/projects/omg/monkeyjob
11
+
12
+ === Bugs & such
13
+ Please report via Github issue tracking.
14
+
15
+ === See also
16
+ * http://github.com/omg/threadpool -- Thread pool implementation
17
+ * http://github.com/omg/xmlnuts -- Ruby <-> XML mapping
18
+ * http://github.com/omg/statelogic -- A simple state machine for ActiveRecord
19
+
20
+
21
+ Free hint: If you liek mudkipz^W^Wfeel like generous today you can tip me at http://tipjoy.com/u/pisuka
22
+
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ $KCODE = 'u'
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'rake/clean'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/testtask'
9
+
10
+ Rake::GemPackageTask.new(Gem::Specification.load('threadpool.gemspec')) do |p|
11
+ p.need_tar = true
12
+ p.need_zip = true
13
+ end
14
+
15
+ Rake::RDocTask.new do |rdoc|
16
+ files =['README.rdoc', 'MIT-LICENSE', 'lib/**/*.rb']
17
+ rdoc.rdoc_files.add(files)
18
+ rdoc.main = "README.rdoc" # page to start on
19
+ rdoc.title = "XmlNuts Documentation"
20
+ rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
21
+ rdoc.options << '--line-numbers' << '--inline-source'
22
+ end
23
+
24
+ Rake::TestTask.new do |t|
25
+ t.test_files = FileList['test/**/*.rb']
26
+ end
27
+
@@ -0,0 +1,15 @@
1
+ module Monkey
2
+ class Broker
3
+ class ActiveRecord
4
+ class JobModel < ::ActiveRecord::Base
5
+ set_table_name 'monkey_jobs'
6
+ serialize :handler
7
+
8
+ named_scope :candidates, proc {|queues|
9
+ {:conditions => ["#{queues.blank? ? 'queue IS NULL' : 'queue IN (:queues)'} AND run_at <= :now",
10
+ {:now => Time.now, :queues => queues}], :order => 'run_at, id'}
11
+ }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,86 @@
1
+ require 'monitor'
2
+
3
+ begin
4
+ require 'activerecord'
5
+ rescue MissingSourceFile => e
6
+ raise $!.exception('missing activerecord!')
7
+ end
8
+
9
+ module Monkey
10
+ class Broker
11
+ class ActiveRecord < Broker
12
+ class Cookie < Job::Cookie::Impl
13
+ attr_reader :queue, :id
14
+
15
+ def initialize(broker, jm)
16
+ super(jm.handler)
17
+ @broker, @queue, @id = broker, jm.queue, jm.id
18
+ end
19
+
20
+ def client
21
+ @broker.client
22
+ end
23
+
24
+ def to_s
25
+ "#{id}#{queue && '@' + queue} #{job}"
26
+ end
27
+
28
+ private
29
+ def on_state_changed(was)
30
+ super
31
+ @broker.send(:on_finish, self) if finished?
32
+ end
33
+ end
34
+
35
+ attr_reader :client
36
+
37
+ def initialize(options = {})
38
+ extend MonitorMixin
39
+ @queues = Array(options[:queue] || options[:queues])
40
+ @client = Client::ActiveRecord.new
41
+ end
42
+
43
+ def start(&callback)
44
+ @jobs = Set.new
45
+ loop do
46
+ synchronize do
47
+ JobModel.logger.hush do |old_logger_level|
48
+ JobModel.transaction do
49
+ for jm in JobModel.candidates(@queues).all(:lock => true)
50
+ JobModel.logger.with_level(old_logger_level) do
51
+ if @jobs.add?(jm.id) && !callback.call(Cookie.new(self, jm))
52
+ @jobs.delete(jm.id)
53
+ break
54
+ end
55
+ end
56
+ end
57
+ JobModel.update_all({:run_at => 5.minutes.from_now}, :id => @jobs.to_a)
58
+ end
59
+ end
60
+ end
61
+ sleep(5)
62
+ end
63
+ end
64
+
65
+ def close
66
+ self
67
+ end
68
+
69
+ private
70
+ def on_finish(cookie)
71
+ synchronize do
72
+ @jobs.delete(cookie.id)
73
+ if cookie.success?
74
+ Monkey.logger.hush { JobModel.delete(cookie.id) }
75
+ Monkey.logger.debug "Deleted job #{cookie}"
76
+ end
77
+ end
78
+ rescue
79
+ Monkey.logger.error $!
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ require 'monkeyjob/broker/active_record/job_model'
86
+
@@ -0,0 +1,6 @@
1
+ module Monkey
2
+ class Broker
3
+ autoload :ActiveRecord, 'monkeyjob/broker/active_record'
4
+ end
5
+ end
6
+
@@ -0,0 +1,22 @@
1
+ module Monkey
2
+ class Client
3
+ class ActiveRecord < Client
4
+ def initialize(options = {})
5
+ @queue = options[:queue]
6
+ end
7
+
8
+ def submit(job, options = {})
9
+ jm = Broker::ActiveRecord::JobModel.silence do
10
+ Broker::ActiveRecord::JobModel.new {|m|
11
+ m.run_at = options[:run_at] || Time.now
12
+ m.handler = job
13
+ m.queue = options.fetch(:queue, @queue)
14
+ }.tap {|jm| jm.save! }
15
+ end
16
+ Monkey.logger.info { "Submitted #{job} to #{jm.queue}:#{jm.id}" }
17
+ self
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,8 @@
1
+ module Monkey
2
+ class Client
3
+ autoload :ActiveRecord, 'monkeyjob/client/active_record'
4
+
5
+ cattr_accessor :default
6
+ end
7
+ end
8
+
@@ -0,0 +1,103 @@
1
+ module Monkey
2
+ class Job
3
+ def to_proc
4
+ method(:run).to_proc
5
+ end
6
+
7
+ def to_s
8
+ self.class.name
9
+ end
10
+
11
+ def submit(options = {})
12
+ self.class.submit(self, options)
13
+ end
14
+
15
+ def submit_to(client, options = {})
16
+ client.submit(self, options)
17
+ end
18
+
19
+ def self.submit(handler, options = {})
20
+ Client.default.submit(handler, options)
21
+ end
22
+
23
+ class Cookie
24
+ attr_reader :state, :result, :exception, :started_at, :finished_at
25
+
26
+ def running?
27
+ state == :running
28
+ end
29
+
30
+ def success?
31
+ state == :success
32
+ end
33
+
34
+ def error?
35
+ state == :error
36
+ end
37
+
38
+ def finished?
39
+ success? || error?
40
+ end
41
+
42
+ private
43
+ def start!
44
+ Monkey.logger.info "Running #{self}"
45
+ @result, @exception, @started_at, @finished_at, self.state = nil, nil, Time.now, nil, :running
46
+ end
47
+
48
+ def unstart!
49
+ @result, @exception, @started_at, @finished_at, self.state = nil, nil, nil, nil, nil
50
+ end
51
+
52
+ def success!(result = nil)
53
+ @result, @exception, @finished_at, self.state = result, nil, Time.now, :success
54
+ Monkey.logger.info "Job #{self} succeeded after #{finished_at - started_at} sec."
55
+ end
56
+
57
+ def error!(exception = nil)
58
+ @result, @exception, @finished_at, self.state = nil, exception, Time.now, :error
59
+ Monkey.logger.error(exception) if exception
60
+ Monkey.logger.error "Job #{self} failed after #{finished_at - started_at} sec."
61
+ end
62
+
63
+ attr_writer :state
64
+
65
+ class Impl < Cookie
66
+ attr_reader :job
67
+
68
+ def initialize(job)
69
+ @job = job
70
+ end
71
+
72
+ def to_s
73
+ job.to_s
74
+ end
75
+
76
+ def to_proc
77
+ method(:run).to_proc
78
+ end
79
+
80
+ def submit(job, options = {})
81
+ client.submit(job, {:queue => queue}.update(options))
82
+ end
83
+
84
+ private
85
+ def state=(v)
86
+ was = state
87
+ super.tap { on_state_changed(was) }
88
+ end
89
+
90
+ def on_state_changed(was)
91
+ end
92
+
93
+ def run
94
+ start!
95
+ success!(job.to_proc.call(self))
96
+ rescue
97
+ error!($!)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+
@@ -0,0 +1,26 @@
1
+ module Monkey
2
+ class Juggler
3
+ def initialize(options = {})
4
+ @broker = options[:broker] || raise(ArgumentError, ':broker is mandatory')
5
+ @runner = options[:runner] || raise(ArgumentError, ':runner is mandatory')
6
+ end
7
+
8
+ def start
9
+ @broker.start {|cookie| @runner.run?(cookie) }
10
+ self
11
+ end
12
+
13
+ def run
14
+ start
15
+ ensure
16
+ close
17
+ end
18
+
19
+ def close
20
+ @broker.close
21
+ @runner.close
22
+ self
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,33 @@
1
+ module Monkey
2
+ module LoggerPatch
3
+ # def included?(other)
4
+ # other.send :alias_method, :global_level, :level
5
+ # other.send :alias_method, :global_level=, :level=
6
+ # end
7
+
8
+ # def level
9
+ # Thread.current["logger[#{object_id}].level"] || global_level
10
+ # end
11
+
12
+ # def level=(level)
13
+ # Thread.current["logger[#{object_id}].level"]
14
+ # end
15
+
16
+ def with_level(temp_level)
17
+ return yield(old_level) unless silencer
18
+ begin
19
+ old_level, self.level = self.level, temp_level
20
+ yield old_level
21
+ ensure
22
+ self.level = old_level
23
+ end
24
+ end
25
+
26
+ def hush(temp_level = Logger::ERROR, &block)
27
+ with_level(temp_level, &block)
28
+ end
29
+ end
30
+
31
+ [Logger, ActiveSupport::BufferedLogger].each {|m| m.send :include, LoggerPatch }
32
+ end
33
+
@@ -0,0 +1,28 @@
1
+ module Monkey
2
+ class Runner
3
+ class Fork
4
+ class Rails < Fork
5
+ def run_local(*args)
6
+ super(*args) do
7
+ begin
8
+ ::ActiveRecord::Base.dirty_reconnect!
9
+ yield(*args)
10
+ ensure
11
+ ::ActiveRecord::Base.connection_handler.clear_all_connections!
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ ActiveRecord::Base
21
+
22
+ class ActiveRecord::Base
23
+ def self.dirty_reconnect!
24
+ @@connection_handler = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new
25
+ establish_connection
26
+ end
27
+ end
28
+
@@ -0,0 +1,88 @@
1
+ require 'monitor'
2
+
3
+ module Monkey
4
+ class Runner
5
+ class Fork < Runner
6
+ autoload :Rails, 'monkeyjob/runner/fork/rails'
7
+
8
+ def initialize(options = {})
9
+ extend MonitorMixin
10
+ @max = options[:max] || 4
11
+ @workers = Set.new
12
+ end
13
+
14
+ def run(cookie)
15
+ run_core(:run_local, cookie)
16
+ end
17
+
18
+ def run?(cookie)
19
+ run_core(:run_local?, cookie)
20
+ end
21
+
22
+ def run_local(*args)
23
+ raise ArgumentError, 'block must be provided' unless block_given?
24
+ pid = fork do
25
+ trap('TERM') do
26
+ Monkey.logger.warn "Process #{Process.pid} received SIGTERM, will exit!(2)"
27
+ exit!(2)
28
+ end
29
+ begin
30
+ Monkey.logger.debug "Process #{Process.pid} running"
31
+ yield(*args)
32
+ Monkey.logger.debug "Process #{Process.pid} will exit!"
33
+ exit!(0)
34
+ rescue
35
+ Monkey.logger.error $!
36
+ Monkey.logger.error "Process #{Process.pid} failed, will exit!(1)"
37
+ exit!(1)
38
+ end
39
+ end
40
+ (spid = Process.detach(pid))[:pid] = pid
41
+ Monkey.logger.debug "Forked #{pid}"
42
+ spid
43
+ end
44
+
45
+ def run_local?(*args, &block)
46
+ synchronize do
47
+ return nil unless @workers.size < @max
48
+ @sweeper ||= Thread.new { loop { sleep(1); synchronize { sweep } } }
49
+ run_local(*args, &block).tap {|spid| @workers << spid }
50
+ end
51
+ end
52
+
53
+ def close
54
+ synchronize do
55
+ @sweeper.try :kill
56
+ @workers.each do |spid|
57
+ begin
58
+ Monkey.logger.warn { "Killing process #{spid[:pid]}" }
59
+ Process.kill('TERM', spid[:pid])
60
+ rescue
61
+ Monkey.logger.debug $!
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ private
68
+ def run_core(method, cookie)
69
+ synchronize do
70
+ send(method, cookie, &cookie.job).tap do |spid|
71
+ (spid[:cookie] = cookie).send(:start!) if spid
72
+ end
73
+ end
74
+ end
75
+
76
+ def sweep
77
+ @workers.delete_if {|spid| finish!(spid) }
78
+ rescue
79
+ Monkey.logger.error($!)
80
+ end
81
+
82
+ def finish!(spid)
83
+ !spid.alive?.tap {|alive| spid[:cookie].try(spid.value.success? ? :success! : :error!) unless alive }
84
+ end
85
+ end
86
+ end
87
+ end
88
+
@@ -0,0 +1,34 @@
1
+ require 'threadpool'
2
+
3
+ module Monkey
4
+ class Runner
5
+ class ThreadPool < Runner
6
+ def initialize(options = {})
7
+ @pool = ::ThreadPool.new(options)
8
+ end
9
+
10
+ def run(cookie)
11
+ run_local(&cookie)
12
+ end
13
+
14
+ def run?(cookie)
15
+ run_local?(&cookie)
16
+ end
17
+
18
+ def run_local?(*args, &block)
19
+ @pool.run(*args, &block)
20
+ self
21
+ end
22
+
23
+ def run_local?(*args, &block)
24
+ @pool.run?(*args, &block) && self
25
+ end
26
+
27
+ def close
28
+ @pool.close
29
+ self
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,10 @@
1
+ module Monkey
2
+ class Runner
3
+ autoload :Fork, 'monkeyjob/runner/fork'
4
+ autoload :ThreadPool, 'monkeyjob/runner/thread_pool'
5
+
6
+ def close
7
+ end
8
+ end
9
+ end
10
+
data/lib/monkeyjob.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'activesupport'
2
+
3
+ module Monkey
4
+ mattr_accessor :logger
5
+ @@logger = Logger.new(STDERR)
6
+
7
+ autoload :Client, 'monkeyjob/client'
8
+ autoload :Broker, 'monkeyjob/broker'
9
+ autoload :Runner, 'monkeyjob/runner'
10
+ autoload :Juggler, 'monkeyjob/juggler'
11
+ end
12
+
13
+ require 'monkeyjob/logger'
14
+ require 'monkeyjob/job'
15
+
@@ -0,0 +1 @@
1
+ require 'monkeyjob'
data/rails/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ Monkey.logger = RAILS_DEFAULT_LOGGER
2
+
@@ -0,0 +1,12 @@
1
+ require 'test/unit'
2
+ require 'lib/monkeyjob'
3
+
4
+
5
+ class MonkeyJobTest < Test::Unit::TestCase
6
+ def setup
7
+ end
8
+
9
+ def test_me
10
+ end
11
+ end
12
+
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omg-monkeyjob
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.6
5
+ platform: ruby
6
+ authors:
7
+ - Igor Gunko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-21 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.2.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: thoughtbot-shoulda
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.6
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: sqlite3-ruby
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.2.4
44
+ version:
45
+ description:
46
+ email: tekmon@gmail.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README.rdoc
53
+ - MIT-LICENSE
54
+ files:
55
+ - README.rdoc
56
+ - MIT-LICENSE
57
+ - Rakefile
58
+ - lib/monkeyjob.rb
59
+ - lib/omg-monkeyjob.rb
60
+ - lib/monkeyjob/logger.rb
61
+ - lib/monkeyjob/job.rb
62
+ - lib/monkeyjob/juggler.rb
63
+ - lib/monkeyjob/client.rb
64
+ - lib/monkeyjob/broker.rb
65
+ - lib/monkeyjob/runner.rb
66
+ - lib/monkeyjob/client/active_record.rb
67
+ - lib/monkeyjob/broker/active_record.rb
68
+ - lib/monkeyjob/broker/active_record/job_model.rb
69
+ - lib/monkeyjob/runner/thread_pool.rb
70
+ - lib/monkeyjob/runner/fork.rb
71
+ - lib/monkeyjob/runner/fork/rails.rb
72
+ - rails/init.rb
73
+ has_rdoc: true
74
+ homepage: http://github.com/omg/monkeyjob
75
+ post_install_message:
76
+ rdoc_options:
77
+ - --line-numbers
78
+ - --main
79
+ - README.rdoc
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "0"
87
+ version:
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: "0"
93
+ version:
94
+ requirements: []
95
+
96
+ rubyforge_project:
97
+ rubygems_version: 1.2.0
98
+ signing_key:
99
+ specification_version: 2
100
+ summary: Like Delayed Job on steroids
101
+ test_files:
102
+ - test/monkeyjob_test.rb