promiscuous 0.53.1 → 0.90.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/lib/promiscuous.rb +25 -28
  2. data/lib/promiscuous/amqp.rb +27 -8
  3. data/lib/promiscuous/amqp/bunny.rb +131 -16
  4. data/lib/promiscuous/amqp/fake.rb +52 -0
  5. data/lib/promiscuous/amqp/hot_bunnies.rb +56 -0
  6. data/lib/promiscuous/amqp/null.rb +6 -6
  7. data/lib/promiscuous/cli.rb +108 -24
  8. data/lib/promiscuous/config.rb +73 -12
  9. data/lib/promiscuous/convenience.rb +18 -0
  10. data/lib/promiscuous/dependency.rb +59 -0
  11. data/lib/promiscuous/dsl.rb +36 -0
  12. data/lib/promiscuous/error.rb +3 -1
  13. data/lib/promiscuous/error/already_processed.rb +5 -0
  14. data/lib/promiscuous/error/base.rb +1 -0
  15. data/lib/promiscuous/error/connection.rb +7 -5
  16. data/lib/promiscuous/error/dependency.rb +111 -0
  17. data/lib/promiscuous/error/lock_unavailable.rb +12 -0
  18. data/lib/promiscuous/error/lost_lock.rb +12 -0
  19. data/lib/promiscuous/error/missing_context.rb +29 -0
  20. data/lib/promiscuous/error/publisher.rb +5 -15
  21. data/lib/promiscuous/error/recovery.rb +7 -0
  22. data/lib/promiscuous/error/subscriber.rb +2 -4
  23. data/lib/promiscuous/key.rb +36 -0
  24. data/lib/promiscuous/loader.rb +12 -16
  25. data/lib/promiscuous/middleware.rb +112 -0
  26. data/lib/promiscuous/publisher.rb +7 -4
  27. data/lib/promiscuous/publisher/context.rb +92 -0
  28. data/lib/promiscuous/publisher/mock_generator.rb +72 -0
  29. data/lib/promiscuous/publisher/model.rb +3 -86
  30. data/lib/promiscuous/publisher/model/active_record.rb +8 -15
  31. data/lib/promiscuous/publisher/model/base.rb +136 -0
  32. data/lib/promiscuous/publisher/model/ephemeral.rb +69 -0
  33. data/lib/promiscuous/publisher/model/mock.rb +61 -0
  34. data/lib/promiscuous/publisher/model/mongoid.rb +57 -100
  35. data/lib/promiscuous/{common/lint.rb → publisher/operation.rb} +1 -1
  36. data/lib/promiscuous/publisher/operation/base.rb +707 -0
  37. data/lib/promiscuous/publisher/operation/mongoid.rb +370 -0
  38. data/lib/promiscuous/publisher/worker.rb +22 -0
  39. data/lib/promiscuous/railtie.rb +21 -3
  40. data/lib/promiscuous/redis.rb +132 -40
  41. data/lib/promiscuous/resque.rb +12 -0
  42. data/lib/promiscuous/sidekiq.rb +15 -0
  43. data/lib/promiscuous/subscriber.rb +9 -20
  44. data/lib/promiscuous/subscriber/model.rb +4 -104
  45. data/lib/promiscuous/subscriber/model/active_record.rb +10 -0
  46. data/lib/promiscuous/subscriber/model/base.rb +96 -0
  47. data/lib/promiscuous/subscriber/model/mongoid.rb +86 -0
  48. data/lib/promiscuous/subscriber/model/observer.rb +37 -0
  49. data/lib/promiscuous/subscriber/operation.rb +167 -0
  50. data/lib/promiscuous/subscriber/payload.rb +34 -0
  51. data/lib/promiscuous/subscriber/worker.rb +22 -18
  52. data/lib/promiscuous/subscriber/worker/message.rb +48 -25
  53. data/lib/promiscuous/subscriber/worker/message_synchronizer.rb +273 -181
  54. data/lib/promiscuous/subscriber/worker/pump.rb +17 -43
  55. data/lib/promiscuous/subscriber/worker/recorder.rb +24 -0
  56. data/lib/promiscuous/subscriber/worker/runner.rb +24 -3
  57. data/lib/promiscuous/subscriber/worker/stats.rb +62 -0
  58. data/lib/promiscuous/timer.rb +38 -0
  59. data/lib/promiscuous/version.rb +1 -1
  60. metadata +98 -143
  61. data/README.md +0 -33
  62. data/lib/promiscuous/amqp/ruby_amqp.rb +0 -140
  63. data/lib/promiscuous/common.rb +0 -4
  64. data/lib/promiscuous/common/class_helpers.rb +0 -12
  65. data/lib/promiscuous/common/lint/base.rb +0 -24
  66. data/lib/promiscuous/common/options.rb +0 -51
  67. data/lib/promiscuous/ephemeral.rb +0 -14
  68. data/lib/promiscuous/error/recover.rb +0 -1
  69. data/lib/promiscuous/observer.rb +0 -5
  70. data/lib/promiscuous/publisher/active_record.rb +0 -7
  71. data/lib/promiscuous/publisher/amqp.rb +0 -18
  72. data/lib/promiscuous/publisher/attributes.rb +0 -32
  73. data/lib/promiscuous/publisher/base.rb +0 -23
  74. data/lib/promiscuous/publisher/class.rb +0 -36
  75. data/lib/promiscuous/publisher/envelope.rb +0 -7
  76. data/lib/promiscuous/publisher/ephemeral.rb +0 -9
  77. data/lib/promiscuous/publisher/lint.rb +0 -35
  78. data/lib/promiscuous/publisher/lint/amqp.rb +0 -14
  79. data/lib/promiscuous/publisher/lint/attributes.rb +0 -12
  80. data/lib/promiscuous/publisher/lint/base.rb +0 -5
  81. data/lib/promiscuous/publisher/lint/class.rb +0 -15
  82. data/lib/promiscuous/publisher/lint/polymorphic.rb +0 -22
  83. data/lib/promiscuous/publisher/mock.rb +0 -79
  84. data/lib/promiscuous/publisher/mongoid.rb +0 -33
  85. data/lib/promiscuous/publisher/mongoid/embedded.rb +0 -27
  86. data/lib/promiscuous/publisher/mongoid/embedded_many.rb +0 -12
  87. data/lib/promiscuous/publisher/polymorphic.rb +0 -8
  88. data/lib/promiscuous/subscriber/active_record.rb +0 -11
  89. data/lib/promiscuous/subscriber/amqp.rb +0 -25
  90. data/lib/promiscuous/subscriber/attributes.rb +0 -35
  91. data/lib/promiscuous/subscriber/base.rb +0 -29
  92. data/lib/promiscuous/subscriber/class.rb +0 -29
  93. data/lib/promiscuous/subscriber/dummy.rb +0 -19
  94. data/lib/promiscuous/subscriber/envelope.rb +0 -18
  95. data/lib/promiscuous/subscriber/lint.rb +0 -30
  96. data/lib/promiscuous/subscriber/lint/amqp.rb +0 -21
  97. data/lib/promiscuous/subscriber/lint/attributes.rb +0 -21
  98. data/lib/promiscuous/subscriber/lint/base.rb +0 -14
  99. data/lib/promiscuous/subscriber/lint/class.rb +0 -13
  100. data/lib/promiscuous/subscriber/lint/polymorphic.rb +0 -39
  101. data/lib/promiscuous/subscriber/mongoid.rb +0 -27
  102. data/lib/promiscuous/subscriber/mongoid/embedded.rb +0 -17
  103. data/lib/promiscuous/subscriber/mongoid/embedded_many.rb +0 -44
  104. data/lib/promiscuous/subscriber/observer.rb +0 -26
  105. data/lib/promiscuous/subscriber/polymorphic.rb +0 -36
  106. data/lib/promiscuous/subscriber/upsert.rb +0 -12
