promiscuous 1.0.0.beta3 → 1.0.0.beta4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: af8d0d4847186472653f6b0932e343c3835d278c
4
- data.tar.gz: 216be106e5ed0602d87e5e92ae0837b7b8eef200
3
+ metadata.gz: a2be6a0c5f3e918b401df4b70498f2120e2493e1
4
+ data.tar.gz: 14a8ff258fd6cb14d59d7ad8efb874a37bb5424e
5
5
  SHA512:
6
- metadata.gz: 6edea211a0ceef16f23aece877377efae9612eacf45afb4471eeb4c065bef9aa9167403c76ad96bdee673fa37d549c1ac499ee88427e0c1d86623b83f567807b
7
- data.tar.gz: 4a97397c2f5cc298f05b73a53f6db994a8b6dc80e5a8d2d4b28205295b0b100a3ebd53e35734ad9a3450059dead3c2eb96c2e2a3c5d7da874e825a635f352105
6
+ metadata.gz: d98fd116d3dd2e123a703cca6feb25be23a75ad139df43f0c0e7a1a2091ceb84d782c46ae455f06385f4136d69533ea01243389ab42c5719a1970b68b59e28a4
7
+ data.tar.gz: de9801711b20a656c81fc53fcf74c67015e8bd9e9b25c8af1c20a3d0045e02a5008c0b2f3d427ec84e5bc369aa15434ebbe5d11d45a46156043cf2cb3ccdeec8
data/lib/promiscuous.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'active_support/dependencies/autoload'
1
2
  require 'active_support/core_ext'
2
3
  require 'active_model/callbacks'
3
4
  require 'multi_json'
@@ -42,6 +43,7 @@ module Promiscuous
42
43
  def connect
43
44
  AMQP.connect
44
45
  Redis.connect
46
+
45
47
  @should_be_connected = true
46
48
  end
47
49
 
@@ -1,9 +1,9 @@
1
1
  module Promiscuous::Config
2
2
  mattr_accessor :app, :backend, :amqp_url,
3
3
  :publisher_amqp_url, :subscriber_amqp_url, :publisher_exchange,
4
- :subscriber_exchanges, :queue_name, :queue_options, :redis_url,
5
- :redis_urls, :redis_stats_url, :stats_interval,
6
- :socket_timeout, :heartbeat, :hash_size,
4
+ :subscriber_exchanges, :queue_name, :queue_options,
5
+ :redis_url, :redis_stats_url, :stats_interval,
6
+ :socket_timeout, :heartbeat,
7
7
  :prefetch, :recovery_timeout, :recovery_interval, :logger, :subscriber_threads,
8
8
  :version_field, :error_notifier, :transport_collection,
9
9
  :on_stats, :max_retries, :generation, :destroy_timeout, :destroy_check_interval
@@ -44,13 +44,11 @@ module Promiscuous::Config
44
44
  self.queue_name ||= "#{self.app}.promiscuous"
45
45
  self.queue_options ||= {:durable => true, :arguments => {'x-ha-policy' => 'all'}}
46
46
  self.redis_url ||= 'redis://localhost/'
47
- self.redis_urls ||= [self.redis_url]
48
47
  # TODO self.redis_slave_url ||= nil
49
- self.redis_stats_url ||= self.redis_urls.first
48
+ self.redis_stats_url ||= self.redis_url
50
49
  self.stats_interval ||= 0
51
50
  self.socket_timeout ||= 10
52
51
  self.heartbeat ||= 60
53
- self.hash_size ||= 2**20 # one million keys ~ 200Mb.
54
52
  self.prefetch ||= 1000
55
53
  self.recovery_timeout ||= 10.seconds
56
54
  self.recovery_interval ||= 5.seconds
@@ -9,7 +9,7 @@ module Promiscuous::Publisher::Model::ActiveRecord
9
9
  raise <<-help
10
10
  #{self} must include a _v column. Create the following migration:
11
11
  change_table :#{self.table_name} do |t|
12
- t.integer :_v, :limit => 8
12
+ t.integer :_v, :limit => 8, :default => 1
13
13
  end
14
14
  help
15
15
  end
@@ -30,7 +30,8 @@ module Promiscuous::Publisher::Model::Mock
30
30
  # json dump.
