multiple_man 0.6.1 → 0.7.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 +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
|