promiscuous 0.53.1 → 0.90.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.
- data/lib/promiscuous.rb +25 -28
- data/lib/promiscuous/amqp.rb +27 -8
- data/lib/promiscuous/amqp/bunny.rb +131 -16
- data/lib/promiscuous/amqp/fake.rb +52 -0
- data/lib/promiscuous/amqp/hot_bunnies.rb +56 -0
- data/lib/promiscuous/amqp/null.rb +6 -6
- data/lib/promiscuous/cli.rb +108 -24
- data/lib/promiscuous/config.rb +73 -12
- data/lib/promiscuous/convenience.rb +18 -0
- data/lib/promiscuous/dependency.rb +59 -0
- data/lib/promiscuous/dsl.rb +36 -0
- data/lib/promiscuous/error.rb +3 -1
- data/lib/promiscuous/error/already_processed.rb +5 -0
- data/lib/promiscuous/error/base.rb +1 -0
- data/lib/promiscuous/error/connection.rb +7 -5
- data/lib/promiscuous/error/dependency.rb +111 -0
- data/lib/promiscuous/error/lock_unavailable.rb +12 -0
- data/lib/promiscuous/error/lost_lock.rb +12 -0
- data/lib/promiscuous/error/missing_context.rb +29 -0
- data/lib/promiscuous/error/publisher.rb +5 -15
- data/lib/promiscuous/error/recovery.rb +7 -0
- data/lib/promiscuous/error/subscriber.rb +2 -4
- data/lib/promiscuous/key.rb +36 -0
- data/lib/promiscuous/loader.rb +12 -16
- data/lib/promiscuous/middleware.rb +112 -0
- data/lib/promiscuous/publisher.rb +7 -4
- data/lib/promiscuous/publisher/context.rb +92 -0
- data/lib/promiscuous/publisher/mock_generator.rb +72 -0
- data/lib/promiscuous/publisher/model.rb +3 -86
- data/lib/promiscuous/publisher/model/active_record.rb +8 -15
- data/lib/promiscuous/publisher/model/base.rb +136 -0
- data/lib/promiscuous/publisher/model/ephemeral.rb +69 -0
- data/lib/promiscuous/publisher/model/mock.rb +61 -0
- data/lib/promiscuous/publisher/model/mongoid.rb +57 -100
- data/lib/promiscuous/{common/lint.rb → publisher/operation.rb} +1 -1
- data/lib/promiscuous/publisher/operation/base.rb +707 -0
- data/lib/promiscuous/publisher/operation/mongoid.rb +370 -0
- data/lib/promiscuous/publisher/worker.rb +22 -0
- data/lib/promiscuous/railtie.rb +21 -3
- data/lib/promiscuous/redis.rb +132 -40
- data/lib/promiscuous/resque.rb +12 -0
- data/lib/promiscuous/sidekiq.rb +15 -0
- data/lib/promiscuous/subscriber.rb +9 -20
- data/lib/promiscuous/subscriber/model.rb +4 -104
- data/lib/promiscuous/subscriber/model/active_record.rb +10 -0
- data/lib/promiscuous/subscriber/model/base.rb +96 -0
- data/lib/promiscuous/subscriber/model/mongoid.rb +86 -0
- data/lib/promiscuous/subscriber/model/observer.rb +37 -0
- data/lib/promiscuous/subscriber/operation.rb +167 -0
- data/lib/promiscuous/subscriber/payload.rb +34 -0
- data/lib/promiscuous/subscriber/worker.rb +22 -18
- data/lib/promiscuous/subscriber/worker/message.rb +48 -25
- data/lib/promiscuous/subscriber/worker/message_synchronizer.rb +273 -181
- data/lib/promiscuous/subscriber/worker/pump.rb +17 -43
- data/lib/promiscuous/subscriber/worker/recorder.rb +24 -0
- data/lib/promiscuous/subscriber/worker/runner.rb +24 -3
- data/lib/promiscuous/subscriber/worker/stats.rb +62 -0
- data/lib/promiscuous/timer.rb +38 -0
- data/lib/promiscuous/version.rb +1 -1
- metadata +98 -143
- data/README.md +0 -33
- data/lib/promiscuous/amqp/ruby_amqp.rb +0 -140
- data/lib/promiscuous/common.rb +0 -4
- data/lib/promiscuous/common/class_helpers.rb +0 -12
- data/lib/promiscuous/common/lint/base.rb +0 -24
- data/lib/promiscuous/common/options.rb +0 -51
- data/lib/promiscuous/ephemeral.rb +0 -14
- data/lib/promiscuous/error/recover.rb +0 -1
- data/lib/promiscuous/observer.rb +0 -5
- data/lib/promiscuous/publisher/active_record.rb +0 -7
- data/lib/promiscuous/publisher/amqp.rb +0 -18
- data/lib/promiscuous/publisher/attributes.rb +0 -32
- data/lib/promiscuous/publisher/base.rb +0 -23
- data/lib/promiscuous/publisher/class.rb +0 -36
- data/lib/promiscuous/publisher/envelope.rb +0 -7
- data/lib/promiscuous/publisher/ephemeral.rb +0 -9
- data/lib/promiscuous/publisher/lint.rb +0 -35
- data/lib/promiscuous/publisher/lint/amqp.rb +0 -14
- data/lib/promiscuous/publisher/lint/attributes.rb +0 -12
- data/lib/promiscuous/publisher/lint/base.rb +0 -5
- data/lib/promiscuous/publisher/lint/class.rb +0 -15
- data/lib/promiscuous/publisher/lint/polymorphic.rb +0 -22
- data/lib/promiscuous/publisher/mock.rb +0 -79
- data/lib/promiscuous/publisher/mongoid.rb +0 -33
- data/lib/promiscuous/publisher/mongoid/embedded.rb +0 -27
- data/lib/promiscuous/publisher/mongoid/embedded_many.rb +0 -12
- data/lib/promiscuous/publisher/polymorphic.rb +0 -8
- data/lib/promiscuous/subscriber/active_record.rb +0 -11
- data/lib/promiscuous/subscriber/amqp.rb +0 -25
- data/lib/promiscuous/subscriber/attributes.rb +0 -35
- data/lib/promiscuous/subscriber/base.rb +0 -29
- data/lib/promiscuous/subscriber/class.rb +0 -29
- data/lib/promiscuous/subscriber/dummy.rb +0 -19
- data/lib/promiscuous/subscriber/envelope.rb +0 -18
- data/lib/promiscuous/subscriber/lint.rb +0 -30
- data/lib/promiscuous/subscriber/lint/amqp.rb +0 -21
- data/lib/promiscuous/subscriber/lint/attributes.rb +0 -21
- data/lib/promiscuous/subscriber/lint/base.rb +0 -14
- data/lib/promiscuous/subscriber/lint/class.rb +0 -13
- data/lib/promiscuous/subscriber/lint/polymorphic.rb +0 -39
- data/lib/promiscuous/subscriber/mongoid.rb +0 -27
- data/lib/promiscuous/subscriber/mongoid/embedded.rb +0 -17
- data/lib/promiscuous/subscriber/mongoid/embedded_many.rb +0 -44
- data/lib/promiscuous/subscriber/observer.rb +0 -26
- data/lib/promiscuous/subscriber/polymorphic.rb +0 -36
- data/lib/promiscuous/subscriber/upsert.rb +0 -12
data/lib/promiscuous/config.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
module Promiscuous::Config
|
|
2
2
|
mattr_accessor :app, :logger, :error_notifier, :backend, :amqp_url,
|
|
3
|
-
:redis_url, :
|
|
4
|
-
:
|
|
3
|
+
:redis_url, :redis_urls, :redis_slave_url, :redis_stats_url,
|
|
4
|
+
:stats_interval, :queue_options, :heartbeat, :bareback,
|
|
5
|
+
:hash_size, :recovery, :prefetch, :recovery_timeout,
|
|
6
|
+
:socket_timeout, :subscriber_threads
|
|
5
7
|
|
|
6
8
|
def self.backend=(value)
|
|
7
9
|
@@backend = value
|
|
@@ -13,17 +15,44 @@ module Promiscuous::Config
|
|
|
13
15
|
class_variables.each { |var| class_variable_set(var, nil) }
|
|
14
16
|
end
|
|
15
17
|
|
|
16
|
-
def self.
|
|
17
|
-
|
|
18
|
+
def self.best_amqp_backend
|
|
19
|
+
if RUBY_PLATFORM == 'java'
|
|
20
|
+
begin
|
|
21
|
+
require 'hot_bunnies'
|
|
22
|
+
:hot_bunnies
|
|
23
|
+
rescue LoadError
|
|
24
|
+
:bunny
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
:bunny
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self._configure(&block)
|
|
32
|
+
block.call(self) if block
|
|
18
33
|
|
|
19
|
-
self.app
|
|
20
|
-
self.amqp_url
|
|
21
|
-
self.redis_url
|
|
22
|
-
self.
|
|
23
|
-
self.
|
|
24
|
-
self.
|
|
25
|
-
self.
|
|
26
|
-
self.
|
|
34
|
+
self.app ||= Rails.application.class.parent_name.underscore rescue nil if defined?(Rails)
|
|
35
|
+
self.amqp_url ||= 'amqp://guest:guest@localhost:5672'
|
|
36
|
+
self.redis_url ||= 'redis://localhost/'
|
|
37
|
+
self.redis_urls ||= [self.redis_url]
|
|
38
|
+
#self.redis_slave_url ||= nil
|
|
39
|
+
self.redis_stats_url ||= self.redis_urls.first
|
|
40
|
+
self.stats_interval ||= 0
|
|
41
|
+
self.socket_timeout ||= 10
|
|
42
|
+
self.backend ||= best_amqp_backend
|
|
43
|
+
self.queue_options ||= {:durable => true, :arguments => {'x-ha-policy' => 'all'}}
|
|
44
|
+
self.heartbeat ||= 60
|
|
45
|
+
self.bareback ||= false
|
|
46
|
+
self.hash_size ||= 2**20 # one million keys ~ 200Mb.
|
|
47
|
+
self.recovery ||= false
|
|
48
|
+
self.prefetch ||= 1000
|
|
49
|
+
self.recovery_timeout ||= 10
|
|
50
|
+
self.logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
|
|
51
|
+
self.subscriber_threads ||= 10
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.configure(&block)
|
|
55
|
+
self._configure(&block)
|
|
27
56
|
|
|
28
57
|
unless self.app
|
|
29
58
|
raise "Promiscuous.configure: please give a name to your app with \"config.app = 'your_app_name'\""
|
|
@@ -31,6 +60,38 @@ module Promiscuous::Config
|
|
|
31
60
|
|
|
32
61
|
# amqp connection is done in when setting the backend
|
|
33
62
|
Promiscuous::Redis.connect
|
|
63
|
+
|
|
64
|
+
hook_fork
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.hook_fork
|
|
68
|
+
return if @fork_hooked
|
|
69
|
+
|
|
70
|
+
Kernel.module_eval do
|
|
71
|
+
alias_method :fork_without_promiscuous, :fork
|
|
72
|
+
|
|
73
|
+
def fork(&block)
|
|
74
|
+
Promiscuous.disconnect
|
|
75
|
+
pid = if block
|
|
76
|
+
fork_without_promiscuous do
|
|
77
|
+
Promiscuous.connect
|
|
78
|
+
block.call
|
|
79
|
+
end
|
|
80
|
+
else
|
|
81
|
+
fork_without_promiscuous
|
|
82
|
+
end
|
|
83
|
+
Promiscuous.connect
|
|
84
|
+
pid
|
|
85
|
+
rescue Exception => e
|
|
86
|
+
puts e
|
|
87
|
+
puts e.backtrace.join("\n")
|
|
88
|
+
raise e
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
module_function :fork
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
@fork_hooked = true
|
|
34
95
|
end
|
|
35
96
|
|
|
36
97
|
def self.configured?
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Promiscuous::Convenience
|
|
2
|
+
extend self
|
|
3
|
+
|
|
4
|
+
def without_promiscuous
|
|
5
|
+
raise "No block given" unless block_given?
|
|
6
|
+
old_disabled, Promiscuous.disabled = Promiscuous.disabled, true
|
|
7
|
+
yield
|
|
8
|
+
ensure
|
|
9
|
+
Promiscuous.disabled = old_disabled
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class ::Array
|
|
14
|
+
def without_promiscuous
|
|
15
|
+
raise "What is this block?" if block_given?
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'fnv'
|
|
2
|
+
|
|
3
|
+
class Promiscuous::Dependency
|
|
4
|
+
attr_accessor :internal_key, :version
|
|
5
|
+
|
|
6
|
+
def initialize(*args)
|
|
7
|
+
@internal_key = args.join('/')
|
|
8
|
+
|
|
9
|
+
if @internal_key =~ /^[0-9]+$/
|
|
10
|
+
@internal_key = @internal_key.to_i
|
|
11
|
+
@hash = @internal_key
|
|
12
|
+
else
|
|
13
|
+
@hash = FNV.new.fnv1a_32(@internal_key)
|
|
14
|
+
|
|
15
|
+
if Promiscuous::Config.hash_size.to_i > 0
|
|
16
|
+
# We hash dependencies to have a O(1) memory footprint in Redis.
|
|
17
|
+
# The hashing needs to be deterministic across instances in order to
|
|
18
|
+
# function properly.
|
|
19
|
+
@internal_key = @hash % Promiscuous::Config.hash_size.to_i
|
|
20
|
+
@hash = @internal_key
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def key(role)
|
|
26
|
+
Promiscuous::Key.new(role).join(@internal_key)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def redis_node(distributed_redis=nil)
|
|
30
|
+
distributed_redis ||= Promiscuous::Redis.master
|
|
31
|
+
distributed_redis.nodes[@hash % distributed_redis.nodes.size]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def as_json(options={})
|
|
35
|
+
@version ? [@internal_key, @version].join(':') : @internal_key
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.parse(payload)
|
|
39
|
+
case payload
|
|
40
|
+
when /^(.+):([0-9]+)$/ then new($1).tap { |d| d.version = $2.to_i }
|
|
41
|
+
when /^(.+)$/ then new($1)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def to_s
|
|
46
|
+
as_json.to_s
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# We need the eql? method to function properly (we use ==, uniq, ...) in operation
|
|
50
|
+
# XXX The version is not taken in account.
|
|
51
|
+
def eql?(other)
|
|
52
|
+
self.internal_key == other.internal_key
|
|
53
|
+
end
|
|
54
|
+
alias == eql?
|
|
55
|
+
|
|
56
|
+
def hash
|
|
57
|
+
self.internal_key.hash
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Promiscuous::DSL
|
|
2
|
+
def define(&block)
|
|
3
|
+
instance_eval(&block)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def publish(model, options={}, &block)
|
|
7
|
+
Definition.new(:publish, model, options).instance_eval(&block)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def subscribe(model, options={}, &block)
|
|
11
|
+
Definition.new(:subscribe, model, options).instance_eval(&block)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Definition
|
|
15
|
+
def initialize(mode, model, options)
|
|
16
|
+
@mode = mode
|
|
17
|
+
@model = model
|
|
18
|
+
@options = options
|
|
19
|
+
@model_class = @model.to_s.singularize.classify.constantize
|
|
20
|
+
|
|
21
|
+
promiscuous_include = mode == :publish ? Promiscuous::Publisher : Promiscuous::Subscriber
|
|
22
|
+
@model_class.class_eval { include promiscuous_include }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def attributes(*fields)
|
|
26
|
+
options = fields.extract_options!
|
|
27
|
+
@model_class.__send__(@mode, *fields, @options.merge(options))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def track_dependencies_of(field)
|
|
31
|
+
@model_class.track_dependencies_of(field)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
alias attribute attributes
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/promiscuous/error.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
module Promiscuous::Error
|
|
2
2
|
extend Promiscuous::Autoload
|
|
3
|
-
autoload :Connection, :Publisher, :Subscriber, :
|
|
3
|
+
autoload :Base, :Connection, :Publisher, :Subscriber, :Recovery,
|
|
4
|
+
:Dependency, :MissingContext, :AlreadyProcessed,
|
|
5
|
+
:LockUnavailable, :LostLock
|
|
4
6
|
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class Promiscuous::Error::Base < RuntimeError; end
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
class Promiscuous::Error::Connection <
|
|
1
|
+
class Promiscuous::Error::Connection < Promiscuous::Error::Base
|
|
2
2
|
attr_accessor :service, :url
|
|
3
3
|
|
|
4
4
|
def initialize(options={})
|
|
5
5
|
super(nil)
|
|
6
6
|
self.service = options[:service]
|
|
7
|
-
self.url =
|
|
7
|
+
self.url = case service
|
|
8
|
+
when :zookeeper then "zookeeper://#{Promiscuous::Config.zookeeper_hosts}"
|
|
9
|
+
when :redis then Promiscuous::Config.redis_url
|
|
10
|
+
when :amqp then Promiscuous::Config.amqp_url
|
|
11
|
+
end
|
|
8
12
|
end
|
|
9
13
|
|
|
10
14
|
def message
|
|
11
15
|
"Lost connection with #{url}"
|
|
12
16
|
end
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
message
|
|
16
|
-
end
|
|
18
|
+
alias to_s message
|
|
17
19
|
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
class Promiscuous::Error::Dependency < Promiscuous::Error::Base
|
|
2
|
+
attr_accessor :dependency_solutions, :operation, :context
|
|
3
|
+
|
|
4
|
+
def initialize(options={})
|
|
5
|
+
self.operation = options[:operation]
|
|
6
|
+
self.context = Promiscuous::Publisher::Context.current
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# TODO Convert all that with Erb
|
|
10
|
+
|
|
11
|
+
def message
|
|
12
|
+
msg = nil
|
|
13
|
+
case operation.operation
|
|
14
|
+
when :read
|
|
15
|
+
msg = "Promiscuous doesn't have any tracked dependencies to perform this multi read operation.\n" +
|
|
16
|
+
"This is what you can do:\n\n" +
|
|
17
|
+
" 1. Bypass Promiscuous\n\n" +
|
|
18
|
+
" If you don't use the result of this operation in your following writes,\n" +
|
|
19
|
+
" you can wrap your read query in a 'without_promiscuous { }' block.\n" +
|
|
20
|
+
" This is the preferred solution when you are sure that the read doesn't\n" +
|
|
21
|
+
" influence the value of a published attribute.\n\n" +
|
|
22
|
+
" Rule of thumb: Predicates (methods ending with ?) are often suitable for this use case.\n\n"
|
|
23
|
+
cnt = 2
|
|
24
|
+
if operation.operation_ext != :count
|
|
25
|
+
msg += " #{cnt}. Synchronize on individual instances\n\n" +
|
|
26
|
+
" If the collection you are iterating through is small (<10), it becomes intersting\n" +
|
|
27
|
+
" to track instances through their ids instead of the query selector. Example:\n\n" +
|
|
28
|
+
" criteria.without_promiscuous.each do |doc|\n" +
|
|
29
|
+
" next if doc.should_do_something?\n" +
|
|
30
|
+
" doc.reload # tell promiscuous to track the instance\n" +
|
|
31
|
+
" doc.do_something!\n" +
|
|
32
|
+
" end\n\n"
|
|
33
|
+
cnt += 1
|
|
34
|
+
end
|
|
35
|
+
if operation.selector_keys.present?
|
|
36
|
+
msg += " #{cnt}. Track New Dependencies\n\n" +
|
|
37
|
+
" Add #{operation.selector_keys.count == 1 ? "the following line" : "one of the following lines"} " +
|
|
38
|
+
"in the #{operation.instance.class} model:\n\n" +
|
|
39
|
+
" class #{operation.instance.class}\n" +
|
|
40
|
+
operation.selector_keys.map { |field| " track_dependencies_of :#{field}" }.join("\n") + "\n" +
|
|
41
|
+
" end\n\n" +
|
|
42
|
+
(operation.selector_keys.count > 1 ?
|
|
43
|
+
" The more specific field, the better. Promiscuous works better when working with small subsets\n" +
|
|
44
|
+
" For example, tracking something like 'member_id' is a fairly safe choice.\n\n" : "") +
|
|
45
|
+
" Note that dependency tracking slows down your writes. It can be seen as the analogous\n" +
|
|
46
|
+
" of an index on a regular database.\n" +
|
|
47
|
+
" You may find more information about the implications in the Promiscuous wiki (TODO:link).\n\n"
|
|
48
|
+
end
|
|
49
|
+
when :update
|
|
50
|
+
msg = "Promiscuous cannot track dependencies of a multi update operation.\n" +
|
|
51
|
+
"This is what you can do:\n\n" +
|
|
52
|
+
" 1. Instead of doing a multi updates, update each instance separately\n\n" +
|
|
53
|
+
" 2. Do not assign has_many associations directly, but use the << operator instead.\n\n"
|
|
54
|
+
when :destroy
|
|
55
|
+
msg = "Promiscuous cannot track dependencies of a multi delete operation.\n" +
|
|
56
|
+
"This is what you can do:\n\n" +
|
|
57
|
+
" 1. Instead of doing a multi delete, delete each instance separatly.\n\n" +
|
|
58
|
+
" 2. Use destroy_all instead of destroy_all.\n\n" +
|
|
59
|
+
" 3. Declare your has_many relationships with :dependent => :destroy instead of :delete.\n\n"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
msg += "#{"-" * 100}\n\n"
|
|
63
|
+
|
|
64
|
+
msg += "Promiscuous cannot allow the following "
|
|
65
|
+
case operation.operation_ext || operation.operation
|
|
66
|
+
when :count then msg += 'count'
|
|
67
|
+
when :mapreduce then msg += 'mapreduce'
|
|
68
|
+
when :read then msg += 'each loop'
|
|
69
|
+
when :update then msg += 'multi update'
|
|
70
|
+
when :destroy then msg += 'multi destroy'
|
|
71
|
+
end
|
|
72
|
+
msg += " in the '#{context.name}' context:\n\n"
|
|
73
|
+
msg += " #{self.class.explain_operation(self.operation)}"
|
|
74
|
+
msg += "\n\nProTip: Try again with TRACE=2 in the shell or ENV['TRACE']='2' in the console.\n" unless ENV['TRACE']
|
|
75
|
+
msg
|
|
76
|
+
rescue Exception => e
|
|
77
|
+
"#{e.to_s}\n#{e.backtrace.join("\n")}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.explain_operation(operation, limit=100)
|
|
81
|
+
instance = operation.instance
|
|
82
|
+
selector = instance ? get_selector(instance.attributes, limit) : ""
|
|
83
|
+
class_name = instance ? instance.class : "Unknown"
|
|
84
|
+
|
|
85
|
+
if operation.operation == :create
|
|
86
|
+
"#{instance.class}.create(#{selector})"
|
|
87
|
+
else
|
|
88
|
+
case operation.operation_ext || operation.operation
|
|
89
|
+
when :count then verb = 'count'
|
|
90
|
+
when :mapreduce then verb = 'mapreduce(...)'
|
|
91
|
+
when :read then verb = operation.multi? ? 'each { ... }' : 'first'
|
|
92
|
+
when :update then verb = operation.multi? ? 'update_all' : 'update'
|
|
93
|
+
when :destroy then verb = operation.multi? ? 'delete_all' : 'delete'
|
|
94
|
+
end
|
|
95
|
+
msg = "#{class_name}#{selector.present? ? ".where(#{selector})" : ""}.#{verb}"
|
|
96
|
+
if operation.operation == :update && operation.respond_to?(:change) && operation.change
|
|
97
|
+
msg += "(#{get_selector(operation.change, limit)})"
|
|
98
|
+
end
|
|
99
|
+
msg
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def self.get_selector(attributes, limit=100)
|
|
104
|
+
attributes = attributes['$set'] if attributes.count == 1 && attributes['$set']
|
|
105
|
+
selector = attributes.map { |k,v| ":#{k} => #{v}" }.join(", ")
|
|
106
|
+
selector = "#{selector[0...(limit-3)]}..." if selector.size > limit
|
|
107
|
+
selector
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
alias to_s message
|
|
111
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class Promiscuous::Error::LockUnavailable < Promiscuous::Error::Base
|
|
2
|
+
def initialize(lock)
|
|
3
|
+
@lock = lock
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def message
|
|
7
|
+
"The lock is not available on #{@lock}\n" +
|
|
8
|
+
"If an app instance died, the lock will expire in less than a minute."
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
alias to_s message
|
|
12
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class Promiscuous::Error::LostLock < Promiscuous::Error::Base
|
|
2
|
+
def initialize(lock)
|
|
3
|
+
@lock = lock
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def message
|
|
7
|
+
"The following lock was lost during the operation and will be recovered if not already done:\n" +
|
|
8
|
+
" #{@lock}"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
alias to_s message
|
|
12
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class Promiscuous::Error::MissingContext < Promiscuous::Error::Base
|
|
2
|
+
def message
|
|
3
|
+
require 'erb'
|
|
4
|
+
ERB.new(<<-ERB.gsub(/^\s+<%/, '<%').gsub(/^ {6}/, ''), nil, '-').result(binding)
|
|
5
|
+
Promiscuous needs to execute all your read/write queries in a context for publishing.
|
|
6
|
+
This is what you can do:
|
|
7
|
+
1. Wrap your operations in a Promiscuous context yourself (jobs, etc.):
|
|
8
|
+
|
|
9
|
+
Promiscuous::Middleware.with_context 'jobs/name' do
|
|
10
|
+
# Code including all your read and write queries
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
2. Disable Promiscuous completely (only for testing):
|
|
14
|
+
|
|
15
|
+
RSpec.configure do |config|
|
|
16
|
+
config.around do |example|
|
|
17
|
+
without_promiscuous { example.run }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Note that opening a context will reactivate promiscuous temporarily
|
|
22
|
+
even if it was disabled.
|
|
23
|
+
|
|
24
|
+
3. You are in render() in the Rails controller, and you should not write.
|
|
25
|
+
ERB
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
alias to_s message
|
|
29
|
+
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
class Promiscuous::Error::Publisher <
|
|
2
|
-
attr_accessor :inner, :instance, :payload
|
|
1
|
+
class Promiscuous::Error::Publisher < Promiscuous::Error::Base
|
|
2
|
+
attr_accessor :inner, :instance, :payload
|
|
3
3
|
|
|
4
4
|
def initialize(inner, options={})
|
|
5
5
|
super(nil)
|
|
@@ -8,24 +8,14 @@ class Promiscuous::Error::Publisher < RuntimeError
|
|
|
8
8
|
self.inner = inner
|
|
9
9
|
self.instance = options[:instance]
|
|
10
10
|
self.payload = options[:payload]
|
|
11
|
-
self.out_of_sync = options[:out_of_sync]
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
def message
|
|
15
14
|
msg = "#{inner.class}: #{inner.message}"
|
|
16
|
-
if instance
|
|
17
|
-
|
|
18
|
-
msg = "FATAL (out of sync) #{msg}" if out_of_sync
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
if payload
|
|
22
|
-
msg = "#{msg} payload: #{payload}"
|
|
23
|
-
end
|
|
24
|
-
|
|
15
|
+
msg = "#{msg} while publishing #{instance.inspect}" if instance
|
|
16
|
+
msg = "#{msg} payload: #{payload}" if payload
|
|
25
17
|
msg
|
|
26
18
|
end
|
|
27
19
|
|
|
28
|
-
|
|
29
|
-
message
|
|
30
|
-
end
|
|
20
|
+
alias to_s message
|
|
31
21
|
end
|