soter 1.0.0

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.
@@ -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: