soter 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,83 @@
1
+ require_relative 'soter/config'
2
+ require_relative 'soter/job_worker'
3
+
4
+ require 'mongo'
5
+ require 'mongo_queue'
6
+
7
+ module Soter
8
+
9
+ require 'mongo_queue'
10
+
11
+ def self.config
12
+ @config ||= Soter::Config.new
13
+ end
14
+
15
+ def self.enqueue(handler, job_params={}, queue_options={})
16
+ job = {
17
+ 'job' => {
18
+ 'params' => job_params,
19
+ 'class' => handler.to_s
20
+ },
21
+ 'queue_options' => queue_options,
22
+ 'active_at' => queue_options.delete(:active_at)
23
+ }
24
+
25
+ queue.insert(job)
26
+ dispatch_worker
27
+ end
28
+
29
+ def self.dequeue(job_params)
30
+ queue.remove('job_params' => job_params)
31
+ end
32
+
33
+ private
34
+
35
+ # First 8 retry values in minutes are:
36
+ # 2.minutes, 17.minutes, 2.hours, 6.hours,
37
+ # 16.hours, 31.hours, 54.hours, 85.hours
38
+ def self.retry_offset(retries)
39
+ ( (retries-1) ** 3) * (15 * 60) + 120
40
+ end
41
+
42
+ def self.database
43
+ if Soter.config.host
44
+ @database ||= Mongo::Connection.new(Soter.config.host, Soter.config.port)
45
+ else
46
+ @database ||= Mongo::ReplSetConnection.new(Soter.config.hosts.first)
47
+ end
48
+ end
49
+
50
+ def self.queue
51
+ @queue ||= Mongo::Queue.new(database, Soter.config.queue_settings)
52
+ end
53
+
54
+ def self.workers
55
+ begin
56
+ result = @queue.send(:collection).
57
+ distinct(:locked_by, {:locked_by => {"$ne" => nil}})
58
+ rescue
59
+ result = []
60
+ end
61
+
62
+ result || []
63
+ end
64
+
65
+ def self.dispatch_worker
66
+ if workers.count < max_workers
67
+ JobWorker.new.start
68
+ else
69
+ queue.cleanup! #remove stuck locks
70
+ end
71
+ end
72
+
73
+ def self.max_workers
74
+ Soter.config.workers || 5
75
+ end
76
+
77
+ def self.recursive_const_get(name)
78
+ name.to_s.split("::").inject(self) do |b, c|
79
+ b.const_get(c)
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,18 @@
1
+ module Soter
2
+ class Config < Struct.new(:fork, :logfile, :logger, :host, :port, :db,
3
+ :workers, :attempts, :hosts)
4
+
5
+ def queue_settings
6
+ host_settings = host ? {host: host} : {hosts: hosts}
7
+
8
+ host_settings.merge({
9
+ port: port,
10
+ database: db,
11
+ collection: "soter_queue",
12
+ timeout: 300,
13
+ attempts: (attempts || 3)
14
+ })
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,84 @@
1
+ module Soter
2
+ class JobWorker
3
+
4
+ require 'digest/md5'
5
+
6
+ def start
7
+ if fork?
8
+ fork do
9
+ perform
10
+ end
11
+ else
12
+ perform
13
+ end
14
+ end
15
+
16
+ def initialize
17
+ @queue = Soter.queue
18
+ @log = Soter.config.logger || File.open("#{logfile}", "a")
19
+
20
+ @log.sync = true if @log.respond_to?(:sync=)
21
+ @queue.cleanup! # remove expired locks
22
+ end
23
+
24
+ def perform
25
+ process_id = Digest::MD5.
26
+ hexdigest("#{Socket.gethostname}-#{Process.pid}-#{Thread.current}")
27
+
28
+ log "#{process_id}: Spawning"
29
+
30
+ while(job = @queue.lock_next(process_id))
31
+ log "#{process_id}: Starting work on job #{job['_id']}"
32
+ log "#{process_id}: Job info =>"
33
+
34
+ job.each {
35
+ |key, value| log "#{process_id}: {#{key} : #{value}}" }
36
+
37
+ begin
38
+ handler_class = Soter.recursive_const_get(job['job']['class'])
39
+ job_handler = handler_class.new(job['job']['params'])
40
+
41
+ job_handler.perform
42
+
43
+ log "#{process_id}: " + job_handler.message
44
+
45
+ if job_handler.success?
46
+ @queue.complete(job, process_id)
47
+ log "#{process_id}: Completed job #{job['_id']}"
48
+ else
49
+ offset = Soter.retry_offset(job['attempts']+1)
50
+ job['active_at'] = Time.now.utc + offset
51
+ @queue.error(job, job_handler.message)
52
+
53
+ log "#{process_id}: Failed job #{job['_id']}"
54
+ end
55
+
56
+ sleep(1) if fork?
57
+ rescue Exception => e
58
+ @queue.complete(job, e.message)
59
+ log "#{process_id}: Failed job #{job['_id']}" +
60
+ " with error #{e.message}"
61
+ log "#{process_id}: Backtrace =>"
62
+ e.backtrace.each { |line| log "#{process_id}: #{line}"}
63
+ end
64
+ end #while
65
+ log "#{process_id}: Harakiri"
66
+ @log.close
67
+ end #start
68
+
69
+ private
70
+
71
+ def fork?
72
+ Soter.config.fork
73
+ end
74
+
75
+ def logfile
76
+ Soter.config.logfile || 'log/soter.log'
77
+ end
78
+
79
+ def log(message)
80
+ @log << message + "\n"
81
+ end
82
+
83
+ end
84
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: soter
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - andresf
9
+ - solojavier
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-02-08 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: rake
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: mongo
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: bson_ext
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ description: ruby + mongoid background jobs library
80
+ email: 1.27201@gmail.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - lib/soter/config.rb
86
+ - lib/soter/job_worker.rb
87
+ - lib/soter.rb
88
+ homepage: https://github.com/wowzerinc/soter
89
+ licenses: []
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 1.8.25
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Background jobs
112
+ test_files: []
113
+ has_rdoc: