rack-app-worker 0.1.0

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