31
31
  batch = op.create_transport_batch([op])
32
32
 
33
- Promiscuous::Subscriber::Message.new(batch.payload).process
33
+ message = Promiscuous::Subscriber::Message.new(batch.payload)
34
+ Promiscuous::Subscriber::UnitOfWork.process(message)
34
35
  end
35
36
 
36
37
  module ClassMethods
@@ -1,17 +1,7 @@
1
1
  class Promiscuous::Publisher::Transport::Persistence::ActiveRecord
2
- def initialize
3
- unless connection.table_exists?(table)
4
- raise <<-help
5
- Promiscuous requires the following migration to be run:
6
- create_table :_promiscuous do |t|
7
- t.string :batch
8
- t.timestamp :at, :default => :now
9
- end
10
- help
11
- end
12
- end
13
-
14
2
  def save(batch)
3
+ check_schema
4
+
15
5
  q = "INSERT INTO #{table} (\"batch\") " +
16
6
  "VALUES ('#{batch.dump}') RETURNING id"
17
7
 
@@ -21,6 +11,8 @@ class Promiscuous::Publisher::Transport::Persistence::ActiveRecord
21
11
  end
22
12
 
23
13
  def expired
14
+ check_schema
15
+
24
16
  q = "SELECT id, p.batch FROM #{table} p " +
25
17
  "WHERE at < current_timestamp - #{Promiscuous::Config.recovery_timeout} * INTERVAL '1 second'"
26
18
 
@@ -28,6 +20,8 @@ class Promiscuous::Publisher::Transport::Persistence::ActiveRecord
28
20
  end
29
21
 
30
22
  def delete(batch)
23
+ check_schema
24
+
31
25
  q = "DELETE FROM #{table} WHERE id = #{batch.id}"
32
26
 
33
27
  connection.exec_query(q, 'Promiscuous Recovery Delete')
@@ -35,6 +29,22 @@ class Promiscuous::Publisher::Transport::Persistence::ActiveRecord
35
29
 
36
30
  private
37
31
 
32
+ def check_schema
33
+ return if @schema_checked
34
+
35
+ unless connection.table_exists?(table)
36
+ raise <<-help
37
+ Promiscuous requires the following migration to be run:
38
+ create_table :_promiscuous do |t|
39
+ t.string :batch
40
+ t.timestamp :at, :default => :now
41
+ end
42
+ help
43
+ end
44
+
45
+ @schema_checked = true
46
+ end
47
+
38
48
  def connection
39
49
  ActiveRecord::Base.connection
40
50
  end
@@ -1,228 +1,27 @@
1
1
  require 'redis'
2
- require 'redis/distributed'
3
- require 'digest/sha1'
4
2
 
5
- module Promiscuous::Redis
6
- def self.connect
7
- disconnect
8
- @master = new_connection
9
- end
10
-
11
- def self.master
12
- ensure_connected unless @master
13
- @master
14
- end
15
-
16
- def self.node_for(key)
17
- distributed_redis ||= Promiscuous::Redis.master
18
- distributed_redis.nodes[FNV.new.fnv1a_32(key) % @master.nodes.size]
19
- end
20
-
21
- def self.slave
22
- ensure_connected unless @slave
23
- @slave
24
- end
25
-
26
- def self.ensure_slave
27
- # ensure_slave is called on the first publisher declaration.
28
- if Promiscuous::Config.redis_slave_url
29
- self.slave = new_connection(Promiscuous::Config.redis_slave_url)
30
- end
31
- end
32
-
33
- def self.disconnect
34
- @master.quit if @master
35
- @slave.quit if @slave
36
- @master = nil
37
- @slave = nil
38
- end
39
-
40
- def self.new_connection(url=nil)
41
- url ||= Promiscuous::Config.redis_urls
42
- redis = ::Redis::Distributed.new(url, :tcp_keepalive => 60)
3
+ class Promiscuous::Redis
4
+ class_attribute :connection
43
5
 
44
- redis.info.each do |info|
45
- version = info['redis_version']
46
- unless Gem::Version.new(version) >= Gem::Version.new('2.6.0')
47
- raise "You are using Redis #{version}. Please use Redis 2.6.0 or later."
48
- end
49
- end
50
-
51
- redis
52
- end
53
-
54
- def self.new_blocking_connection
55
- # This removes the read/select loop in redis, it's weird and unecessary when
56
- # blocking on the connection.
57
- new_connection.tap do |redis|
58
- redis.nodes.each do |node|
59
- node.client.connection.instance_eval do
60
- @sock.instance_eval do
61
- def _read_from_socket(nbytes)
62
- readpartial(nbytes)
63
- end
64
- end
65
- end
66
- end
67
- end
6
+ def self.connect
7
+ self.connection = Redis.new(:url => Promiscuous::Config.redis_url)
68
8
  end
69
9
 
70
10
  def self.ensure_connected
71
11
  Promiscuous.ensure_connected
72
12
 
73
- @master.nodes.each do |node|
74
- begin
75
- node.ping
76
- rescue Exception => e
77
- raise lost_connection_exception(node, :inner => e)
78
- end
13
+ begin
14
+ connection.ping
15
+ rescue Exception => e
16
+ raise lost_connection_exception(node, :inner => e)
79
17
  end
80
18
  end
81
19
 
82
- def self.lost_connection_exception(node, options={})
83
- Promiscuous::Error::Connection.new("redis://#{node.location}", options)
84
- end
85
-
86
- class Script
87
- def initialize(script)
88
- @script = script
89
- @sha = Digest::SHA1.hexdigest(@script)
90
- end
91
-
92
- def eval(redis, options={})
93
- redis.evalsha(@sha, options)
94
- rescue ::Redis::CommandError => e
95
- if e.message =~ /^NOSCRIPT/
96
- redis.script(:load, @script)
97
- retry
98
- end
99
- raise e
100
- end
101
-
102
- def to_s
103
- @script
104
- end
20
+ def self.disconnect
21
+ self.connection.quit
105
22
  end
106
23
 
107
- class Mutex
108
- attr_reader :token
109
-
110
- def initialize(key, options={})
111
- # TODO remove old code with orig_key
112
- @orig_key = key.to_s
113
- @key = "#{key}:lock"
114
- @timeout = options[:timeout].to_i
115
- @sleep = options[:sleep].to_f
116
- @expire = options[:expire].to_i
117
- @lock_set = options[:lock_set]
118
- @node = options[:node]
119
- raise "Which node?" unless @node
120
- end
121
-
122
- def key
123
- @orig_key
124
- end
125
-
126
- def node
127
- @node
128
- end
129
-
130
- def lock
131
- result = false
132
- start_at = Time.now
133
- while Time.now - start_at < @timeout
134
- break if result = try_lock
135
- sleep @sleep
136
- end
137
- result
138
- end
139
-
140
- def try_lock
141
- raise "You are trying to lock an already locked mutex" if @token
142
-
143
- now = Time.now.to_i
144
-
145
- # This script loading is not thread safe (touching a class variable), but
146
- # that's okay, because the race is harmless.
147
- @@lock_script ||= Promiscuous::Redis::Script.new <<-SCRIPT
148
- local key = KEYS[1]
149
- local token_key = KEYS[2]
150
- local lock_set = KEYS[3]
151
- local now = tonumber(ARGV[1])
152
- local expires_at = tonumber(ARGV[2])
153
- local orig_key = ARGV[3]
154
-
155
- local prev_expires_at = tonumber(redis.call('hget', key, 'expires_at'))
156
- if prev_expires_at and prev_expires_at > now then
157
- return {false, nil}
158
- end
159
-
160
- local next_token = redis.call('incr', 'promiscuous:next_token')
161
-
162
- redis.call('hmset', key, 'expires_at', expires_at, 'token', next_token)
163
-
164
- if lock_set then
165
- redis.call('zadd', lock_set, now, orig_key)
166
- end
167
-
168
- if prev_expires_at then
169
- return {'recovered', next_token}
170
- else
171
- return {true, next_token}
172
- end
173
- SCRIPT
174
- result, @token = @@lock_script.eval(@node, :keys => [@key, 'promiscuous:next_token', @lock_set].compact,
175
- :argv => [now, now + @expire, @orig_key])
176
- result == 'recovered' ? :recovered : !!result
177
- end
178
-
179
- def extend
180
- now = Time.now.to_i
181
- @@extend_script ||= Promiscuous::Redis::Script.new <<-SCRIPT
182
- local key = KEYS[1]
183
- local expires_at = tonumber(ARGV[1])
184
- local token = ARGV[2]
185
-
186
- if redis.call('hget', key, 'token') == token then
187
- redis.call('hset', key, 'expires_at', expires_at)
188
- return true
189
- else
190
- return false
191
- end
192
- SCRIPT
193
- !!@@extend_script.eval(@node, :keys => [@key].compact, :argv => [now + @expire, @token])
194
- end
195
-
196
- def unlock
197
- raise "You are trying to unlock a non locked mutex" unless @token
198
-
199
- # Since it's possible that the operations in the critical section took a long time,
200
- # we can't just simply release the lock. The unlock method checks if the unique @token
201
- # remains the same, and do not release if the lock token was overwritten.
202
- @@unlock_script ||= Script.new <<-LUA
203
- local key = KEYS[1]
204
- local lock_set = KEYS[2]
205
- local token = ARGV[1]
206
- local orig_key = ARGV[2]
207
-
208
- if redis.call('hget', key, 'token') == token then
209
- redis.call('del', key)
210
- if lock_set then
211
- redis.call('zrem', lock_set, orig_key)
212
- end
213
- return true
214
- else
215
- return false
216
- end
217
- LUA
218
- result = @@unlock_script.eval(@node, :keys => [@key, @lock_set].compact, :argv => [@token, @orig_key])
219
- @token = nil
220
- !!result
221
- end
222
-
223
- def still_locked?
224
- raise "You never locked that mutex" unless @token
225
- @node.hget(@key, 'token').to_i == @token
226
- end
24
+ def self.lost_connection_exception(node, options={})
25
+ Promiscuous::Error::Connection.new("redis://#{connection.location}", options)
227
26
  end
228
27
  end
@@ -1,4 +1,5 @@
1
1
  require 'fnv'
2
+ require 'robust-redis-lock'
2
3
 
3
4
  class Promiscuous::Subscriber::UnitOfWork
4
5
  attr_accessor :message
@@ -58,17 +59,16 @@ class Promiscuous::Subscriber::UnitOfWork
58
59
  return yield unless operation.version
59
60
 
60
61
  key = "#{app}:#{operation.key}"
61
- lock_options = LOCK_OPTIONS.merge(:node => Promiscuous::Redis.node_for(key))
62
- mutex = Promiscuous::Redis::Mutex.new(key, lock_options)
62
+ lock = Redis::Lock.new(key, LOCK_OPTIONS.merge(:redis => Promiscuous::Redis.connection))
63
63
 
64
- unless mutex.lock
65
- raise Promiscuous::Error::LockUnavailable.new(mutex.key)
64
+ unless lock.lock
65
+ raise Promiscuous::Error::LockUnavailable.new(lock.key)
66
66
  end
67
67
 
68
68
  begin
69
69
  yield
70
70
  ensure
71
- unless mutex.unlock
71
+ unless lock.unlock
72
72
  # TODO Be safe in case we have a duplicate message and lost the lock on it
73
73
  raise "The subscriber lost the lock during its operation. It means that someone else\n"+
74
74
  "received a duplicate message, and we got screwed.\n"
@@ -84,7 +84,6 @@ class Promiscuous::Subscriber::UnitOfWork
84
84
  end
85
85
 
86
86
  def on_message
87
- # XXX This needs to be done for each operation
88
87
  with_transaction do
89
88
  self.operations.each { |op| execute_operation(op) if op.model }
90
89
  end
@@ -71,7 +71,8 @@ class Promiscuous::Subscriber::Worker::EventualDestroyer
71
71
  private
72
72
 
73
73
  def self.redis
74
- Promiscuous::Redis.master.nodes.first
74
+ Promiscuous.ensure_connected
75
+ Promiscuous::Redis.connection
75
76
  end
76
77
 
77
78
  def self.key
@@ -1,3 +1,3 @@
1
1
  module Promiscuous
