rack-app-worker 0.1.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,18 @@
1
+ require 'logger'
2
+ class Rack::App::Worker::Logger
3
+
4
+ def self.default_out(new_out=nil)
5
+ @default_out = new_out unless new_out.nil?
6
+ @default_out ||= $stdout
7
+ end
8
+
9
+ def initialize(out=self.class.default_out)
10
+ @logger = ::Logger.new(out)
11
+ @logger.level= Rack::App::Worker::Environment.log_level
12
+ end
13
+
14
+ [:debug, :info, :warn, :error, :fatal, :unknown].each do |severity_level|
15
+ define_method(severity_level) { |message| @logger.public_send(severity_level, message) }
16
+ end
17
+
18
+ end
@@ -0,0 +1,100 @@
1
+ require 'logger'
2
+ require 'rack/app/worker'
3
+ class Rack::App::Worker::Observer
4
+
5
+ def initialize
6
+ @shutdown_signal_received = false
7
+ @ready_for_shutdown = false
8
+ end
9
+
10
+ def start
11
+ logger.info(__method__.to_s)
12
+ loop do
13
+ break if shutdown_signal_received
14
+
15
+ logger.debug(Rack::App::Worker::Register.worker_definitions.keys.inspect)
16
+ Rack::App::Worker::Register.worker_definitions.values.each do |definition|
17
+
18
+ queue = rabbitmq.send_queue(definition[:name])
19
+ status = check_status(queue)
20
+
21
+ if need_more_worker?(status)
22
+ create_consumer(definition)
23
+ end
24
+
25
+ if need_less_worker?(status)
26
+ signal_shutdown_for_a_consumer(definition)
27
+ end
28
+
29
+ end
30
+
31
+ sleep(heartbeat_interval)
32
+
33
+ end
34
+ end
35
+
36
+ def stop
37
+ @shutdown_signal_received = true
38
+ sleep(0.1) until @ready_for_shutdown
39
+ end
40
+
41
+ protected
42
+
43
+ def check_status(queue)
44
+ queue.status
45
+ rescue Timeout::Error
46
+ retry
47
+ end
48
+
49
+ def create_consumer(definition)
50
+ logger.info("#{__method__}(#{definition[:name]})")
51
+ Rack::App::Worker::Consumer.new(definition).start
52
+ end
53
+
54
+ def signal_shutdown_for_a_consumer(definition)
55
+ logger.info("#{__method__}(#{definition[:name]})")
56
+ Rack::App::Worker::Consumer.new(definition).stop
57
+ end
58
+
59
+ def heartbeat_interval
60
+ Rack::App::Worker::Environment.heartbeat_interval
61
+ end
62
+
63
+ def message_count_limit
64
+ Rack::App::Worker::Environment.message_count_limit
65
+ end
66
+
67
+ def shutdown_signal_received
68
+ if @shutdown_signal_received
69
+ logger.info(__method__.to_s)
70
+ consumers.each { |c| c.stop_all }
71
+ rabbitmq.session.close
72
+ @ready_for_shutdown = true
73
+ end
74
+ (!!@ready_for_shutdown)
75
+ end
76
+
77
+ def need_less_worker?(status)
78
+ (status[:message_count] < message_count_limit) and (status[:consumer_count] > 1)
79
+ end
80
+
81
+ def need_more_worker?(status)
82
+ ((status[:consumer_count] == 0) or (status[:message_count] >= message_count_limit)) and
83
+ status[:consumer_count] <= Rack::App::Worker::Environment.max_consumer_number
84
+ end
85
+
86
+ def consumers
87
+ Rack::App::Worker::Register.worker_definitions.values.map do |definition|
88
+ Rack::App::Worker::Consumer.new(definition)
89
+ end
90
+ end
91
+
92
+ def rabbitmq
93
+ @rabbitmq ||= Rack::App::Worker::RabbitMQ.new
94
+ end
95
+
96
+ def logger
97
+ @logger ||= Rack::App::Worker::Logger.new
98
+ end
99
+
100
+ end
@@ -0,0 +1,117 @@
1
+ require 'bunny'
2
+ require 'rack/app/worker'
3
+ class Rack::App::Worker::RabbitMQ
4
+
5
+ def session
6
+ check_connection
7
+ @session
8
+ end
9
+
10
+ def channel
11
+ @channel ||= create_channel
12
+ @channel = create_channel unless @channel.open?
13
+ @channel
14
+ end
15
+
16
+ def send_exchange(name)
17
+ exchange_for('send', name)
18
+ end
19
+
20
+ def broadcast_exchange(name)
21
+ exchange_for('broadcast', name)
22
+ end
23
+
24
+ def send_queue(name)
25
+ queue_name = "#{namespace}.#{cluster}.#{name}"
26
+ queue = channel.queue(queue_name, :durable => true, :auto_delete => false)
27
+ queue.bind(send_exchange(name)) unless exchange_already_bind?(queue, name)
28
+ return queue
29
+ end
30
+
31
+ def create_broadcast_queue(name)
32
+ queue = channel.queue('', :exclusive => true, :auto_delete => false)
33
+ queue.bind(broadcast_exchange(name)) unless exchange_already_bind?(queue, name)
34
+ return queue
35
+ end
36
+
37
+ protected
38
+
39
+ def exchange_already_bind?(queue, name)
40
+ queue.recover_bindings.any? { |binding| binding[:exchange] == exchange_name('send', name) }
41
+ rescue Timeout::Error
42
+ sleep(rand(1..5))
43
+ retry
44
+ end
45
+
46
+ def create_new_session
47
+ session = ::Bunny.new
48
+ session.start
49
+ return session
50
+ end
51
+
52
+ def check_connection
53
+ case @session
54
+
55
+ when ::Bunny::Session
56
+ @session.close if @session.status == :not_connected
57
+ create_session if @session.closed?
58
+
59
+ when NilClass
60
+ create_session
61
+
62
+ end
63
+ rescue ::Bunny::TCPConnectionFailedForAllHosts
64
+ sleep(1)
65
+ retry
66
+ end
67
+
68
+ def create_session
69
+ new_session = create_new_session
70
+ new_session.logger.level = Logger::ERROR
71
+ @session = new_session
72
+ end
73
+
74
+ def cluster
75
+ Rack::App::Worker::Environment.worker_cluster
76
+ end
77
+
78
+ def namespace
79
+ Rack::App::Worker::Environment.namespace
80
+ end
81
+
82
+ def create_channel
83
+ new_channel = session.create_channel
84
+ new_channel.basic_qos(Rack::App::Worker::Environment.queue_qos)
85
+ new_channel
86
+ end
87
+
88
+ def exchange_cache
89
+ @exchange_cache ||= {}
90
+ end
91
+
92
+ def exchange_for(type, name)
93
+ exchange_cache[name] ||= proc {
94
+ channel.fanout(exchange_name(type, name), :durable => true)
95
+ }.call
96
+ end
97
+
98
+ def exchange_name(type, name)
99
+ "#{namespace}.#{type}.#{name}"
100
+ end
101
+
102
+ def session_finalizer!
103
+ @session_finalizer ||= lambda do
104
+ this = self
105
+ Kernel.at_exit do
106
+ begin
107
+ session = this.instance_variable_get(:@session)
108
+ session && session.respond_to?(:close) && session.close
109
+ rescue Timeout::Error
110
+ nil
111
+ end
112
+ end
113
+ true
114
+ end.call
115
+ end
116
+
117
+ end
@@ -0,0 +1,24 @@
1
+ require 'rack/app/worker'
2
+ module Rack::App::Worker::Register
3
+
4
+ require 'rack/app/worker/register/builder'
5
+ require 'rack/app/worker/register/clients'
6
+
7
+ extend self
8
+
9
+ def add(name,class_constructor)
10
+ builder = Rack::App::Worker::Register::Builder.new(name.to_sym)
11
+ builder.consumer(class_constructor)
12
+ worker_definitions[name.to_sym]= builder.to_def
13
+ nil
14
+ end
15
+
16
+ def [](name)
17
+ worker_definitions[name.to_sym]
18
+ end
19
+
20
+ def worker_definitions
21
+ @worker_definitions ||= {}
22
+ end
23
+
24
+ end
@@ -0,0 +1,25 @@
1
+ class Rack::App::Worker::Register::Builder
2
+
3
+ def initialize(name)
4
+ @name = name
5
+ end
6
+
7
+ def consumer(class_definition)
8
+ if class_definition.is_a?(Class)
9
+ @consumer_class = class_definition
10
+ elsif class_definition.is_a?(Proc)
11
+ klass = Class.new
12
+ klass.class_exec(&class_definition)
13
+ @consumer_class = klass
14
+ end
15
+ end
16
+
17
+ def to_def
18
+ {
19
+ class: @consumer_class,
20
+ name: @name,
21
+ client: Rack::App::Worker::ClientProxy.new(@name)
22
+ }
23
+ end
24
+
25
+ end
@@ -0,0 +1,10 @@
1
+ require 'rack/app/worker'
2
+ module Rack::App::Worker::Register::Clients
3
+
4
+ extend(self)
5
+
6
+ def [](name)
7
+ Rack::App::Worker::Register[name][:client]
8
+ end
9
+
10
+ end
@@ -0,0 +1,18 @@
1
+ module Rack::App::Worker::Utils
2
+
3
+ extend(self)
4
+
5
+ def process_alive?(pid)
6
+ ::Process.kill(0, pid.to_i)
7
+ return true
8
+ rescue ::Errno::ESRCH
9
+ return false
10
+ end
11
+
12
+ def maximum_allowed_process_number
13
+ (`ulimit -u`.to_i * 0.75).to_i + 10
14
+ rescue Errno::ENOENT
15
+ 100
16
+ end
17
+
18
+ end
@@ -0,0 +1,2 @@
1
+ require 'rack/app/worker'
2
+ Rack::App::Worker::VERSION = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'VERSION')).strip
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "rack-app-worker"
5
+ spec.version = File.read(File.join(File.dirname(__FILE__),'VERSION')).strip
6
+ spec.authors = ["Adam Luzsi"]
7
+ spec.email = ["adamluzsi@gmail.com"]
8
+
9
+ spec.summary = %q{Rack::App framework background worker extension}
10
+ spec.description = %q{Rack::App framework background worker extension}
11
+
12
+ spec.homepage = "http://www.rack-app.com"
13
+ spec.license = 'Apache License Version 2.0'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.0")
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.11"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
+
25
+ spec.add_dependency "rack-app", ">= 3.6.0"
26
+ spec.add_dependency "bunny", ">= 2.3.0"
27
+
28
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-app-worker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Luzsi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-05-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack-app
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 3.6.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 3.6.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: bunny
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 2.3.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 2.3.0
83
+ description: Rack::App framework background worker extension
84
+ email:
85
+ - adamluzsi@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - CODE_OF_CONDUCT.md
94
+ - Gemfile
95
+ - LICENSE
96
+ - README.md
97
+ - Rakefile
98
+ - VERSION
99
+ - bin/console
100
+ - bin/setup
101
+ - lib/rack/app/worker.rb
102
+ - lib/rack/app/worker/cli.rb
103
+ - lib/rack/app/worker/client_proxy.rb
104
+ - lib/rack/app/worker/client_proxy/wrapper.rb
105
+ - lib/rack/app/worker/consumer.rb
106
+ - lib/rack/app/worker/daemonizer.rb
107
+ - lib/rack/app/worker/dsl.rb
108
+ - lib/rack/app/worker/dsl/for_class.rb
109
+ - lib/rack/app/worker/dsl/for_endpoints.rb
110
+ - lib/rack/app/worker/environment.rb
111
+ - lib/rack/app/worker/logger.rb
112
+ - lib/rack/app/worker/observer.rb
113
+ - lib/rack/app/worker/rabbit_mq.rb
114
+ - lib/rack/app/worker/register.rb
115
+ - lib/rack/app/worker/register/builder.rb
116
+ - lib/rack/app/worker/register/clients.rb
117
+ - lib/rack/app/worker/utils.rb
118
+ - lib/rack/app/worker/version.rb
119
+ - rack-app-worker.gemspec
120
+ homepage: http://www.rack-app.com
121
+ licenses:
122
+ - Apache License Version 2.0
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '2.0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.4.8
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: Rack::App framework background worker extension
144
+ test_files: []