multiple_man 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/multiple_man/channel_maintenance/gc.rb +101 -0
- data/lib/multiple_man/channel_maintenance/reaper.rb +35 -0
- data/lib/multiple_man/configuration.rb +8 -4
- data/lib/multiple_man/connection.rb +82 -11
- data/lib/multiple_man/listeners/seeder_listener.rb +1 -1
- data/lib/multiple_man/mixins/listener.rb +4 -4
- data/lib/multiple_man/mixins/publisher.rb +6 -2
- data/lib/multiple_man/version.rb +1 -1
- data/lib/multiple_man.rb +3 -0
- data/spec/integration/ephermal_model_spec.rb +0 -2
- data/spec/publisher_spec.rb +3 -3
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8dbcb9ce65f9deaea7bc949150dcad625861594e
|
4
|
+
data.tar.gz: f6fd179c4f92d65d0df27c23ad13bfeaf0359e37
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e53300b5197df7628f32a22f8440a03a2d4c91977b4e3ea6a4033b3a6a02f6832862ac1f30744295f12bd2690495cf9e24046fb58947d59933c0a90e942995e
|
7
|
+
data.tar.gz: 9b3a72cb0e46d7560b0b2736650928587f1f2ab21f8b30ce506dbfd66fc37a4af57e2dd1663a07397a8d5025e7d71e00e3721471fc865a79281ec8de14a7e279
|
data/README.md
CHANGED
@@ -0,0 +1,101 @@
|
|
1
|
+
module MultipleMan
|
2
|
+
module ChannelMaintenance
|
3
|
+
class GC
|
4
|
+
def self.finalizer(thread_id, channel, queue, reaper)
|
5
|
+
proc { queue << RemoveCommand.new(thread_id); reaper.push(channel) }
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(config, reaper)
|
9
|
+
@reaper = reaper
|
10
|
+
@queue = Queue.new
|
11
|
+
|
12
|
+
@executor = Thread.new do
|
13
|
+
channels_by_thread = Hash.new {|h, k| h[k] = [] }
|
14
|
+
loop do
|
15
|
+
begin
|
16
|
+
command = queue.pop
|
17
|
+
command.execute(channels_by_thread)
|
18
|
+
rescue
|
19
|
+
puts "Sweeper died", $!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@sweeper_thread = Thread.new do
|
25
|
+
loop do
|
26
|
+
sleep 15
|
27
|
+
queue << SweepCommand.new(queue, reaper)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def push(channel)
|
33
|
+
thread_id = Thread.current.object_id
|
34
|
+
|
35
|
+
finalizer = self.class.finalizer(thread_id, channel, queue, reaper)
|
36
|
+
ObjectSpace.define_finalizer(Thread.current, finalizer)
|
37
|
+
|
38
|
+
queue << AddCommand.new(thread_id, channel)
|
39
|
+
|
40
|
+
puts "Opened channel #{channel.number}"
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop
|
45
|
+
executor.kill
|
46
|
+
executor.join
|
47
|
+
|
48
|
+
sweeper_thread.kill
|
49
|
+
sweeper_thread.join
|
50
|
+
|
51
|
+
reaper.stop
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
attr_reader :queue, :reaper, :executor, :sweeper_thread
|
56
|
+
|
57
|
+
class AddCommand
|
58
|
+
attr_reader :thread_id, :channel
|
59
|
+
|
60
|
+
def initialize(thread_id, channel)
|
61
|
+
@thread_id = thread_id
|
62
|
+
@channel = channel
|
63
|
+
end
|
64
|
+
|
65
|
+
def execute(channels_by_thread)
|
66
|
+
channels_by_thread[thread_id] << channel
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class RemoveCommand
|
71
|
+
attr_reader :thread_id
|
72
|
+
|
73
|
+
def initialize(thread_id)
|
74
|
+
@thread_id = thread_id
|
75
|
+
end
|
76
|
+
|
77
|
+
def execute(channels_by_thread)
|
78
|
+
channels_by_thread.delete(thread_id)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class SweepCommand
|
83
|
+
attr_reader :queue, :reaper
|
84
|
+
def initialize(queue, reaper)
|
85
|
+
@queue = queue
|
86
|
+
@reaper = reaper
|
87
|
+
end
|
88
|
+
|
89
|
+
def execute(channels_by_thread)
|
90
|
+
channels_by_thread.each do |thread_id, channels|
|
91
|
+
thing = ObjectSpace._id2ref(thread_id) rescue nil
|
92
|
+
next if thing.kind_of?(Thread) && thing.alive?
|
93
|
+
|
94
|
+
channels.each {|c| reaper.push(c)}
|
95
|
+
queue << RemoveCommand.new(thread_id)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module MultipleMan
|
2
|
+
module ChannelMaintenance
|
3
|
+
class Reaper
|
4
|
+
def initialize(config)
|
5
|
+
@config = config
|
6
|
+
@queue = Queue.new
|
7
|
+
|
8
|
+
@worker = Thread.new do
|
9
|
+
loop do
|
10
|
+
channel = queue.pop
|
11
|
+
begin
|
12
|
+
channel.close unless closed?
|
13
|
+
puts "Channel #{channel.number} closed!"
|
14
|
+
rescue Exception
|
15
|
+
sleep config.connection_recovery[:time_between_retries]
|
16
|
+
retry
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def push(channel)
|
23
|
+
queue << channel
|
24
|
+
end
|
25
|
+
|
26
|
+
def stop
|
27
|
+
worker.kill
|
28
|
+
worker.join
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
attr_reader :config, :queue, :worker
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -5,9 +5,13 @@ module MultipleMan
|
|
5
5
|
self.topic_name = "multiple_man"
|
6
6
|
self.app_name = Rails.application.class.parent.to_s if defined?(Rails)
|
7
7
|
self.enabled = true
|
8
|
-
self.channel_pool_size = 5
|
9
8
|
self.worker_concurrency = 1
|
10
9
|
self.reraise_errors = true
|
10
|
+
self.connection_recovery = {
|
11
|
+
time_before_reconnect: 0.2,
|
12
|
+
time_between_retries: 0.8,
|
13
|
+
max_retries: 5
|
14
|
+
}
|
11
15
|
end
|
12
16
|
|
13
17
|
def logger
|
@@ -18,8 +22,8 @@ module MultipleMan
|
|
18
22
|
@error_handler = block
|
19
23
|
end
|
20
24
|
|
21
|
-
attr_accessor :topic_name, :app_name, :connection, :enabled, :
|
22
|
-
:worker_concurrency, :reraise_errors
|
25
|
+
attr_accessor :topic_name, :app_name, :connection, :enabled, :error_handler,
|
26
|
+
:worker_concurrency, :reraise_errors, :connection_recovery
|
23
27
|
attr_writer :logger
|
24
28
|
end
|
25
29
|
|
@@ -30,4 +34,4 @@ module MultipleMan
|
|
30
34
|
def self.configure
|
31
35
|
yield(configuration) if block_given?
|
32
36
|
end
|
33
|
-
end
|
37
|
+
end
|
@@ -1,22 +1,79 @@
|
|
1
1
|
require 'bunny'
|
2
|
-
require 'connection_pool'
|
3
2
|
require 'active_support/core_ext/module'
|
4
3
|
|
5
4
|
module MultipleMan
|
6
5
|
class Connection
|
6
|
+
@mutex = Mutex.new
|
7
7
|
|
8
8
|
def self.connect
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
yield new(channel)
|
10
|
+
Thread.current[:multiple_man_exception_retry_count] = 0
|
11
|
+
rescue Bunny::Exception, Timeout::Error => e
|
12
|
+
recovery_options = MultipleMan.configuration.connection_recovery
|
13
|
+
MultipleMan.logger.debug "Bunny Error: #{e.inspect}"
|
14
|
+
|
15
|
+
retry_count = Thread.current[:multiple_man_exception_retry_count] || 0
|
16
|
+
retry_count += 1
|
17
|
+
|
18
|
+
if retry_count < recovery_options[:max_retries]
|
19
|
+
Thread.current[:multiple_man_exception_retry_count] = retry_count
|
20
|
+
sleep recovery_options[:time_between_retries]
|
21
|
+
retry
|
22
|
+
else
|
23
|
+
Thread.current[:multiple_man_exception_retry_count] = 0
|
24
|
+
raise "MultipleMan::ConnectionError"
|
25
|
+
end
|
26
|
+
rescue Exception => e
|
27
|
+
Infl::Errors::Logger.log(e)
|
28
|
+
raise
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.channel
|
32
|
+
Thread.current.thread_variable_get(:multiple_man_current_channel) || begin
|
33
|
+
channel = connection.create_channel
|
34
|
+
channel_gc.push(channel)
|
35
|
+
Thread.current.thread_variable_set(:multiple_man_current_channel, channel)
|
36
|
+
|
37
|
+
channel
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.connection
|
42
|
+
@mutex.synchronize do
|
43
|
+
@connection ||= begin
|
44
|
+
connection = Bunny.new(
|
45
|
+
MultipleMan.configuration.connection,
|
46
|
+
heartbeat_interval: 5,
|
47
|
+
automatically_recover: true,
|
48
|
+
recover_from_connection_close: true,
|
49
|
+
network_recovery_interval: MultipleMan.configuration.connection_recovery[:time_before_reconnect]
|
50
|
+
)
|
51
|
+
MultipleMan.logger.debug "Connecting to #{MultipleMan.configuration.connection}"
|
52
|
+
connection.start
|
53
|
+
|
54
|
+
connection
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.channel_gc
|
60
|
+
@channel_gc ||= ChannelMaintenance::GC.new(
|
61
|
+
MultipleMan.configuration,
|
62
|
+
ChannelMaintenance::Reaper.new(MultipleMan.configuration))
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.reset!
|
66
|
+
@mutex.synchronize do
|
67
|
+
@connection.close if @connection
|
68
|
+
@connection = nil
|
69
|
+
|
70
|
+
@channel_gc.stop if @channel_gc
|
71
|
+
@channel_gc = nil
|
72
|
+
end
|
17
73
|
end
|
18
74
|
|
19
75
|
attr_reader :topic
|
76
|
+
delegate :queue, to: :channel
|
20
77
|
|
21
78
|
def initialize(channel)
|
22
79
|
self.channel = channel
|
@@ -27,8 +84,6 @@ module MultipleMan
|
|
27
84
|
MultipleMan.configuration.topic_name
|
28
85
|
end
|
29
86
|
|
30
|
-
delegate :queue, to: :channel
|
31
|
-
|
32
87
|
private
|
33
88
|
|
34
89
|
attr_accessor :channel
|
@@ -36,3 +91,19 @@ module MultipleMan
|
|
36
91
|
|
37
92
|
end
|
38
93
|
end
|
94
|
+
|
95
|
+
__END__
|
96
|
+
|
97
|
+
# Possible usage
|
98
|
+
|
99
|
+
Unicorn.after_fork do
|
100
|
+
MultipleMan::Connection.reset!
|
101
|
+
end
|
102
|
+
|
103
|
+
Sidekiq.configure_server do |config|
|
104
|
+
MultipleMan::Connection.reset!
|
105
|
+
end
|
106
|
+
|
107
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
108
|
+
MultipleMan::Connection.reset! if forked
|
109
|
+
end
|
@@ -11,19 +11,19 @@ module MultipleMan
|
|
11
11
|
attr_accessor :klass
|
12
12
|
attr_accessor :operation
|
13
13
|
|
14
|
-
def create
|
14
|
+
def create(payload)
|
15
15
|
# noop
|
16
16
|
end
|
17
17
|
|
18
|
-
def update
|
18
|
+
def update(payload)
|
19
19
|
# noop
|
20
20
|
end
|
21
21
|
|
22
|
-
def destroy
|
22
|
+
def destroy(payload)
|
23
23
|
# noop
|
24
24
|
end
|
25
25
|
|
26
|
-
def seed
|
26
|
+
def seed(payload)
|
27
27
|
# noop
|
28
28
|
end
|
29
29
|
|
@@ -6,7 +6,11 @@ module MultipleMan
|
|
6
6
|
base.extend(ClassMethods)
|
7
7
|
if base.respond_to?(:after_commit)
|
8
8
|
base.after_commit(on: :create) { |r| r.multiple_man_publish(:create) }
|
9
|
-
base.after_commit(on: :update)
|
9
|
+
base.after_commit(on: :update) do |r|
|
10
|
+
if !r.respond_to?(:previous_changes) || r.previous_changes.any?
|
11
|
+
r.multiple_man_publish(:update)
|
12
|
+
end
|
13
|
+
end
|
10
14
|
base.after_commit(on: :destroy) { |r| r.multiple_man_publish(:destroy) }
|
11
15
|
end
|
12
16
|
|
@@ -28,4 +32,4 @@ module MultipleMan
|
|
28
32
|
end
|
29
33
|
end
|
30
34
|
end
|
31
|
-
end
|
35
|
+
end
|
data/lib/multiple_man/version.rb
CHANGED
data/lib/multiple_man.rb
CHANGED
@@ -30,8 +30,6 @@ describe "Publishing of ephermal models" do
|
|
30
30
|
data: { foo: 'foo', bar: 'bar', baz: 'baz'}
|
31
31
|
}.to_json
|
32
32
|
|
33
|
-
#MultipleMan::Connection.unstub!(:connection)
|
34
|
-
|
35
33
|
expect_any_instance_of(Bunny::Exchange).to receive(:publish)
|
36
34
|
.with(payload, routing_key: 'multiple_man.Ephermal.create')
|
37
35
|
|
data/spec/publisher_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe MultipleMan::Publisher do
|
3
|
+
describe MultipleMan::Publisher do
|
4
4
|
class MockClass
|
5
5
|
class << self
|
6
6
|
attr_accessor :subscriber
|
@@ -32,6 +32,6 @@ describe MultipleMan::Publisher do
|
|
32
32
|
mock_publisher = double(MultipleMan::ModelPublisher)
|
33
33
|
MultipleMan::ModelPublisher.any_instance.should_receive(:publish).with(my_mock, :create)
|
34
34
|
my_mock.save
|
35
|
-
end
|
35
|
+
end
|
36
36
|
end
|
37
|
-
end
|
37
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: multiple_man
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Brunner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -166,6 +166,8 @@ files:
|
|
166
166
|
- Rakefile
|
167
167
|
- lib/multiple_man.rb
|
168
168
|
- lib/multiple_man/attribute_extractor.rb
|
169
|
+
- lib/multiple_man/channel_maintenance/gc.rb
|
170
|
+
- lib/multiple_man/channel_maintenance/reaper.rb
|
169
171
|
- lib/multiple_man/configuration.rb
|
170
172
|
- lib/multiple_man/connection.rb
|
171
173
|
- lib/multiple_man/identity.rb
|
@@ -223,7 +225,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
223
225
|
version: '0'
|
224
226
|
requirements: []
|
225
227
|
rubyforge_project:
|
226
|
-
rubygems_version: 2.4.
|
228
|
+
rubygems_version: 2.4.7
|
227
229
|
signing_key:
|
228
230
|
specification_version: 4
|
229
231
|
summary: MultipleMan syncs changes to ActiveRecord models via AMQP
|