data/lib/promiscuous.rb CHANGED
@@ -1,12 +1,28 @@
1
1
  require 'active_support/core_ext'
2
+ require 'active_model/callbacks'
3
+ require 'multi_json'
2
4
 
3
5
  module Promiscuous
6
+ def self.require_for(gem, file)
7
+ require gem
8
+ require file
9
+ rescue LoadError
10
+ end
11
+
4
12
  require 'promiscuous/autoload'
5
- require 'promiscuous/railtie' if defined?(Rails)
13
+ require_for 'rails', 'promiscuous/railtie'
14
+ require_for 'resque', 'promiscuous/resque'
15
+ require_for 'sidekiq', 'promiscuous/sidekiq'
16
+
6
17
 
7
18
  extend Promiscuous::Autoload
8
19
  autoload :Common, :Publisher, :Subscriber, :Observer, :Worker, :Ephemeral,
9
- :CLI, :Error, :Loader, :AMQP, :Redis, :Config
20
+ :CLI, :Error, :Loader, :AMQP, :Redis, :ZK, :Config, :DSL, :Key,
21
+ :Convenience, :Dependency, :Middleware, :Timer
22
+
23
+ extend Promiscuous::DSL
24
+
25
+ Object.__send__(:include, Promiscuous::Convenience)
10
26
 
11
27
  class << self
12
28
  def configure(&block)
@@ -19,13 +35,6 @@ module Promiscuous
19
35
  end
20
36
  end
21
37
 
22
- def reload
23
- desc = Promiscuous::Publisher::Base.descendants
24
- desc += Promiscuous::Subscriber::Base.descendants
25
- desc.reject! { |klass| klass.name =~ /^Promiscuous::/ }
26
- desc.each { |klass| klass.setup_class_binding }
27
- end
28
-
29
38
  def connect
30
39
  AMQP.connect
31
40
  Redis.connect
@@ -39,36 +48,24 @@ module Promiscuous
39
48
  def healthy?
40
49
  AMQP.ensure_connected
41
50
  Redis.ensure_connected
42
- rescue
51
+ rescue Exception
43
52
  false
44
53
  else
45
54
  true
46
55
  end
47
- end
48
-
49
- module ConsoleHelpers
50
- # These are just for the subscriber, some helpers to debug in production
51
- def global_key
52
- Promiscuous::Redis.sub_key('global')
53
- end
54
56
 
55
- def global_version
56
- Promiscuous::Redis.get(global_key).to_i
57
+ def disabled
58
+ Thread.current[:promiscuous_disabled] || $promiscuous_disabled
57
59
  end
58
60
 
59
- def global_version=(value)
60
- Promiscuous::Redis.set(global_key, value)
61
- Promiscuous::Redis.publish(global_key, value)
62
- value
61
+ def disabled=(value)
62
+ Thread.current[:promiscuous_disabled] = value
63
63
  end
64
64
 
65
- def global_version!
66
- version = Promiscuous::Redis.incr(global_key)
67
- Promiscuous::Redis.publish(global_key, version)
68
- version
65
+ def context(*args, &block)
66
+ Publisher::Context.open(*args, &block)
69
67
  end
70
68
  end
71
- extend ConsoleHelpers
72
69
 
73
70
  at_exit { self.disconnect rescue nil }
74
71
  end
@@ -1,16 +1,18 @@
1
1
  module Promiscuous::AMQP
2
2
  extend Promiscuous::Autoload
3
- autoload :Bunny, :RubyAMQP, :Null
3
+ autoload :HotBunnies, :Bunny, :Null, :Fake
4
4
 
5
- EXCHANGE = 'promiscuous'.freeze
5
+ PUB_EXCHANGE = ENV['PUB_EXCHANGE'] || 'promiscuous'
6
+ SUB_EXCHANGE = ENV['SUB_EXCHANGE'] || 'promiscuous'
6
7
 
7
8
  class << self
8
9
  attr_accessor :backend
10
+ attr_accessor :backend_class
9
11
 
10
12
  def backend=(value)
11
- disconnect if @backend
12
- @backend = value.nil? ? nil : "Promiscuous::AMQP::#{value.to_s.camelize.gsub(/amqp/, 'AMQP')}".constantize
13
- connect if @backend
13
+ disconnect
14
+ @backend_class = value.nil? ? nil : "Promiscuous::AMQP::#{value.to_s.camelize.gsub(/amqp/, 'AMQP')}".constantize
15
+ connect if @backend_class
14
16
  end
15
17
 
16
18
  def lost_connection_exception
@@ -23,10 +25,27 @@ module Promiscuous::AMQP
23
25
 
24
26
  def publish(options={})
25
27
  ensure_connected
26
- Promiscuous.debug "[publish] (#{options[:exchange_name]}) #{options[:key]} -> #{options[:payload]}"
27
- backend.publish(options)
28
+ Promiscuous.debug "[publish] #{options[:key]} -> #{options[:payload]}"
29
+ backend.respond_to?(:async) ? backend.async.publish(options) : backend.publish(options)
28
30
  end
29
31
 
30
- delegate :connect, :disconnect, :connected?, :open_queue, :to => :backend
32
+ def connect
33
+ return if @backend
34
+ @backend = backend_class.new
35
+ @backend.connect
36
+ end
37
+
38
+ def disconnect
39
+ return unless @backend
40
+ @backend.disconnect
41
+ @backend.terminate if @backend.respond_to?(:terminate)
42
+ @backend = nil
43
+ end
44
+
45
+ delegate :connected?, :to => :backend
46
+
47
+ def const_missing(sym)
48
+ backend_class.const_get(sym)
49
+ end
31
50
  end
32
51
  end
@@ -1,30 +1,145 @@
1
- module Promiscuous::AMQP:: Bunny
2
- mattr_accessor :connection
1
+ class Promiscuous::AMQP::Bunny
2
+ def self.hijack_bunny
3
+ return if @bunny_hijacked
4
+ ::Bunny::Session.class_eval do
5
+ alias_method :handle_network_failure_without_promiscuous, :handle_network_failure
3
6
 
4
- def self.connect
7
+ def handle_network_failure(e)
8
+ Promiscuous.warn "[amqp] #{e}. Reconnecting..."
9
+ Promiscuous::Config.error_notifier.try(:call, e)
10
+ handle_network_failure_without_promiscuous(e)
11
+ end
12
+ end
13
+ @bunny_hijacked = true
14
+ end
15
+
16
+ attr_accessor :connection, :connection_lock, :callback_mapping
17
+
18
+ def initialize_driver
5
19
  require 'bunny'
6
- self.connection = ::Bunny.new(Promiscuous::Config.amqp_url, { :heartbeat => Promiscuous::Config.heartbeat })
7
- self.connection.start
20
+ self.class.hijack_bunny
21
+ end
22
+
23
+ def initialize
24
+ initialize_driver
25
+ # The bunny socket doesn't like when multiple threads access to it apparently
26
+ @connection_lock = Mutex.new
27
+ @callback_mapping = {}
28
+ end
29
+
30
+ def connect
31
+ @connection, @channel = new_connection
32
+ @exchange = exchange(@channel, :pub)
33
+ confirm_select(@channel, &method(:on_confirm))
34
+ end
35
+
36
+ def new_connection
37
+ connection = ::Bunny.new(Promiscuous::Config.amqp_url,
38
+ :heartbeat_interval => Promiscuous::Config.heartbeat,
39
+ :socket_timeout => Promiscuous::Config.socket_timeout,
40
+ :connect_timeout => Promiscuous::Config.socket_timeout)
41
+ connection.start
42
+
43
+ channel = connection.create_channel
44
+ [connection, channel]
45
+ end
46
+
47
+ def disconnect
48
+ @connection_lock.synchronize do
49
+ return unless connected?
50
+ @connection.stop
51
+ @connection = @channel = nil
52
+ end
8
53
  end
9
54
 
10
- def self.disconnect
11
- self.connection.stop
55
+ def connected?
56
+ !!@connection.try(:connected?)
12
57
  end
13
58
 
14
- def self.connected?
15
- !!self.connection.try(:connected?)
59
+ def raw_publish(options)
60
+ @exchange.publish(options[:payload], :key => options[:key], :persistent => true)
16
61
  end
17
62
 
18
- def self.publish(options={})
19
- exchange(options[:exchange_name]).
20
- publish(options[:payload], :key => options[:key], :persistent => true)
63
+ def exchange(channel, which)
64
+ exchange_name = which == :pub ? Promiscuous::AMQP::PUB_EXCHANGE :
65
+ Promiscuous::AMQP::SUB_EXCHANGE
66
+ channel.exchange(exchange_name, :type => :topic, :durable => true)
21
67
  end
22
68
 
23
- def self.open_queue(options={}, &block)
24
- raise "Cannot open queue with bunny"
69
+ def publish(options={})
70
+ @connection_lock.synchronize do
71
+ tag = @channel.next_publish_seq_no if options[:on_confirm]
72
+ raw_publish(options)
73
+ @callback_mapping[tag] = options[:on_confirm] if options[:on_confirm]
74
+ end
75
+ rescue Exception => e
76
+ e = Promiscuous::Error::Publisher.new(e, :payload => options[:payload])
77
+ Promiscuous.warn "[publish] #{e} #{e.backtrace.join("\n")}"
78
+ Promiscuous::Config.error_notifier.try(:call, e)
25
79
  end
26
80
 
27
- def self.exchange(name)
28
- connection.exchange(name, :type => :topic, :durable => true)
81
+ def confirm_select(channel, &callback)
82
+ channel.confirm_select(callback)
83
+ end
84
+
85
+ def on_confirm(tag, multiple, nack=false)
86
+ if multiple
87
+ cbs = @callback_mapping.keys
88
+ .select { |k| k <= tag }
89
+ .map { |k| @callback_mapping.delete(k) }
90
+ cbs.each(&:call) unless nack
91
+ else
92
+ cb = @callback_mapping.delete(tag)
93
+ cb.try(:call) unless nack
94
+ end
95
+ end
96
+
97
+ module Subscriber
98
+ def subscribe(options={}, &block)
99
+ queue_name = options[:queue_name]
100
+ bindings = options[:bindings]
101
+ Promiscuous::AMQP.ensure_connected
102
+
103
+ @lock = Mutex.new
104
+ @connection, @channel = Promiscuous::AMQP.backend.new_connection
105
+ @channel.basic_qos(Promiscuous::Config.prefetch)
106
+ exchange = Promiscuous::AMQP.backend.exchange(@channel, :sub)
107
+ @queue = @channel.queue(queue_name, Promiscuous::Config.queue_options)
108
+ bindings.each do |binding|
109
+ @queue.bind(exchange, :routing_key => binding)
110
+ Promiscuous.debug "[bind] #{queue_name} -> #{binding}"
111
+ end
112
+
113
+ @subscription = subscribe_queue(@queue, &block)
114
+ end
115
+
116
+ def subscribe_queue(queue, &block)
117
+ queue.subscribe(:ack => true) do |delivery_info, metadata, payload|
118
+ block.call(MetaData.new(self, delivery_info), payload)
119
+ end
120
+ end
121
+
122
+ class MetaData
123
+ def initialize(subscriber, delivery_info)
124
+ @subscriber = subscriber
125
+ @delivery_info = delivery_info
126
+ end
127
+
128
+ def ack
129
+ @subscriber.ack_message(@delivery_info.delivery_tag)
130
+ end
131
+ end
132
+
133
+ def ack_message(tag)
134
+ @lock.synchronize { @channel.ack(tag) } if @channel
135
+ end
136
+
137
+ def recover
138
+ @lock.synchronize { @channel.basic_recover(true) } if @channel
139
+ end
140
+
141
+ def disconnect
142
+ @lock.synchronize { @connection.stop; @channel = nil }
143
+ end
29
144
  end
30
145
  end
@@ -0,0 +1,52 @@
1
+ class Promiscuous::AMQP::Fake
2
+ attr_accessor :messages
3
+
4
+ class << self
5
+ def backend
6
+ Promiscuous::AMQP.backend
7
+ end
8
+ delegate :num_messages, :get_next_message, :get_next_payload, :to => :backend
9
+ end
10
+
11
+ def connect
12
+ @messages = []
13
+ end
14
+
15
+ def disconnect
16
+ end
17
+
18
+ def connected?
19
+ true
20
+ end
21
+
22
+ def publish(options={})
23
+ @messages << options
24
+ options[:on_confirm].try(:call)
25
+ end
26
+
27
+ def num_messages
28
+ @messages.count
29
+ end
30
+
31
+ def get_next_message
32
+ @messages.shift
33
+ end
34
+
35
+ def get_next_payload
36
+ JSON.parse(get_next_message[:payload])
37
+ end
38
+
39
+ def open_queue(options={}, &block)
40
+ end
41
+
42
+ module Subscriber
43
+ def subscribe(options={}, &block)
44
+ end
45
+
46
+ def recover
47
+ end
48
+
49
+ def disconnect
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,56 @@
1
+ class Promiscuous::AMQP::HotBunnies < Promiscuous::AMQP::Bunny
2
+ attr_accessor :connection
3
+
4
+ def initialize_driver
5
+ require 'hot_bunnies'
6
+ end
7
+
8
+ # TODO auto reconnect
9
+
10
+ def new_connection
11
+ connection = ::HotBunnies.connect(:uri => Promiscuous::Config.amqp_url,
12
+ :heartbeat_interval => Promiscuous::Config.heartbeat,
13
+ :connection_timeout => Promiscuous::Config.socket_timeout)
14
+
15
+ channel = connection.create_channel
16
+ [connection, channel]
17
+ end
18
+
19
+ def disconnect
20
+ @connection_lock.synchronize do
21
+ return unless connected?
22
+ @channel.close rescue nil
23
+ @connection.close rescue nil
24
+ @connection = @channel = nil
25
+ end
26
+ end
27
+
28
+ def connected?
29
+ !!@connection.try(:is_open)
30
+ end
31
+
32
+ def raw_publish(options={})
33
+ @exchange.publish(options[:payload], :routing_key => options[:key], :persistent => true)
34
+ end
35
+
36
+ def confirm_select(channel, &callback)
37
+ channel.add_confirm_listener(&callback)
38
+ channel.confirm_select
39
+ end
40
+
41
+ module Subscriber
42
+ include Promiscuous::AMQP::Bunny::Subscriber
43
+
44
+ def subscribe_queue(queue, &block)
45
+ queue.subscribe(:ack => true, :blocking => false, &block)
46
+ end
47
+
48
+ def disconnect
49
+ @lock.synchronize do
50
+ @channel = nil
51
+ @subscription.shutdown! rescue nil
52
+ @connection.close rescue nil
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,17 +1,17 @@
1
- module Promiscuous::AMQP::Null
2
- def self.connect
1
+ class Promiscuous::AMQP::Null
2
+ def connect
3
3
  end
4
4
 
5
- def self.disconnect
5
+ def disconnect
6
6
  end
7
7
 
8
- def self.connected?
8
+ def connected?
9
9
  true
10
10
  end
11
11
 
12
- def self.publish(options={})
12
+ def publish(options={})
13
13
  end
14
14
 
15
- def self.open_queue(options={}, &block)
15
+ def open_queue(options={}, &block)
16
16
  end
17
17
  end
@@ -1,10 +1,10 @@
1
1
  class Promiscuous::CLI
2
2
  attr_accessor :options
3
3
 
4
- def trap_signals
4
+ def trap_debug_signals
5
5
  Signal.trap 'SIGUSR2' do
6
6
  Thread.list.each do |thread|
7
- print_status '-' * 80
7
+ print_status '----[ Threads ]----' + '-' * (100-19)
8
8
  if thread.backtrace
9
9
  print_status "Thread #{thread} #{thread['label']}"
10
10
  print_status thread.backtrace.join("\n")
@@ -12,37 +12,98 @@ class Promiscuous::CLI
12
12
  print_status "Thread #{thread} #{thread['label']} -- no backtrace"
