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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +33 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE +201 -0
- data/README.md +60 -0
- data/Rakefile +6 -0
- data/VERSION +1 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/rack/app/worker.rb +46 -0
- data/lib/rack/app/worker/cli.rb +37 -0
- data/lib/rack/app/worker/client_proxy.rb +27 -0
- data/lib/rack/app/worker/client_proxy/wrapper.rb +16 -0
- data/lib/rack/app/worker/consumer.rb +76 -0
- data/lib/rack/app/worker/daemonizer.rb +184 -0
- data/lib/rack/app/worker/dsl.rb +4 -0
- data/lib/rack/app/worker/dsl/for_class.rb +12 -0
- data/lib/rack/app/worker/dsl/for_endpoints.rb +7 -0
- data/lib/rack/app/worker/environment.rb +71 -0
- data/lib/rack/app/worker/logger.rb +18 -0
- data/lib/rack/app/worker/observer.rb +100 -0
- data/lib/rack/app/worker/rabbit_mq.rb +117 -0
- data/lib/rack/app/worker/register.rb +24 -0
- data/lib/rack/app/worker/register/builder.rb +25 -0
- data/lib/rack/app/worker/register/clients.rb +10 -0
- data/lib/rack/app/worker/utils.rb +18 -0
- data/lib/rack/app/worker/version.rb +2 -0
- data/rack-app-worker.gemspec +28 -0
- metadata +144 -0
@@ -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,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,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: []
|