omg-monkeyjob 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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