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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d859ac14aeccc95ff5ab2ec4d1b5e4cb07f18e77
4
- data.tar.gz: e5723029260e45a9012c8394ca3b7c4e9fab8535
3
+ metadata.gz: 8dbcb9ce65f9deaea7bc949150dcad625861594e
4
+ data.tar.gz: f6fd179c4f92d65d0df27c23ad13bfeaf0359e37
5
5
  SHA512:
6
- metadata.gz: f696e8233f25addccab4bd17b2fe2757dd798664aaf727327ce615b60b92a18e3e2da568d10af11b26e780ffd1be0ead24b922a4a30bb1fda01def94ed636167
7
- data.tar.gz: 750cd59c5e45f14aa97f915bd9ddb2a371ff7f982435cb0629fb3821204de619a132950bcbde2ed1721b1ecd2c1548930caa74e27cd7d98d613b8f4bb93f9fa5
6
+ metadata.gz: 8e53300b5197df7628f32a22f8440a03a2d4c91977b4e3ea6a4033b3a6a02f6832862ac1f30744295f12bd2690495cf9e24046fb58947d59933c0a90e942995e
7
+ data.tar.gz: 9b3a72cb0e46d7560b0b2736650928587f1f2ab21f8b30ce506dbfd66fc37a4af57e2dd1663a07397a8d5025e7d71e00e3721471fc865a79281ec8de14a7e279
data/README.md CHANGED
@@ -157,7 +157,7 @@ def ListenerClass
157
157
  # do something when a model is created
158
158
  end
159
159
  end
160
- ``
160
+ ```
161
161
 
162
162
  ## Listening for subscriptions
163
163
 
@@ -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, :channel_pool_size, :error_handler,
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
- connection = Bunny.new(MultipleMan.configuration.connection)
10
- MultipleMan.logger.debug "Connecting to #{MultipleMan.configuration.connection}"
11
- connection.start
12
- channel = connection.create_channel
13
- yield new(channel) if block_given?
14
- ensure
15
- channel.close if channel && channel.open?
16
- connection.close if connection && connection.open?
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
@@ -5,7 +5,7 @@ module MultipleMan::Listeners
5
5
  end
6
6
 
7
7
  # seeds should only ever be a create
8
- def operation(delivery_info)
8
+ def operation(delivery_info, payload)
9
9
  "create"
10
10
  end
11
11
 
@@ -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) { |r| r.multiple_man_publish(: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
@@ -1,3 +1,3 @@
1
1
  module MultipleMan
2
- VERSION = "0.6.1"
2
+ VERSION = "0.7.0"
3
3
  end
data/lib/multiple_man.rb CHANGED
@@ -22,6 +22,9 @@ module MultipleMan
22
22
  require 'multiple_man/identity'
23
23
  require 'multiple_man/publish'
24
24
 
25
+ require 'multiple_man/channel_maintenance/gc'
26
+ require 'multiple_man/channel_maintenance/reaper'
27
+
25
28
  def self.logger
26
29
  configuration.logger
27
30
  end
@@ -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
 
@@ -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.6.1
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-05-14 00:00:00.000000000 Z
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.6
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