13
13
  end
14
14
  end
15
+
16
+ if @worker && @worker.respond_to?(:message_synchronizer)
17
+ if blocked_messages = @worker.message_synchronizer.try(:blocked_messages)
18
+ print_status '----[ Pending Dependencies ]----' + '-' * (100-32)
19
+ blocked_messages.reverse_each { |msg| print_status msg }
20
+ end
21
+ print_status '-' * 80
22
+ end
15
23
  end
24
+ end
16
25
 
26
+ def trap_exit_signals
17
27
  %w(SIGTERM SIGINT).each do |signal|
18
28
  Signal.trap(signal) do
29
+ exit 1 if @stop
19
30
  print_status "Exiting..."
20
- @worker.terminate if @worker.try(:alive?)
31
+ @worker.try(:stop)
32
+ @worker = nil
21
33
  @stop = true
22
34
  end
23
35
  end
24
36
  end
25
37
 
38
+ def trap_signals
39
+ trap_debug_signals
40
+ trap_exit_signals
41
+ end
42
+
26
43
  def publish
27
44
  options[:criterias].map { |criteria| eval(criteria) }.each do |criteria|
28
45
  break if @stop
29
46
  title = criteria.name
30
47
  title = "#{title}#{' ' * [0, 20 - title.size].max}"
31
48
  bar = ProgressBar.create(:format => '%t |%b>%i| %c/%C %e', :title => title, :total => criteria.count)
32
- criteria.each do |doc|
49
+ criteria.unscoped.each do |doc|
33
50
  break if @stop
34
- doc.promiscuous_sync
51
+ Promiscuous.context("cli/sync") { doc.promiscuous.sync }
35
52
  bar.increment
36
53
  end
37
54
  end
38
55
  end
39
56
 
57
+ def record
58
+ @worker = Promiscuous::Subscriber::Worker::Recorder.new(options[:log_file])
59
+ @worker.start
60
+ print_status "Recording..."
61
+ sleep 0.2 until !@worker
62
+ end
63
+
64
+ def replay_payload(payload)
65
+ endpoint = MultiJson.load(payload)['__amqp__']
66
+ if endpoint
67
+ # TODO confirm
68
+ Promiscuous::AMQP.publish(:key => endpoint, :payload => payload)
69
+ @num_msg += 1
70
+ else
71
+ puts "[warn] missing destination in #{payload}"
72
+ end
73
+ end
74
+
75
+ def replay
76
+ require 'json'
77
+ @num_msg = 0
78
+ File.open(options[:log_file], 'r').each do |line|
79
+ break if @stop
80
+ case line
81
+ when /^\[promiscuous\] \[receive\] ({.*})$/ then replay_payload($1)
82
+ when /^\[promiscuous\] \[publish\] .* -> ({.*})$/ then replay_payload($1)
83
+ when /^({.*})$/ then replay_payload($1)
84
+ end
85
+ end
86
+
87
+ puts "Replayed #{@num_msg} messages"
88
+ end
89
+
40
90
  def subscribe
41
- Promiscuous::Loader.load_descriptors if defined?(Rails)
42
- @worker = Promiscuous::Subscriber::Worker.run!
43
- Celluloid::Actor[:pump].subscribe_sync.wait
44
- print_status "Replicating with #{Promiscuous::Subscriber::AMQP.subscribers.count} subscribers"
45
- sleep 1 until !@worker.alive?
91
+ @worker = Promiscuous::Subscriber::Worker.new
92
+ @worker.start
93
+ print_status "Replicating..."
94
+ sleep 0.2 until !@worker
95
+ end
96
+
97
+ def publisher_recovery
98
+ @worker = Promiscuous::Publisher::Worker.new
99
+ @worker.start
100
+ print_status "Waiting for messages to recover..."
101
+ sleep 0.2 until !@worker
102
+ end
103
+
104
+ def generate_mocks
105
+ f = options[:output] ? File.open(options[:output], 'w') : STDOUT
106
+ f.write Promiscuous::Publisher::MockGenerator.generate
46
107
  end