2
- VERSION = '1.0.0.beta3'
2
+ VERSION = '1.0.0.beta4'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: promiscuous
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta3
4
+ version: 1.0.0.beta4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicolas Viennot
@@ -9,104 +9,104 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-05-23 00:00:00.000000000 Z
12
+ date: 2014-06-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - ">="
18
+ - - '>='
19
19
  - !ruby/object:Gem::Version
20
20
  version: '3'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - ">="
25
+ - - '>='
26
26
  - !ruby/object:Gem::Version
27
27
  version: '3'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: activemodel
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - ">="
32
+ - - '>='
33
33
  - !ruby/object:Gem::Version
34
34
  version: '3'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - ">="
39
+ - - '>='
40
40
  - !ruby/object:Gem::Version
41
41
  version: '3'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: bunny
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - ">="
46
+ - - '>='
47
47
  - !ruby/object:Gem::Version
48
48
  version: 0.10.7
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - ">="
53
+ - - '>='
54
54
  - !ruby/object:Gem::Version
55
55
  version: 0.10.7
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: amq-protocol
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
- - - ">="
60
+ - - '>='
61
61
  - !ruby/object:Gem::Version
62
62
  version: 1.8.0
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
- - - ">="
67
+ - - '>='
68
68
  - !ruby/object:Gem::Version
69
69
  version: 1.8.0
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: ruby-progressbar
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
- - - "~>"
74
+ - - ~>
75
75
  - !ruby/object:Gem::Version
76
76
  version: 1.2.0
77
77
  type: :runtime
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
- - - "~>"
81
+ - - ~>
82
82
  - !ruby/object:Gem::Version
83
83
  version: 1.2.0
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: redis
86
86
  requirement: !ruby/object:Gem::Requirement
87
87
  requirements:
88
- - - "~>"
88
+ - - ~>
89
89
  - !ruby/object:Gem::Version
90
90
  version: 3.0.2
91
91
  type: :runtime
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
- - - "~>"
95
+ - - ~>
96
96
  - !ruby/object:Gem::Version
97
97
  version: 3.0.2
98
98
  - !ruby/object:Gem::Dependency
99
99
  name: algorithms
100
100
  requirement: !ruby/object:Gem::Requirement
101
101
  requirements:
102
- - - "~>"
102
+ - - ~>
103
103
  - !ruby/object:Gem::Version
104
104
  version: 0.6.1
105
105
  type: :runtime
106
106
  prerelease: false
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
- - - "~>"
109
+ - - ~>
110
110
  - !ruby/object:Gem::Version
111
111
  version: 0.6.1
112
112
  - !ruby/object:Gem::Dependency
@@ -127,16 +127,30 @@ dependencies:
127
127
  name: multi_json
128
128
  requirement: !ruby/object:Gem::Requirement
129
129
  requirements:
130
- - - "~>"
130
+ - - ~>
131
131
  - !ruby/object:Gem::Version
132
132
  version: 1.8.0
133
133
  type: :runtime
134
134
  prerelease: false
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
- - - "~>"
137
+ - - ~>
138
138
  - !ruby/object:Gem::Version
139
139
  version: 1.8.0
140
+ - !ruby/object:Gem::Dependency
141
+ name: robust-redis-lock
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ~>
145
+ - !ruby/object:Gem::Version
146
+ version: 0.2.2
147
+ type: :runtime
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ~>
152
+ - !ruby/object:Gem::Version
153
+ version: 0.2.2
140
154
  description: Replicate models across applications
141
155
  email:
142
156
  - nicolas@viennot.biz
@@ -225,18 +239,19 @@ require_paths:
225
239
  - lib
226
240
  required_ruby_version: !ruby/object:Gem::Requirement
227
241
  requirements:
228
- - - ">="
242
+ - - '>='
229
243
  - !ruby/object:Gem::Version
230
244
  version: '0'
231
245
  required_rubygems_version: !ruby/object:Gem::Requirement
232
246
  requirements:
233
- - - ">"
247
+ - - '>'
234
248
  - !ruby/object:Gem::Version
235
249
  version: 1.3.1
236
250
  requirements: []
237
251
  rubyforge_project:
238
- rubygems_version: 2.1.11
252
+ rubygems_version: 2.0.14
239
253
  signing_key:
240
254
  specification_version: 4
241
255
  summary: Replicate models across applications
242
256
  test_files: []
257
+ has_rdoc: false