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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/lib/promiscuous/amqp/bunny.rb +63 -36
  3. data/lib/promiscuous/amqp/fake.rb +3 -1
  4. data/lib/promiscuous/amqp/hot_bunnies.rb +26 -16
  5. data/lib/promiscuous/amqp/null.rb +1 -0
  6. data/lib/promiscuous/amqp.rb +12 -12
  7. data/lib/promiscuous/cli.rb +70 -29
  8. data/lib/promiscuous/config.rb +54 -29
  9. data/lib/promiscuous/convenience.rb +1 -1
  10. data/lib/promiscuous/dependency.rb +25 -6
  11. data/lib/promiscuous/error/connection.rb +11 -9
  12. data/lib/promiscuous/error/dependency.rb +8 -1
  13. data/lib/promiscuous/loader.rb +4 -2
  14. data/lib/promiscuous/publisher/bootstrap/connection.rb +25 -0
  15. data/lib/promiscuous/publisher/bootstrap/data.rb +127 -0
  16. data/lib/promiscuous/publisher/bootstrap/mode.rb +19 -0
  17. data/lib/promiscuous/publisher/bootstrap/status.rb +40 -0
  18. data/lib/promiscuous/publisher/bootstrap/version.rb +46 -0
  19. data/lib/promiscuous/publisher/bootstrap.rb +27 -0
  20. data/lib/promiscuous/publisher/context/base.rb +67 -0
  21. data/lib/promiscuous/{middleware.rb → publisher/context/middleware.rb} +16 -13
  22. data/lib/promiscuous/publisher/context/transaction.rb +36 -0
  23. data/lib/promiscuous/publisher/context.rb +4 -88
  24. data/lib/promiscuous/publisher/mock_generator.rb +9 -9
  25. data/lib/promiscuous/publisher/model/active_record.rb +7 -7
  26. data/lib/promiscuous/publisher/model/base.rb +29 -29
  27. data/lib/promiscuous/publisher/model/ephemeral.rb +5 -3
  28. data/lib/promiscuous/publisher/model/mock.rb +9 -5
  29. data/lib/promiscuous/publisher/model/mongoid.rb +5 -22
  30. data/lib/promiscuous/publisher/operation/active_record.rb +360 -0
  31. data/lib/promiscuous/publisher/operation/atomic.rb +167 -0
  32. data/lib/promiscuous/publisher/operation/base.rb +279 -474
  33. data/lib/promiscuous/publisher/operation/mongoid.rb +153 -145
  34. data/lib/promiscuous/publisher/operation/non_persistent.rb +28 -0
  35. data/lib/promiscuous/publisher/operation/proxy_for_query.rb +42 -0
  36. data/lib/promiscuous/publisher/operation/transaction.rb +85 -0
  37. data/lib/promiscuous/publisher/operation.rb +1 -1
  38. data/lib/promiscuous/publisher/worker.rb +7 -7
  39. data/lib/promiscuous/publisher.rb +1 -1
  40. data/lib/promiscuous/railtie.rb +20 -5
  41. data/lib/promiscuous/redis.rb +104 -56
  42. data/lib/promiscuous/subscriber/message_processor/base.rb +38 -0
  43. data/lib/promiscuous/subscriber/message_processor/bootstrap.rb +17 -0
  44. data/lib/promiscuous/subscriber/message_processor/regular.rb +192 -0
  45. data/lib/promiscuous/subscriber/message_processor.rb +4 -0
  46. data/lib/promiscuous/subscriber/model/base.rb +20 -15
  47. data/lib/promiscuous/subscriber/model/mongoid.rb +4 -4
  48. data/lib/promiscuous/subscriber/model/observer.rb +16 -2
  49. data/lib/promiscuous/subscriber/operation/base.rb +68 -0
  50. data/lib/promiscuous/subscriber/operation/bootstrap.rb +54 -0
  51. data/lib/promiscuous/subscriber/operation/regular.rb +13 -0
  52. data/lib/promiscuous/subscriber/operation.rb +3 -166
  53. data/lib/promiscuous/subscriber/worker/message.rb +61 -35
  54. data/lib/promiscuous/subscriber/worker/message_synchronizer.rb +90 -59
  55. data/lib/promiscuous/subscriber/worker/pump.rb +17 -5
  56. data/lib/promiscuous/subscriber/worker/recorder.rb +4 -1
  57. data/lib/promiscuous/subscriber/worker/runner.rb +49 -9
  58. data/lib/promiscuous/subscriber/worker/stats.rb +2 -2
  59. data/lib/promiscuous/subscriber/worker.rb +6 -0
  60. data/lib/promiscuous/subscriber.rb +1 -1
  61. data/lib/promiscuous/timer.rb +31 -18
  62. data/lib/promiscuous/version.rb +1 -1
  63. data/lib/promiscuous.rb +23 -3
  64. metadata +104 -89
  65. data/lib/promiscuous/subscriber/payload.rb +0 -34
@@ -1,18 +1,20 @@
1
1
  class Promiscuous::Error::Connection < Promiscuous::Error::Base
2
- attr_accessor :service, :url
2
+ attr_accessor :url, :inner
3
3
 
4
- def initialize(options={})
4
+ def initialize(url, options={})
5
5
  super(nil)
6
- self.service = options[:service]
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
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
- "Lost connection with #{url}"
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.to_s}\n#{e.backtrace.join("\n")}"
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
@@ -8,12 +8,14 @@ module Promiscuous::Loader
8
8
  end
9
9
 
10
10
  # A one shot recovery on boot
11
- Promiscuous::Publisher::Worker.new.try_recover
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.select! { |k| k.to_s =~ /__promiscuous__/ }
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
- Promiscuous::Middleware.with_context(full_name) { super }
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
- Promiscuous.context(*args, &block)
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 write we shouldn't.
30
- old_context, Context.current = Context.current, nil
31
- old_disabled, Promiscuous.disabled = Promiscuous.disabled, true
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
- Context.current = old_context
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
- class Promiscuous::Publisher::Context
2
- # XXX Context are not sharable among threads
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
- Thread.current[:promiscuous_context]
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
- publish :to => '<%= publisher.publish_to %>'
25
+ mock :from => '<%= Promiscuous::Config.app %>'
27
26
  <% if defined?(Mongoid::Document) && publisher.include?(Mongoid::Document) -%>
28
- mock :id => :bson
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 %> < <%= publisher.publish_as %>
38
- <% attributes_for(subclass, publisher).each do |attr| -%>
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.publish_to =~ /^__promiscuous__\// }
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?(:publish_to) &&
70
- publisher.superclass.publish_to }
69
+ .reject { |publisher| publisher.superclass.respond_to?(:publish_as) &&
70
+ publisher.superclass.publish_as }
71
71
  end
72
72
  end