47
108
 
48
109
  def parse_args(args)
@@ -54,8 +115,12 @@ class Promiscuous::CLI
54
115
 
55
116
  opts.separator ""
56
117
  opts.separator "Actions:"
57
- opts.separator " promiscuous publish \"Member.where(:updated_at.gt => 1.day.ago)\" BrandAction"
118
+ opts.separator " promiscuous publish \"Model1.where(:updated_at.gt => 1.day.ago)\" Model2 Model3..."
58
119
  opts.separator " promiscuous subscribe"
120
+ opts.separator " promiscuous mocks"
121
+ opts.separator " promiscuous record logfile"
122
+ opts.separator " promiscuous replay logfile"
123
+ opts.separator " promiscuous publisher_recovery"
59
124
  opts.separator ""
60
125
  opts.separator "Options:"
61
126
 
@@ -76,6 +141,14 @@ class Promiscuous::CLI
76
141
  Promiscuous::Config.prefetch = prefetch.to_i
77
142
  end
78
143
 
144
+ opts.on "-o", "--output FILE", "Output file for mocks. Defaults to stdout" do |file|
145
+ options[:output] = file
146
+ end
147
+
148
+ opts.on "-s", "--stat-interval [DURATION]", "Stats refresh rate (0 to disable)" do |duration|
149
+ Promiscuous::Config.stats_interval = duration.to_f
150
+ end
151
+
79
152
  opts.on("-h", "--help", "Show this message") do
80
153
  puts opts
81
154
  exit
@@ -93,19 +166,21 @@ class Promiscuous::CLI
93
166
 
94
167
  options[:action] = args.shift.try(:to_sym)
95
168
  options[:criterias] = args
169
+ options[:log_file] = args.first
96
170
 
97
- unless options[:action].in? [:publish, :subscribe]
98
- puts parser
99
- exit
100
- end
101
-
102
- if options[:action] == :publish
103
- raise "Please specify one or more criterias" unless options[:criterias].present?
104
- else
105
- raise "Why are you specifying a criteria?" if options[:criterias].present?
171
+ case options[:action]
172
+ when :publish then raise "Please specify one or more criterias" unless options[:criterias].present?
173
+ when :subscribe then raise "Why are you specifying a criteria?" if options[:criterias].present?
174
+ when :record then raise "Please specify a log file to record" unless options[:log_file].present?
175
+ when :replay then raise "Please specify a log file to replay" unless options[:log_file].present?
176
+ when :publisher_recovery
177
+ when :mocks
178
+ else puts parser; exit 1
106
179
  end
107
180
 
108
181
  options
182
+ rescue SystemExit
183
+ exit
109
184
  rescue Exception => e
110
185
  puts e
111
186
  exit
@@ -113,10 +188,15 @@ class Promiscuous::CLI
113
188
 
114
189
  def load_app
115
190
  if options[:require]
116
- require options[:require]
191
+ begin
192
+ require options[:require]
193
+ rescue LoadError
194
+ require "./#{options[:require]}"
195
+ end
117
196
  else
118
197
  require 'rails'
119
- require File.expand_path("./config/environment.rb")
198
+ require 'promiscuous/railtie'
199
+ require File.expand_path("./config/environment")
120
200
  ::Rails.application.eager_load!
121
201
  end
122
202
  end
@@ -131,8 +211,12 @@ class Promiscuous::CLI
131
211
  def run
132
212
  trap_signals
133
213
  case options[:action]
134
- when :publish then publish
214
+ when :publish then publish
135
215
  when :subscribe then subscribe
216
+ when :record then record
217
+ when :replay then replay
218
+ when :mocks then generate_mocks
219
+ when :publisher_recovery then publisher_recovery
136
220
  end
137
221
  end
138
222
 
@@ -146,6 +230,6 @@ class Promiscuous::CLI
146
230
 
147
231
  def print_status(msg)
148
232
  Promiscuous.info msg
149
- $stderr.puts msg
233
+ STDERR.puts msg
150
234
  end
151
235
  end