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 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