promiscuous 0.90.0 → 0.91.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 +7 -0
- data/lib/promiscuous/amqp/bunny.rb +63 -36
- data/lib/promiscuous/amqp/fake.rb +3 -1
- data/lib/promiscuous/amqp/hot_bunnies.rb +26 -16
- data/lib/promiscuous/amqp/null.rb +1 -0
- data/lib/promiscuous/amqp.rb +12 -12
- data/lib/promiscuous/cli.rb +70 -29
- data/lib/promiscuous/config.rb +54 -29
- data/lib/promiscuous/convenience.rb +1 -1
- data/lib/promiscuous/dependency.rb +25 -6
- data/lib/promiscuous/error/connection.rb +11 -9
- data/lib/promiscuous/error/dependency.rb +8 -1
- data/lib/promiscuous/loader.rb +4 -2
- data/lib/promiscuous/publisher/bootstrap/connection.rb +25 -0
- data/lib/promiscuous/publisher/bootstrap/data.rb +127 -0
- data/lib/promiscuous/publisher/bootstrap/mode.rb +19 -0
- data/lib/promiscuous/publisher/bootstrap/status.rb +40 -0
- data/lib/promiscuous/publisher/bootstrap/version.rb +46 -0
- data/lib/promiscuous/publisher/bootstrap.rb +27 -0
- data/lib/promiscuous/publisher/context/base.rb +67 -0
- data/lib/promiscuous/{middleware.rb → publisher/context/middleware.rb} +16 -13
- data/lib/promiscuous/publisher/context/transaction.rb +36 -0
- data/lib/promiscuous/publisher/context.rb +4 -88
- data/lib/promiscuous/publisher/mock_generator.rb +9 -9
- data/lib/promiscuous/publisher/model/active_record.rb +7 -7
- data/lib/promiscuous/publisher/model/base.rb +29 -29
- data/lib/promiscuous/publisher/model/ephemeral.rb +5 -3
- data/lib/promiscuous/publisher/model/mock.rb +9 -5
- data/lib/promiscuous/publisher/model/mongoid.rb +5 -22
- data/lib/promiscuous/publisher/operation/active_record.rb +360 -0
- data/lib/promiscuous/publisher/operation/atomic.rb +167 -0
- data/lib/promiscuous/publisher/operation/base.rb +279 -474
- data/lib/promiscuous/publisher/operation/mongoid.rb +153 -145
- data/lib/promiscuous/publisher/operation/non_persistent.rb +28 -0
- data/lib/promiscuous/publisher/operation/proxy_for_query.rb +42 -0
- data/lib/promiscuous/publisher/operation/transaction.rb +85 -0
- data/lib/promiscuous/publisher/operation.rb +1 -1
- data/lib/promiscuous/publisher/worker.rb +7 -7
- data/lib/promiscuous/publisher.rb +1 -1
- data/lib/promiscuous/railtie.rb +20 -5
- data/lib/promiscuous/redis.rb +104 -56
- data/lib/promiscuous/subscriber/message_processor/base.rb +38 -0
- data/lib/promiscuous/subscriber/message_processor/bootstrap.rb +17 -0
- data/lib/promiscuous/subscriber/message_processor/regular.rb +192 -0
- data/lib/promiscuous/subscriber/message_processor.rb +4 -0
- data/lib/promiscuous/subscriber/model/base.rb +20 -15
- data/lib/promiscuous/subscriber/model/mongoid.rb +4 -4
- data/lib/promiscuous/subscriber/model/observer.rb +16 -2
- data/lib/promiscuous/subscriber/operation/base.rb +68 -0
- data/lib/promiscuous/subscriber/operation/bootstrap.rb +54 -0
- data/lib/promiscuous/subscriber/operation/regular.rb +13 -0
- data/lib/promiscuous/subscriber/operation.rb +3 -166
- data/lib/promiscuous/subscriber/worker/message.rb +61 -35
- data/lib/promiscuous/subscriber/worker/message_synchronizer.rb +90 -59
- data/lib/promiscuous/subscriber/worker/pump.rb +17 -5
- data/lib/promiscuous/subscriber/worker/recorder.rb +4 -1
- data/lib/promiscuous/subscriber/worker/runner.rb +49 -9
- data/lib/promiscuous/subscriber/worker/stats.rb +2 -2
- data/lib/promiscuous/subscriber/worker.rb +6 -0
- data/lib/promiscuous/subscriber.rb +1 -1
- data/lib/promiscuous/timer.rb +31 -18
- data/lib/promiscuous/version.rb +1 -1
- data/lib/promiscuous.rb +23 -3
- metadata +104 -89
- data/lib/promiscuous/subscriber/payload.rb +0 -34
@@ -1,18 +1,20 @@
|
|
1
1
|
class Promiscuous::Error::Connection < Promiscuous::Error::Base
|
2
|
-
attr_accessor :
|
2
|
+
attr_accessor :url, :inner
|
3
3
|
|
4
|
-
def initialize(options={})
|
4
|
+
def initialize(url, options={})
|
5
5
|
super(nil)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
when :amqp then Promiscuous::Config.amqp_url
|
11
|
-
end
|
6
|
+
# XXX TODO The url may contain a password, we should filter it out
|
7
|
+
@url = url
|
8
|
+
@inner = options[:inner]
|
9
|
+
set_backtrace(@inner.backtrace) if @inner
|
12
10
|
end
|
13
11
|
|
14
12
|
def message
|
15
|
-
|
13
|
+
if @inner
|
14
|
+
"Lost connection with #{@url}: #{@inner.class}: #{@inner.message}"
|
15
|
+
else
|
16
|
+
"Lost connection with #{@url}"
|
17
|
+
end
|
16
18
|
end
|
17
19
|
|
18
20
|
alias to_s message
|
@@ -74,7 +74,7 @@ class Promiscuous::Error::Dependency < Promiscuous::Error::Base
|
|
74
74
|
msg += "\n\nProTip: Try again with TRACE=2 in the shell or ENV['TRACE']='2' in the console.\n" unless ENV['TRACE']
|
75
75
|
msg
|
76
76
|
rescue Exception => e
|
77
|
-
"#{e
|
77
|
+
"#{e}\n#{e.backtrace.join("\n")}"
|
78
78
|
end
|
79
79
|
|
80
80
|
def self.explain_operation(operation, limit=100)
|
@@ -96,12 +96,19 @@ class Promiscuous::Error::Dependency < Promiscuous::Error::Base
|
|
96
96
|
if operation.operation == :update && operation.respond_to?(:change) && operation.change
|
97
97
|
msg += "(#{get_selector(operation.change, limit)})"
|
98
98
|
end
|
99
|
+
|
100
|
+
if operation.operation == :commit
|
101
|
+
msg = "Transaction commit"
|
102
|
+
end
|
103
|
+
|
99
104
|
msg
|
100
105
|
end
|
101
106
|
end
|
102
107
|
|
103
108
|
def self.get_selector(attributes, limit=100)
|
109
|
+
# TODO ActiveRecord?
|
104
110
|
attributes = attributes['$set'] if attributes.count == 1 && attributes['$set']
|
111
|
+
attributes.reject! { |k,v| v.nil? }
|
105
112
|
selector = attributes.map { |k,v| ":#{k} => #{v}" }.join(", ")
|
106
113
|
selector = "#{selector[0...(limit-3)]}..." if selector.size > limit
|
107
114
|
selector
|
data/lib/promiscuous/loader.rb
CHANGED
@@ -8,12 +8,14 @@ module Promiscuous::Loader
|
|
8
8
|
end
|
9
9
|
|
10
10
|
# A one shot recovery on boot
|
11
|
-
Promiscuous::
|
11
|
+
if Promiscuous::Config.recovery_on_boot
|
12
|
+
Promiscuous::Publisher::Worker.new.try_recover
|
13
|
+
end
|
12
14
|
end
|
13
15
|
|
14
16
|
def self.cleanup
|
15
17
|
Promiscuous::Publisher::Model.publishers.clear
|
16
18
|
Promiscuous::Publisher::Model::Mongoid.collection_mapping.clear if defined?(Mongoid)
|
17
|
-
Promiscuous::Subscriber::Model.mapping.
|
19
|
+
Promiscuous::Subscriber::Model.mapping.values.reject! { |as| as =~ /^Promiscuous::/ }
|
18
20
|
end
|
19
21
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Promiscuous::Publisher::Bootstrap::Connection
|
2
|
+
def initialize
|
3
|
+
# We don't put the connection in the initializer because it gets funny when
|
4
|
+
# it comes to the disconnection.
|
5
|
+
connection_options = { :url => Promiscuous::Config.publisher_amqp_url,
|
6
|
+
:exchange => Promiscuous::AMQP::BOOTSTRAP_EXCHANGE }
|
7
|
+
@connection, @channel, @exchange = Promiscuous::AMQP.new_connection(connection_options)
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def publish(options={})
|
12
|
+
options[:key] ||= "#{Promiscuous::Config.app}/__bootstrap__"
|
13
|
+
options[:exchange] ||= @exchange
|
14
|
+
Promiscuous::AMQP.publish(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def close
|
18
|
+
if @connection
|
19
|
+
# TODO not very pretty... We should abstract that
|
20
|
+
@channel.respond_to?(:stop) ? @channel.stop : @channel.close
|
21
|
+
@connection.respond_to?(:stop) ? @connection.stop : @connection.close
|
22
|
+
@exchange = nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
class Promiscuous::Publisher::Bootstrap::Data
|
2
|
+
class << self
|
3
|
+
def setup(options={})
|
4
|
+
Promiscuous::Publisher::Bootstrap::Status.reset
|
5
|
+
range_redis_keys.each { |key| redis.del(key) }
|
6
|
+
|
7
|
+
models = options[:models]
|
8
|
+
models ||= Promiscuous::Publisher::Model.publishers.values
|
9
|
+
.reject { |publisher| publisher.include? Promiscuous::Publisher::Model::Ephemeral }
|
10
|
+
|
11
|
+
models.each_with_index do |model, i|
|
12
|
+
create_range(i, model, options)
|
13
|
+
Promiscuous::Publisher::Bootstrap::Status.total(model.count)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(options={})
|
18
|
+
connection = Promiscuous::Publisher::Bootstrap::Connection.new
|
19
|
+
|
20
|
+
range_redis_keys.each do |key|
|
21
|
+
lock = Promiscuous::Redis::Mutex.new(key, lock_options(options[:lock]))
|
22
|
+
if lock.try_lock
|
23
|
+
if range = redis.get(key)
|
24
|
+
range = MultiJson.load(range)
|
25
|
+
selector = range['selector']
|
26
|
+
options = range['options']
|
27
|
+
klass = range['class'].constantize
|
28
|
+
start = range['start'].to_i
|
29
|
+
finish = range['finish'].to_i
|
30
|
+
|
31
|
+
lock_extended_at = Time.now
|
32
|
+
without_promiscuous do
|
33
|
+
range_selector(klass, selector, options, start, finish).each_with_index do |instance, i|
|
34
|
+
publish_data(connection, instance)
|
35
|
+
|
36
|
+
if (Time.now - lock_extended_at) > lock_options[:expire]/5
|
37
|
+
raise "Another worker stole your work!" unless lock.extend
|
38
|
+
lock_extended_at = Time.now
|
39
|
+
end
|
40
|
+
Promiscuous::Publisher::Bootstrap::Status.inc
|
41
|
+
end
|
42
|
+
redis.del(key)
|
43
|
+
lock.unlock
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def create_range(namespace, model, options)
|
53
|
+
range_size = options[:range_size] || 1000
|
54
|
+
|
55
|
+
without_promiscuous do
|
56
|
+
count = model.all.count
|
57
|
+
return if count == 0
|
58
|
+
|
59
|
+
num_ranges = (count/range_size.to_f).ceil
|
60
|
+
first, last = min_max(model).map { |id| id.to_s.to_i(16) }
|
61
|
+
last += 10 # Ensure that we capture the last ID based on BSON encoding
|
62
|
+
increment = ((last - first)/num_ranges).ceil
|
63
|
+
|
64
|
+
num_ranges.times do |i|
|
65
|
+
range_start = first + (increment * i)
|
66
|
+
range_finish = range_start + increment
|
67
|
+
|
68
|
+
key = range_redis_key.join(namespace).join(i)
|
69
|
+
value = MultiJson.dump(:selector => model.all.selector,
|
70
|
+
:options => model.all.options,
|
71
|
+
:class => model.all.klass.to_s,
|
72
|
+
:start => range_start.to_s,
|
73
|
+
:finish => range_finish.to_s)
|
74
|
+
redis.set(key, value)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def range_selector(klass, selector, options, start, finish)
|
80
|
+
option ||= {}
|
81
|
+
criteria = Mongoid::Criteria.new(klass)
|
82
|
+
criteria.selector = selector
|
83
|
+
criteria.options = options.merge(:timeout => false)
|
84
|
+
|
85
|
+
criteria.order_by("$natural" => 1).where(:_id => { '$gte' => BSON::ObjectId.from_string(start.to_s(16)),
|
86
|
+
'$lt' => BSON::ObjectId.from_string(finish.to_s(16)) })
|
87
|
+
end
|
88
|
+
|
89
|
+
def range_redis_key
|
90
|
+
Promiscuous::Key.new(:pub).join('bootstrap:range')
|
91
|
+
end
|
92
|
+
|
93
|
+
def range_redis_keys
|
94
|
+
redis.keys("#{range_redis_key}*").reject { |k| k =~ /:lock$/ }.sort
|
95
|
+
end
|
96
|
+
|
97
|
+
def lock_options(options=nil)
|
98
|
+
options ||= {}
|
99
|
+
@@lock_options ||= {
|
100
|
+
:timeout => 10.seconds,
|
101
|
+
:sleep => 0.01.seconds,
|
102
|
+
:expire => 5.minutes,
|
103
|
+
:node => redis
|
104
|
+
}.merge(options)
|
105
|
+
end
|
106
|
+
|
107
|
+
def publish_data(connection, instance)
|
108
|
+
operation = instance.promiscuous.payload
|
109
|
+
operation[:operation] = :bootstrap_data
|
110
|
+
|
111
|
+
payload = {}
|
112
|
+
payload[:app] = Promiscuous::Config.app
|
113
|
+
payload[:operations] = [operation]
|
114
|
+
|
115
|
+
connection.publish(:key => Promiscuous::Config.app, :payload => MultiJson.dump(payload))
|
116
|
+
end
|
117
|
+
|
118
|
+
def min_max(model)
|
119
|
+
query = proc { |sort_order| model.order_by("_id" => sort_order).only(:id).limit(1).first.id }
|
120
|
+
[query.call(1), query.call(-1)]
|
121
|
+
end
|
122
|
+
|
123
|
+
def redis
|
124
|
+
Promiscuous::Redis.master.nodes.first
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Promiscuous::Publisher::Bootstrap::Mode
|
2
|
+
def self.enable
|
3
|
+
Promiscuous::Redis.master.nodes.each { |node| node.set(key, 1) }
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.disable
|
7
|
+
Promiscuous::Redis.master.nodes.each { |node| node.del(key) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.enabled?
|
11
|
+
!!Promiscuous::Redis.master.nodes.first.get(key)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.key
|
15
|
+
# XXX You must change the LUA script in promiscuous/publisher/operation/base.rb
|
16
|
+
# if you change this value
|
17
|
+
Promiscuous::Key.new(:pub).join('bootstrap')
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'ruby-progressbar'
|
2
|
+
|
3
|
+
module Promiscuous::Publisher::Bootstrap::Status
|
4
|
+
def self.reset
|
5
|
+
redis.del(key)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.total(count)
|
9
|
+
redis.hincrby(key, 'total', count)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.inc
|
13
|
+
redis.hincrby(key, 'processed', 1)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.monitor
|
17
|
+
total ||= redis.hget(key, 'total').to_i
|
18
|
+
processed = 0
|
19
|
+
exit_now = false
|
20
|
+
|
21
|
+
%w(SIGTERM SIGINT).each { |signal| Signal.trap(signal) { exit_now = true } }
|
22
|
+
bar = ProgressBar.create(:format => '%t |%b>%i| %c/%C %e', :title => "Bootstrapping", :total => total)
|
23
|
+
while processed < total
|
24
|
+
processed = redis.hget(key, 'processed').to_i
|
25
|
+
bar.progress = processed
|
26
|
+
sleep 1
|
27
|
+
break if exit_now
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def self.key
|
34
|
+
Promiscuous::Key.new(:pub).join('bootstrap:counter')
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.redis
|
38
|
+
Promiscuous::Redis.master.nodes.first
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Promiscuous::Publisher::Bootstrap::Version
|
2
|
+
def self.bootstrap
|
3
|
+
connection = Promiscuous::Publisher::Bootstrap::Connection.new
|
4
|
+
Promiscuous::Redis.master.nodes.each_with_index do |node, node_index|
|
5
|
+
begin_at = 0
|
6
|
+
chunk_size = Promiscuous::Config.bootstrap_chunk_size
|
7
|
+
|
8
|
+
while begin_at < Promiscuous::Config.hash_size do
|
9
|
+
end_at = [begin_at + chunk_size, Promiscuous::Config.hash_size].min
|
10
|
+
Chunk.new(connection, node, node_index, (begin_at...end_at)).fetch_and_send
|
11
|
+
begin_at += chunk_size
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Chunk
|
17
|
+
def initialize(connection, node, node_index, range)
|
18
|
+
@connection = connection
|
19
|
+
@node = node
|
20
|
+
@range = range
|
21
|
+
@node_index = node_index
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch_and_send
|
25
|
+
num_nodes = Promiscuous::Redis.master.nodes.size
|
26
|
+
|
27
|
+
futures = {}
|
28
|
+
@node.pipelined do
|
29
|
+
@range.each do |i|
|
30
|
+
next unless i % num_nodes == @node_index
|
31
|
+
futures[i] = @node.get(Promiscuous::Key.new(:pub).join(i, 'rw'))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
operation = {}
|
36
|
+
operation[:operation] = :bootstrap_versions
|
37
|
+
operation[:keys] = futures.map { |i, f| "#{i}:#{f.value}" if f.value }.compact
|
38
|
+
|
39
|
+
payload = {}
|
40
|
+
payload[:app] = Promiscuous::Config.app
|
41
|
+
payload[:operations] = [operation]
|
42
|
+
|
43
|
+
@connection.publish(:payload => MultiJson.dump(payload)) if operation[:keys].present?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Promiscuous::Publisher::Bootstrap
|
2
|
+
extend Promiscuous::Autoload
|
3
|
+
autoload :Connection, :Version, :Data, :Mode, :Status
|
4
|
+
|
5
|
+
def self.setup(options={})
|
6
|
+
puts "Enabling bootstrapping mode"
|
7
|
+
Mode.enable
|
8
|
+
puts "Bootstrapping versions..."
|
9
|
+
Version.bootstrap
|
10
|
+
puts "Setting up data bootstrap..."
|
11
|
+
Data.setup(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.run
|
15
|
+
raise "Setup must be run before starting to bootstrap" unless Mode.enabled?
|
16
|
+
Data.run
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.finalize
|
20
|
+
raise "Setup must be run before disabling" unless Mode.enabled?
|
21
|
+
Mode.disable
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.status
|
25
|
+
Status.monitor
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Promiscuous::Publisher::Context::Base
|
2
|
+
# XXX Context are not sharable among threads
|
3
|
+
|
4
|
+
def self.current
|
5
|
+
Thread.current[:promiscuous_context]
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.current=(value)
|
9
|
+
Thread.current[:promiscuous_context] = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.with_context(*args, &block)
|
13
|
+
raise "You cannot nest contexts" if self.current
|
14
|
+
|
15
|
+
self.current = new(*args)
|
16
|
+
begin
|
17
|
+
self.current.trace "<<< open <<<", :level => 1
|
18
|
+
yield
|
19
|
+
ensure
|
20
|
+
self.current.trace "<<< close <<<", :level => 1
|
21
|
+
self.current = nil
|
22
|
+
|
23
|
+
ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_accessor :name, :read_operations, :extra_dependencies, :current_user_id
|
28
|
+
|
29
|
+
def initialize(*args)
|
30
|
+
@name = args[0].try(:to_s) || 'anonymous'
|
31
|
+
@current_user_id = args[1]
|
32
|
+
@read_operations = []
|
33
|
+
@extra_dependencies = []
|
34
|
+
@transaction_managers = {}
|
35
|
+
|
36
|
+
Promiscuous::AMQP.ensure_connected
|
37
|
+
|
38
|
+
Mongoid::IdentityMap.clear if defined?(Mongoid::IdentityMap)
|
39
|
+
ActiveRecord::IdentityMap.clear if defined?(ActiveRecord::IdentityMap)
|
40
|
+
end
|
41
|
+
|
42
|
+
def transaction_context_of(driver)
|
43
|
+
@transaction_managers[driver] ||= Promiscuous::Publisher::Context::Transaction.new(driver)
|
44
|
+
end
|
45
|
+
|
46
|
+
def trace(msg, options={})
|
47
|
+
level = ENV['TRACE'].to_i - options[:level].to_i
|
48
|
+
return if level < 0
|
49
|
+
|
50
|
+
backtrace = options[:backtrace]
|
51
|
+
alt_fmt = options[:alt_fmt]
|
52
|
+
color = options[:color] || "1;36"
|
53
|
+
|
54
|
+
name = " (#{self.name})#{' ' * ([0, 25 - self.name.size].max)}"
|
55
|
+
STDERR.puts "\e[#{color}m#{name}#{alt_fmt ? '':' '} #{msg}\e[0m"
|
56
|
+
|
57
|
+
if level > 1 && defined?(Rails) && backtrace != :none
|
58
|
+
bt = (backtrace || caller)
|
59
|
+
.grep(/#{Rails.root}/)
|
60
|
+
.map { |line| line.gsub(/#{Rails.root}\/?/, '') }
|
61
|
+
.take(level-1)
|
62
|
+
.map { |line| "\e[1;#{30}m#{name} #{line}\e[0m" }
|
63
|
+
.join("\n")
|
64
|
+
STDERR.puts bt
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -1,41 +1,45 @@
|
|
1
|
-
class Promiscuous::Middleware
|
2
|
-
Context = Promiscuous::Publisher::Context
|
3
|
-
|
1
|
+
class Promiscuous::Publisher::Context::Middleware < Promiscuous::Publisher::Context::Base
|
4
2
|
module Controller
|
5
3
|
extend ActiveSupport::Concern
|
6
4
|
|
7
5
|
def process_action(*args)
|
8
6
|
full_name = "#{self.class.controller_path}/#{self.action_name}"
|
9
|
-
|
7
|
+
current_user_id = self.respond_to?(:current_user) ? self.current_user.try(:id) : nil
|
8
|
+
Promiscuous::Publisher::Context::Middleware.with_context(full_name, current_user_id) { super }
|
10
9
|
end
|
11
10
|
|
12
11
|
def render(*args)
|
13
|
-
Promiscuous::Middleware.without_context { super }
|
12
|
+
Promiscuous::Publisher::Context::Middleware.without_context { super }
|
14
13
|
end
|
15
14
|
end
|
16
15
|
|
17
16
|
def self.with_context(*args, &block)
|
18
|
-
|
17
|
+
# XXX We turn off the disabled flag when entering a middleware.
|
18
|
+
# It has priority because it's much simpler to use for testing.
|
19
|
+
old_disabled, Promiscuous.disabled = Promiscuous.disabled?, false
|
20
|
+
super
|
19
21
|
rescue Exception => e
|
20
22
|
$promiscuous_last_exception = e if e.is_a? Promiscuous::Error::Base
|
21
|
-
pretty_print_exception(e)
|
23
|
+
pretty_print_exception(e) unless e.is_a? ActionView::MissingTemplate
|
22
24
|
raise e
|
25
|
+
ensure
|
26
|
+
Promiscuous.disabled = old_disabled
|
23
27
|
end
|
24
28
|
|
25
29
|
def self.without_context
|
26
30
|
# This is different from the method without_promiscuous in convenience.rb
|
27
31
|
# That's used for render() and things that are *not* supposed to write.
|
28
32
|
# We actually force promiscuous to instrument queries, and make sure that
|
29
|
-
# we don't do any
|
30
|
-
|
31
|
-
|
33
|
+
# we don't do any writes we shouldn't.
|
34
|
+
old_disabled, Promiscuous.disabled = Promiscuous.disabled?, false
|
35
|
+
old_current, self.current = self.current, nil
|
32
36
|
yield
|
33
37
|
rescue Exception => e
|
34
38
|
$promiscuous_last_exception = e if e.is_a? Promiscuous::Error::Base
|
35
|
-
pretty_print_exception(e)
|
39
|
+
pretty_print_exception(e) unless e.is_a? ActionView::MissingTemplate
|
36
40
|
raise e
|
37
41
|
ensure
|
38
|
-
|
42
|
+
self.current = old_current
|
39
43
|
Promiscuous.disabled = old_disabled
|
40
44
|
end
|
41
45
|
|
@@ -108,5 +112,4 @@ class Promiscuous::Middleware
|
|
108
112
|
STDERR.puts
|
109
113
|
$promiscuous_pretty_print_exception_once = :disable if $promiscuous_pretty_print_exception_once
|
110
114
|
end
|
111
|
-
|
112
115
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Promiscuous::Publisher::Context::Transaction
|
2
|
+
attr_accessor :driver
|
3
|
+
|
4
|
+
def initialize(driver)
|
5
|
+
@driver = driver
|
6
|
+
|
7
|
+
@indexes = []
|
8
|
+
@write_operations = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def start
|
12
|
+
@indexes << @write_operations.size
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_write_operation(operation)
|
16
|
+
@write_operations << operation
|
17
|
+
end
|
18
|
+
|
19
|
+
def write_operations_to_commit
|
20
|
+
transaction_index = @indexes.last
|
21
|
+
@write_operations[transaction_index..-1]
|
22
|
+
end
|
23
|
+
|
24
|
+
def rollback
|
25
|
+
transaction_index = @indexes.pop
|
26
|
+
@write_operations.slice!(transaction_index..-1)
|
27
|
+
end
|
28
|
+
|
29
|
+
def commit
|
30
|
+
@indexes.pop
|
31
|
+
end
|
32
|
+
|
33
|
+
def in_transaction?
|
34
|
+
!@indexes.empty?
|
35
|
+
end
|
36
|
+
end
|
@@ -1,92 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
def self.open(*args, &block)
|
5
|
-
old_disabled, Promiscuous.disabled = Promiscuous.disabled, false
|
6
|
-
old_current, self.current = self.current, new(*args)
|
7
|
-
|
8
|
-
begin
|
9
|
-
self.current.alt_trace ">>> open >>>", :backtrace => :none if ENV['TRACE']
|
10
|
-
yield
|
11
|
-
ensure
|
12
|
-
self.current.alt_trace "<<< close <<<", :backtrace => :none if ENV['TRACE']
|
13
|
-
|
14
|
-
self.current.close
|
15
|
-
self.current = old_current
|
16
|
-
Promiscuous.disabled = old_disabled
|
17
|
-
end
|
18
|
-
end
|
1
|
+
module Promiscuous::Publisher::Context
|
2
|
+
extend Promiscuous::Autoload
|
3
|
+
autoload :Base, :Transaction, :Middleware
|
19
4
|
|
20
5
|
def self.current
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.current=(value)
|
25
|
-
Thread.current[:promiscuous_context] = value
|
26
|
-
end
|
27
|
-
|
28
|
-
attr_accessor :name, :operations, :nesting_level, :last_write_dependency
|
29
|
-
|
30
|
-
def initialize(*args)
|
31
|
-
options = args.extract_options!
|
32
|
-
@parent = self.class.current unless !!options[:detached_from_parent]
|
33
|
-
@last_write_dependency = @parent.try(:last_write_dependency)
|
34
|
-
@nesting_level = @parent.try(:nesting_level).to_i + 1
|
35
|
-
@name = args.first.try(:to_s)
|
36
|
-
@name ||= "#{@parent.next_child_name}" if @parent
|
37
|
-
@name ||= 'anonymous'
|
38
|
-
@operations = []
|
39
|
-
@next_child = 0
|
40
|
-
|
41
|
-
Promiscuous::AMQP.ensure_connected
|
42
|
-
Mongoid::IdentityMap.clear if defined?(Mongoid::IdentityMap)
|
43
|
-
end
|
44
|
-
|
45
|
-
def close
|
46
|
-
@parent.try(:close_child, self)
|
47
|
-
end
|
48
|
-
|
49
|
-
def close_child(child)
|
50
|
-
@last_write_dependency = child.last_write_dependency if child.last_write_dependency
|
51
|
-
end
|
52
|
-
|
53
|
-
def next_child_name
|
54
|
-
@next_child += 1
|
55
|
-
"#{name}/#{@next_child}"
|
56
|
-
end
|
57
|
-
|
58
|
-
def add_operation(operation)
|
59
|
-
@operations << operation
|
60
|
-
trace_operation(operation) if ENV['TRACE']
|
61
|
-
end
|
62
|
-
|
63
|
-
def trace_operation(operation, options={})
|
64
|
-
msg = Promiscuous::Error::Dependency.explain_operation(operation, 70)
|
65
|
-
trace(msg, options.merge(:color => operation.read? ? '0;32' : '1;31'))
|
66
|
-
end
|
67
|
-
|
68
|
-
def alt_trace(msg, options={})
|
69
|
-
trace(msg, options.merge(:alt_fmt => true))
|
70
|
-
end
|
71
|
-
|
72
|
-
def trace(msg, options={})
|
73
|
-
backtrace = options[:backtrace]
|
74
|
-
alt_fmt = options[:alt_fmt]
|
75
|
-
color = alt_fmt ? "1;36" : options[:color]
|
76
|
-
|
77
|
-
name = "(#{self.name})#{' ' * ([0, 25 - self.name.size].max)}"
|
78
|
-
name = ' ' * @nesting_level + name
|
79
|
-
STDERR.puts "\e[#{color}m#{name}#{alt_fmt ? '':' '} #{msg}\e[0m"
|
80
|
-
|
81
|
-
level = ENV['TRACE'].to_i
|
82
|
-
if level > 1 && defined?(Rails) && backtrace != :none
|
83
|
-
bt = (backtrace || caller)
|
84
|
-
.grep(/#{Rails.root}/)
|
85
|
-
.map { |line| line.gsub(/#{Rails.root}\/?/, '') }
|
86
|
-
.take(level-1)
|
87
|
-
.map { |line| "\e[1;#{30}m#{name} #{line}\e[0m" }
|
88
|
-
.join("\n")
|
89
|
-
STDERR.puts bt
|
90
|
-
end
|
6
|
+
Base.current
|
91
7
|
end
|
92
8
|
end
|
@@ -17,15 +17,14 @@ module Promiscuous::Publisher::MockGenerator
|
|
17
17
|
<% end -%>
|
18
18
|
|
19
19
|
<% publishers.each do |publisher| -%>
|
20
|
-
<% next unless publisher.publish_to -%>
|
21
20
|
<% %>
|
22
21
|
# ------------------------------------------------------------------
|
23
22
|
|
24
23
|
class <%= publisher.publish_as %>
|
25
24
|
include Promiscuous::Publisher::Model::Mock
|
26
|
-
|
25
|
+
mock :from => '<%= Promiscuous::Config.app %>'
|
27
26
|
<% if defined?(Mongoid::Document) && publisher.include?(Mongoid::Document) -%>
|
28
|
-
mock
|
27
|
+
mock :id => :bson
|
29
28
|
<% end -%>
|
30
29
|
<% %>
|
31
30
|
<% attributes_for(publisher).each do |attr| -%>
|
@@ -34,8 +33,8 @@ module Promiscuous::Publisher::MockGenerator
|
|
34
33
|
end
|
35
34
|
|
36
35
|
<% publisher.descendants.each do |subclass| -%>
|
37
|
-
class <%= subclass.publish_as %> < <%=
|
38
|
-
<% attributes_for(subclass,
|
36
|
+
class <%= subclass.publish_as %> < <%= subclass.superclass.publish_as %>
|
37
|
+
<% attributes_for(subclass, subclass.superclass).each do |attr| -%>
|
39
38
|
publish :<%= attr %>
|
40
39
|
<% end -%>
|
41
40
|
end
|
@@ -53,7 +52,7 @@ module Promiscuous::Publisher::MockGenerator
|
|
53
52
|
|
54
53
|
def self.modules
|
55
54
|
publishers
|
56
|
-
.map { |publisher| [publisher, *publisher.descendants.map(&:name)] }
|
55
|
+
.map { |publisher| [publisher.name, *publisher.descendants.map(&:name)] }
|
57
56
|
.flatten
|
58
57
|
.select { |name| name =~ /::/ }
|
59
58
|
.map { |name| name.gsub(/::[^:]+$/, '') }
|
@@ -63,10 +62,11 @@ module Promiscuous::Publisher::MockGenerator
|
|
63
62
|
|
64
63
|
def self.publishers
|
65
64
|
Promiscuous::Publisher::Model.publishers.values
|
66
|
-
.reject { |publisher| publisher.
|
65
|
+
.reject { |publisher| publisher.ancestors.include?(Promiscuous::Publisher::Model::Mock) }
|
66
|
+
.reject { |publisher| publisher.publish_as =~ /^Promiscuous::/ }
|
67
67
|
.map { |publisher| [publisher, publisher.descendants] }
|
68
68
|
.flatten
|
69
|
-
.reject { |publisher| publisher.superclass.respond_to?(:
|
70
|
-
publisher.superclass.
|
69
|
+
.reject { |publisher| publisher.superclass.respond_to?(:publish_as) &&
|
70
|
+
publisher.superclass.publish_as }
|
71
71
|
end
|
72
72
|
end
|