suo 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c3c678d658e34c698d95ca20d8d7ddfea5c11c6c
4
- data.tar.gz: cb7bf87ef58efff50356c9310adc1fb10fad6ad1
3
+ metadata.gz: 32aa3b3d87eea1a5332765503443960363b69d96
4
+ data.tar.gz: 57feb579d0f4c168e811ba46d14712fc4cd41159
5
5
  SHA512:
6
- metadata.gz: c33bc02743629ab053afa32ff38e0c837cc1b2deacdbb50342e14de50c83321f7d232df4e2dd2338ee8ec16e54b0aa7cc81b97adc67bd86d75c597c06822be49
7
- data.tar.gz: ef2790ad5ad8ac1567346fd04b340288ec8ea6924a267d8b9ea4daea02808b6c665b267bbaee61c90c544bfeb4de3a6953100d12e36d70365ee505765b73c833
6
+ metadata.gz: 0742cbe948509ebc83ece59c59c9179dbfc900a620a9d4beccf8e784f1dfe1ed5ab037b2686f18b988899634d23ac018c5741c823c840848c33fb5af87bc4e55
7
+ data.tar.gz: 7864bf86c8197c73d8016fb76a77ba9c8f506d2e1d8aef7c59798d2c0d9c368e5fa6ab556233e788c1ff307103ab242d0cb081596d69b40b38e7ff2b819b7c82
@@ -1,3 +1,7 @@
1
+ ## 0.3.0
2
+
3
+ - Dramatically simplify the interface by forcing clients to specify the key & resources at lock initialization instead of every method call.
4
+
1
5
  ## 0.2.3
2
6
 
3
7
  - Clarify documentation further with respect to semaphores.
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Suo [![Build Status](https://travis-ci.org/nickelser/suo.svg?branch=master)](https://travis-ci.org/nickelser/suo) [![Code Climate](https://codeclimate.com/github/nickelser/suo/badges/gpa.svg)](https://codeclimate.com/github/nickelser/suo) [![Test Coverage](https://codeclimate.com/github/nickelser/suo/badges/coverage.svg)](https://codeclimate.com/github/nickelser/suo) [![Gem Version](https://badge.fury.io/rb/suo.svg)](http://badge.fury.io/rb/suo)
2
2
 
3
- :lock: Distributed locks using Memcached or Redis in Ruby.
3
+ :lock: Distributed semaphores using Memcached or Redis in Ruby.
4
4
 
5
- Suo provides a very performant distributed lock solution using Compare-And-Set (`CAS`) commands in Memcached, and `WATCH/MULTI` in Redis. It allows locking both single exclusion (a mutex - sharing one resource), and multiple resources.
5
+ Suo provides a very performant distributed lock solution using Compare-And-Set (`CAS`) commands in Memcached, and `WATCH/MULTI` in Redis. It allows locking both single exclusion (like a mutex - sharing one resource), as well as multiple resources.
6
6
 
7
7
  ## Installation
8
8
 
@@ -18,38 +18,40 @@ gem 'suo'
18
18
 
19
19
  ```ruby
20
20
  # Memcached
21
- suo = Suo::Client::Memcached.new(connection: "127.0.0.1:11211")
21
+ suo = Suo::Client::Memcached.new("foo_resource", connection: "127.0.0.1:11211")
22
22
 
23
23
  # Redis
24
- suo = Suo::Client::Redis.new(connection: {host: "10.0.1.1"})
24
+ suo = Suo::Client::Redis.new("baz_resource", connection: {host: "10.0.1.1"})
25
25
 
26
26
  # Pre-existing client
27
- suo = Suo::Client::Memcached.new(client: some_dalli_client)
27
+ suo = Suo::Client::Memcached.new("bar_resource", client: some_dalli_client)
28
28
 
29
- suo.lock("some_key") do
29
+ suo.lock do
30
30
  # critical code here
31
31
  @puppies.pet!
32
32
  end
33
33
 
34
- # The second argument to lock is the number of arguments (defaulting to one - a mutex)
35
- Thread.new { suo.lock("other_key", 2) { puts "One"; sleep 2 } }
36
- Thread.new { suo.lock("other_key", 2) { puts "Two"; sleep 2 } }
37
- Thread.new { suo.lock("other_key", 2) { puts "Three" } }
34
+ # The resources argument is the number of resources the semaphore will allow to lock (defaulting to one - a mutex)
35
+ suo = Suo::Client::Memcached.new("bar_resource", client: some_dalli_client, resources: 2)
36
+
37
+ Thread.new { suo.lock{ puts "One"; sleep 2 } }
38
+ Thread.new { suo.lock { puts "Two"; sleep 2 } }
39
+ Thread.new { suo.lock { puts "Three" } }
38
40
 
39
41
  # will print "One" "Two", but not "Three", as there are only 2 resources
40
42
 
41
43
  # custom acquisition timeouts (time to acquire)
42
- suo = Suo::Client::Memcached.new(client: some_dalli_client, acquisition_timeout: 1) # in seconds
44
+ suo = Suo::Client::Memcached.new("protected_key", client: some_dalli_client, acquisition_timeout: 1) # in seconds
43
45
 
44
46
  # manually locking/unlocking
45
47
  # the return value from lock without a block is a unique token valid only for the current lock
46
48
  # which must be unlocked manually
47
- lock = suo.lock("a_key")
49
+ token = suo
48
50
  foo.baz!
49
- suo.unlock("a_key", lock)
51
+ suo.unlock(token)
50
52
 
51
53
  # custom stale lock expiration (cleaning of dead locks)
52
- suo = Suo::Client::Redis.new(client: some_redis_client, stale_lock_expiration: 60*5)
54
+ suo = Suo::Client::Redis.new("other_key", client: some_redis_client, stale_lock_expiration: 60*5)
53
55
  ```
54
56
 
55
57
  ### Stale locks
@@ -59,21 +61,17 @@ suo = Suo::Client::Redis.new(client: some_redis_client, stale_lock_expiration: 6
59
61
  To re-acquire a lock in the middle of a block, you can use the refresh method on client.
60
62
 
61
63
  ```ruby
62
- suo = Suo::Client::Redis.new
64
+ suo = Suo::Client::Redis.new("foo")
63
65
 
64
66
  # lock is the same token as seen in the manual example, above
65
- suo.lock("foo") do |lock|
67
+ suo.lock do |token|
66
68
  5.times do
67
69
  baz.bar!
68
- suo.refresh("foo", lock)
70
+ suo.refresh(token)
69
71
  end
70
72
  end
71
73
  ```
72
74
 
73
- ## Semaphore
74
-
75
- With multiple resources, Suo acts like a semaphore, but is not strictly a semaphore according to the traditional definition, as the lock acquires ownership.
76
-
77
75
  ## TODO
78
76
  - more race condition tests
79
77
 
@@ -4,96 +4,101 @@ module Suo
4
4
  DEFAULT_OPTIONS = {
5
5
  acquisition_timeout: 0.1,
6
6
  acquisition_delay: 0.01,
7
- stale_lock_expiration: 3600
7
+ stale_lock_expiration: 3600,
8
+ resources: 1
8
9
  }.freeze
9
10
 
10
- attr_accessor :client
11
+ attr_accessor :client, :key, :resources, :options
11
12
 
12
13
  include MonitorMixin
13
14
 
14
- def initialize(options = {})
15
+ def initialize(key, options = {})
15
16
  fail "Client required" unless options[:client]
16
17
  @options = DEFAULT_OPTIONS.merge(options)
17
18
  @retry_count = (@options[:acquisition_timeout] / @options[:acquisition_delay].to_f).ceil
18
19
  @client = @options[:client]
19
- super()
20
+ @resources = @options[:resources].to_i
21
+ @key = key
22
+ super() # initialize Monitor mixin for thread safety
20
23
  end
21
24
 
22
- def lock(key, resources = 1)
23
- token = acquire_lock(key, resources)
25
+ def lock
26
+ token = acquire_lock
24
27
 
25
28
  if block_given? && token
26
29
  begin
27
- yield(token)
30
+ yield
28
31
  ensure
29
- unlock(key, token)
32
+ unlock(token)
30
33
  end
31
34
  else
32
35
  token
33
36
  end
34
37
  end
35
38
 
36
- def locked?(key, resources = 1)
37
- locks(key).size >= resources
39
+ def locked?
40
+ locks.size >= resources
38
41
  end
39
42
 
40
- def locks(key)
41
- val, _ = get(key)
43
+ def locks
44
+ val, _ = get
42
45
  cleared_locks = deserialize_and_clear_locks(val)
43
46
 
44
47
  cleared_locks
45
48
  end
46
49
 
47
- def refresh(key, acquisition_token)
48
- retry_with_timeout(key) do
49
- val, cas = get(key)
50
+ def refresh(token)
51
+ retry_with_timeout do
52
+ val, cas = get
50
53
 
51
54
  if val.nil?
52
- initial_set(key)
55
+ initial_set
53
56
  next
54
57
  end
55
58
 
56
59
  cleared_locks = deserialize_and_clear_locks(val)
57
60
 
58
- refresh_lock(cleared_locks, acquisition_token)
61
+ refresh_lock(cleared_locks, token)
59
62
 
60
- break if set(key, serialize_locks(cleared_locks), cas)
63
+ break if set(serialize_locks(cleared_locks), cas)
61
64
  end
62
65
  end
63
66
 
64
- def unlock(key, acquisition_token)
65
- return unless acquisition_token
67
+ def unlock(token)
68
+ return unless token
66
69
 
67
- retry_with_timeout(key) do
68
- val, cas = get(key)
70
+ retry_with_timeout do
71
+ val, cas = get
69
72
 
70
73
  break if val.nil?
71
74
 
72
75
  cleared_locks = deserialize_and_clear_locks(val)
73
76
 
74
- acquisition_lock = remove_lock(cleared_locks, acquisition_token)
77
+ acquisition_lock = remove_lock(cleared_locks, token)
75
78
 
76
79
  break unless acquisition_lock
77
- break if set(key, serialize_locks(cleared_locks), cas)
80
+ break if set(serialize_locks(cleared_locks), cas)
78
81
  end
79
82
  rescue LockClientError => _ # rubocop:disable Lint/HandleExceptions
80
83
  # ignore - assume success due to optimistic locking
81
84
  end
82
85
 
83
- def clear(key) # rubocop:disable Lint/UnusedMethodArgument
86
+ def clear
84
87
  fail NotImplementedError
85
88
  end
86
89
 
87
90
  private
88
91
 
89
- def acquire_lock(key, resources = 1)
92
+ attr_accessor :retry_count
93
+
94
+ def acquire_lock
90
95
  token = SecureRandom.base64(16)
91
96
 
92
- retry_with_timeout(key) do
93
- val, cas = get(key)
97
+ retry_with_timeout do
98
+ val, cas = get
94
99
 
95
100
  if val.nil?
96
- initial_set(key)
101
+ initial_set
97
102
  next
98
103
  end
99
104
 
@@ -104,41 +109,41 @@ module Suo
104
109
 
105
110
  newval = serialize_locks(cleared_locks)
106
111
 
107
- return token if set(key, newval, cas)
112
+ return token if set(newval, cas)
108
113
  end
109
114
  end
110
115
 
111
116
  nil
112
117
  end
113
118
 
114
- def get(key) # rubocop:disable Lint/UnusedMethodArgument
119
+ def get
115
120
  fail NotImplementedError
116
121
  end
117
122
 
118
- def set(key, newval, cas) # rubocop:disable Lint/UnusedMethodArgument
123
+ def set(newval, cas) # rubocop:disable Lint/UnusedMethodArgument
119
124
  fail NotImplementedError
120
125
  end
121
126
 
122
- def initial_set(key, val = "") # rubocop:disable Lint/UnusedMethodArgument
127
+ def initial_set(val = "") # rubocop:disable Lint/UnusedMethodArgument
123
128
  fail NotImplementedError
124
129
  end
125
130
 
126
- def synchronize(key) # rubocop:disable Lint/UnusedMethodArgument
131
+ def synchronize
127
132
  mon_synchronize { yield }
128
133
  end
129
134
 
130
- def retry_with_timeout(key)
135
+ def retry_with_timeout
131
136
  start = Time.now.to_f
132
137
 
133
- @retry_count.times do
138
+ retry_count.times do
134
139
  elapsed = Time.now.to_f - start
135
- break if elapsed >= @options[:acquisition_timeout]
140
+ break if elapsed >= options[:acquisition_timeout]
136
141
 
137
- synchronize(key) do
142
+ synchronize do
138
143
  yield
139
144
  end
140
145
 
141
- sleep(rand(@options[:acquisition_delay] * 1000).to_f / 1000)
146
+ sleep(rand(options[:acquisition_delay] * 1000).to_f / 1000)
142
147
  end
143
148
  rescue => _
144
149
  raise LockClientError
@@ -163,7 +168,7 @@ module Suo
163
168
  end
164
169
 
165
170
  def clear_expired_locks(locks)
166
- expired = Time.now - @options[:stale_lock_expiration]
171
+ expired = Time.now - options[:stale_lock_expiration]
167
172
  locks.reject { |time, _| time < expired }
168
173
  end
169
174
 
@@ -1,27 +1,27 @@
1
1
  module Suo
2
2
  module Client
3
3
  class Memcached < Base
4
- def initialize(options = {})
4
+ def initialize(key, options = {})
5
5
  options[:client] ||= Dalli::Client.new(options[:connection] || ENV["MEMCACHE_SERVERS"] || "127.0.0.1:11211")
6
6
  super
7
7
  end
8
8
 
9
- def clear(key)
10
- @client.delete(key)
9
+ def clear
10
+ @client.delete(@key)
11
11
  end
12
12
 
13
13
  private
14
14
 
15
- def get(key)
16
- @client.get_cas(key)
15
+ def get
16
+ @client.get_cas(@key)
17
17
  end
18
18
 
19
- def set(key, newval, cas)
20
- @client.set_cas(key, newval, cas)
19
+ def set(newval, cas)
20
+ @client.set_cas(@key, newval, cas)
21
21
  end
22
22
 
23
- def initial_set(key, val = "")
24
- @client.set(key, val)
23
+ def initial_set(val = "")
24
+ @client.set(@key, val)
25
25
  end
26
26
  end
27
27
  end
@@ -1,39 +1,39 @@
1
1
  module Suo
2
2
  module Client
3
3
  class Redis < Base
4
- def initialize(options = {})
4
+ def initialize(key, options = {})
5
5
  options[:client] ||= ::Redis.new(options[:connection] || {})
6
6
  super
7
7
  end
8
8
 
9
- def clear(key)
10
- @client.del(key)
9
+ def clear
10
+ @client.del(@key)
11
11
  end
12
12
 
13
13
  private
14
14
 
15
- def get(key)
16
- [@client.get(key), nil]
15
+ def get
16
+ [@client.get(@key), nil]
17
17
  end
18
18
 
19
- def set(key, newval, _)
19
+ def set(newval, _)
20
20
  ret = @client.multi do |multi|
21
- multi.set(key, newval)
21
+ multi.set(@key, newval)
22
22
  end
23
23
 
24
24
  ret && ret[0] == "OK"
25
25
  end
26
26
 
27
- def synchronize(key)
28
- @client.watch(key) do
27
+ def synchronize
28
+ @client.watch(@key) do
29
29
  yield
30
30
  end
31
31
  ensure
32
32
  @client.unwatch
33
33
  end
34
34
 
35
- def initial_set(key, val = "")
36
- @client.set(key, val)
35
+ def initial_set(val = "")
36
+ @client.set(@key, val)
37
37
  end
38
38
  end
39
39
  end
@@ -1,3 +1,3 @@
1
1
  module Suo
2
- VERSION = "0.2.3"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Nick Elser"]
10
10
  spec.email = ["nick.elser@gmail.com"]
11
11
 
12
- spec.summary = %q(Distributed locks using Memcached or Redis.)
13
- spec.description = %q(Distributed locks using Memcached or Redis.)
12
+ spec.summary = %q(Distributed locks (mutexes & semaphores) using Memcached or Redis.)
13
+ spec.description = %q(Distributed locks (mutexes & semaphores) using Memcached or Redis.)
14
14
  spec.homepage = "https://github.com/nickelser/suo"
15
15
  spec.license = "MIT"
16
16
 
@@ -4,123 +4,121 @@ TEST_KEY = "suo_test_key".freeze
4
4
 
5
5
  module ClientTests
6
6
  def client(options = {})
7
- @client.class.new(options.merge(client: @client.client))
7
+ @client.class.new(options[:key] || TEST_KEY, options.merge(client: @client.client))
8
8
  end
9
9
 
10
10
  def test_throws_failed_error_on_bad_client
11
11
  assert_raises(Suo::LockClientError) do
12
- client = @client.class.new(client: {})
13
- client.lock(TEST_KEY, 1)
12
+ client = @client.class.new(TEST_KEY, client: {})
13
+ client.lock
14
14
  end
15
15
  end
16
16
 
17
17
  def test_single_resource_locking
18
- lock1 = @client.lock(TEST_KEY, 1)
18
+ lock1 = @client.lock
19
19
  refute_nil lock1
20
20
 
21
- locked = @client.locked?(TEST_KEY, 1)
21
+ locked = @client.locked?
22
22
  assert_equal true, locked
23
23
 
24
- lock2 = @client.lock(TEST_KEY, 1)
24
+ lock2 = @client.lock
25
25
  assert_nil lock2
26
26
 
27
- @client.unlock(TEST_KEY, lock1)
27
+ @client.unlock(lock1)
28
28
 
29
- locked = @client.locked?(TEST_KEY, 1)
29
+ locked = @client.locked?
30
30
 
31
31
  assert_equal false, locked
32
32
  end
33
33
 
34
34
  def test_empty_lock_on_invalid_data
35
- @client.send(:initial_set, TEST_KEY, "bad value")
36
- locked = @client.locked?(TEST_KEY)
37
- assert_equal false, locked
35
+ @client.send(:initial_set, "bad value")
36
+ assert_equal false, @client.locked?
38
37
  end
39
38
 
40
39
  def test_clear
41
- lock1 = @client.lock(TEST_KEY, 1)
40
+ lock1 = @client.lock
42
41
  refute_nil lock1
43
42
 
44
- @client.clear(TEST_KEY)
45
-
46
- locked = @client.locked?(TEST_KEY, 1)
43
+ @client.clear
47
44
 
48
- assert_equal false, locked
45
+ assert_equal false, @client.locked?
49
46
  end
50
47
 
51
48
  def test_multiple_resource_locking
52
- lock1 = @client.lock(TEST_KEY, 2)
49
+ @client = client(resources: 2)
50
+
51
+ lock1 = @client.lock
53
52
  refute_nil lock1
54
53
 
55
- locked = @client.locked?(TEST_KEY, 2)
56
- assert_equal false, locked
54
+ assert_equal false, @client.locked?
57
55
 
58
- lock2 = @client.lock(TEST_KEY, 2)
56
+ lock2 = @client.lock
59
57
  refute_nil lock2
60
58
 
61
- locked = @client.locked?(TEST_KEY, 2)
62
- assert_equal true, locked
59
+ assert_equal true, @client.locked?
63
60
 
64
- @client.unlock(TEST_KEY, lock1)
61
+ @client.unlock(lock1)
65
62
 
66
- locked = @client.locked?(TEST_KEY, 1)
67
- assert_equal true, locked
63
+ assert_equal false, @client.locked?
68
64
 
69
- @client.unlock(TEST_KEY, lock2)
65
+ assert_equal 1, @client.locks.size
70
66
 
71
- locked = @client.locked?(TEST_KEY, 1)
72
- assert_equal false, locked
67
+ @client.unlock(lock2)
68
+
69
+ assert_equal false, @client.locked?
70
+ assert_equal 0, @client.locks.size
73
71
  end
74
72
 
75
73
  def test_block_single_resource_locking
76
74
  locked = false
77
75
 
78
- @client.lock(TEST_KEY, 1) { locked = true }
76
+ @client.lock { locked = true }
79
77
 
80
78
  assert_equal true, locked
81
79
  end
82
80
 
83
81
  def test_block_unlocks_on_exception
84
82
  assert_raises(RuntimeError) do
85
- @client.lock(TEST_KEY, 1) { fail "Test" }
83
+ @client.lock{ fail "Test" }
86
84
  end
87
85
 
88
- locked = @client.locked?(TEST_KEY, 1)
89
- assert_equal false, locked
86
+ assert_equal false, @client.locked?
90
87
  end
91
88
 
92
89
  def test_readme_example
93
90
  output = Queue.new
91
+ @client = client(resources: 2)
94
92
  threads = []
95
93
 
96
- threads << Thread.new { @client.lock(TEST_KEY, 2) { output << "One"; sleep 0.5 } }
97
- threads << Thread.new { @client.lock(TEST_KEY, 2) { output << "Two"; sleep 0.5 } }
94
+ threads << Thread.new { @client.lock { output << "One"; sleep 0.5 } }
95
+ threads << Thread.new { @client.lock { output << "Two"; sleep 0.5 } }
98
96
  sleep 0.1
99
- threads << Thread.new { @client.lock(TEST_KEY, 2) { output << "Three" } }
97
+ threads << Thread.new { @client.lock { output << "Three" } }
100
98
 
101
99
  threads.each(&:join)
102
100
 
103
101
  ret = []
104
102
 
105
- ret << output.pop
106
- ret << output.pop
103
+ ret << (output.size > 0 ? output.pop : nil)
104
+ ret << (output.size > 0 ? output.pop : nil)
107
105
 
108
106
  ret.sort!
109
107
 
110
108
  assert_equal 0, output.size
111
109
  assert_equal %w(One Two), ret
112
- assert_equal false, @client.locked?(TEST_KEY)
110
+ assert_equal false, @client.locked?
113
111
  end
114
112
 
115
113
  def test_block_multiple_resource_locking
116
114
  success_counter = Queue.new
117
115
  failure_counter = Queue.new
118
116
 
119
- client = client(acquisition_timeout: 0.9)
117
+ @client = client(acquisition_timeout: 0.9, resources: 50)
120
118
 
121
119
  100.times.map do |i|
122
120
  Thread.new do
123
- success = client.lock(TEST_KEY, 50) do
121
+ success = @client.lock do
124
122
  sleep(3)
125
123
  success_counter << i
126
124
  end
@@ -131,18 +129,18 @@ module ClientTests
131
129
 
132
130
  assert_equal 50, success_counter.size
133
131
  assert_equal 50, failure_counter.size
134
- assert_equal false, client.locked?(TEST_KEY)
132
+ assert_equal false, @client.locked?
135
133
  end
136
134
 
137
135
  def test_block_multiple_resource_locking_longer_timeout
138
136
  success_counter = Queue.new
139
137
  failure_counter = Queue.new
140
138
 
141
- client = client(acquisition_timeout: 3)
139
+ @client = client(acquisition_timeout: 3, resources: 50)
142
140
 
143
141
  100.times.map do |i|
144
142
  Thread.new do
145
- success = client.lock(TEST_KEY, 50) do
143
+ success = @client.lock do
146
144
  sleep(0.5)
147
145
  success_counter << i
148
146
  end
@@ -153,19 +151,19 @@ module ClientTests
153
151
 
154
152
  assert_equal 100, success_counter.size
155
153
  assert_equal 0, failure_counter.size
156
- assert_equal false, client.locked?(TEST_KEY)
154
+ assert_equal false, @client.locked?
157
155
  end
158
156
 
159
157
  def test_unstale_lock_acquisition
160
158
  success_counter = Queue.new
161
159
  failure_counter = Queue.new
162
160
 
163
- client = client(stale_lock_expiration: 0.5)
161
+ @client = client(stale_lock_expiration: 0.5)
164
162
 
165
- t1 = Thread.new { client.lock(TEST_KEY) { sleep 0.6; success_counter << 1 } }
163
+ t1 = Thread.new { @client.lock { sleep 0.6; success_counter << 1 } }
166
164
  sleep 0.3
167
165
  t2 = Thread.new do
168
- locked = client.lock(TEST_KEY) { success_counter << 1 }
166
+ locked = @client.lock { success_counter << 1 }
169
167
  failure_counter << 1 unless locked
170
168
  end
171
169
 
@@ -173,19 +171,19 @@ module ClientTests
173
171
 
174
172
  assert_equal 1, success_counter.size
175
173
  assert_equal 1, failure_counter.size
176
- assert_equal false, client.locked?(TEST_KEY)
174
+ assert_equal false, @client.locked?
177
175
  end
178
176
 
179
177
  def test_stale_lock_acquisition
180
178
  success_counter = Queue.new
181
179
  failure_counter = Queue.new
182
180
 
183
- client = client(stale_lock_expiration: 0.5)
181
+ @client = client(stale_lock_expiration: 0.5)
184
182
 
185
- t1 = Thread.new { client.lock(TEST_KEY) { sleep 0.6; success_counter << 1 } }
183
+ t1 = Thread.new { @client.lock { sleep 0.6; success_counter << 1 } }
186
184
  sleep 0.55
187
185
  t2 = Thread.new do
188
- locked = client.lock(TEST_KEY) { success_counter << 1 }
186
+ locked = @client.lock { success_counter << 1 }
189
187
  failure_counter << 1 unless locked
190
188
  end
191
189
 
@@ -193,59 +191,59 @@ module ClientTests
193
191
 
194
192
  assert_equal 2, success_counter.size
195
193
  assert_equal 0, failure_counter.size
196
- assert_equal false, client.locked?(TEST_KEY)
194
+ assert_equal false, @client.locked?
197
195
  end
198
196
 
199
197
  def test_refresh
200
- client = client(stale_lock_expiration: 0.5)
198
+ @client = client(stale_lock_expiration: 0.5)
201
199
 
202
- lock1 = client.lock(TEST_KEY)
200
+ lock1 = @client.lock
203
201
 
204
- assert_equal true, client.locked?(TEST_KEY)
202
+ assert_equal true, @client.locked?
205
203
 
206
- client.refresh(TEST_KEY, lock1)
204
+ @client.refresh(lock1)
207
205
 
208
- assert_equal true, client.locked?(TEST_KEY)
206
+ assert_equal true, @client.locked?
209
207
 
210
208
  sleep 0.55
211
209
 
212
- assert_equal false, client.locked?(TEST_KEY)
210
+ assert_equal false, @client.locked?
213
211
 
214
- lock2 = client.lock(TEST_KEY)
212
+ lock2 = @client.lock
215
213
 
216
- client.refresh(TEST_KEY, lock1)
214
+ @client.refresh(lock1)
217
215
 
218
- assert_equal true, client.locked?(TEST_KEY)
216
+ assert_equal true, @client.locked?
219
217
 
220
- client.unlock(TEST_KEY, lock1)
218
+ @client.unlock(lock1)
221
219
 
222
220
  # edge case with refresh lock in the middle
223
- assert_equal true, client.locked?(TEST_KEY)
221
+ assert_equal true, @client.locked?
224
222
 
225
- client.clear(TEST_KEY)
223
+ @client.clear
226
224
 
227
- assert_equal false, client.locked?(TEST_KEY)
225
+ assert_equal false, @client.locked?
228
226
 
229
- client.refresh(TEST_KEY, lock2)
227
+ @client.refresh(lock2)
230
228
 
231
- assert_equal true, client.locked?(TEST_KEY)
229
+ assert_equal true, @client.locked?
232
230
 
233
- client.unlock(TEST_KEY, lock2)
231
+ @client.unlock(lock2)
234
232
 
235
233
  # now finally unlocked
236
- assert_equal false, client.locked?(TEST_KEY)
234
+ assert_equal false, @client.locked?
237
235
  end
238
236
 
239
237
  def test_block_refresh
240
238
  success_counter = Queue.new
241
239
  failure_counter = Queue.new
242
240
 
243
- client = client(stale_lock_expiration: 0.5)
241
+ @client = client(stale_lock_expiration: 0.5)
244
242
 
245
243
  t1 = Thread.new do
246
- client.lock(TEST_KEY) do |token|
244
+ @client.lock do |token|
247
245
  sleep 0.6
248
- client.refresh(TEST_KEY, token)
246
+ @client.refresh(token)
249
247
  sleep 1
250
248
  success_counter << 1
251
249
  end
@@ -253,7 +251,7 @@ module ClientTests
253
251
 
254
252
  t2 = Thread.new do
255
253
  sleep 0.8
256
- locked = client.lock(TEST_KEY) { success_counter << 1 }
254
+ locked = @client.lock { success_counter << 1 }
257
255
  failure_counter << 1 unless locked
258
256
  end
259
257
 
@@ -261,19 +259,19 @@ module ClientTests
261
259
 
262
260
  assert_equal 1, success_counter.size
263
261
  assert_equal 1, failure_counter.size
264
- assert_equal false, client.locked?(TEST_KEY)
262
+ assert_equal false, @client.locked?
265
263
  end
266
264
 
267
265
  def test_refresh_multi
268
266
  success_counter = Queue.new
269
267
  failure_counter = Queue.new
270
268
 
271
- client = client(stale_lock_expiration: 0.5)
269
+ @client = client(stale_lock_expiration: 0.5, resources: 2)
272
270
 
273
271
  t1 = Thread.new do
274
- client.lock(TEST_KEY, 2) do |token|
272
+ @client.lock do |token|
275
273
  sleep 0.4
276
- client.refresh(TEST_KEY, token)
274
+ @client.refresh(token)
277
275
  success_counter << 1
278
276
  sleep 0.5
279
277
  end
@@ -281,7 +279,7 @@ module ClientTests
281
279
 
282
280
  t2 = Thread.new do
283
281
  sleep 0.55
284
- locked = client.lock(TEST_KEY, 2) do
282
+ locked = @client.lock do
285
283
  success_counter << 1
286
284
  sleep 0.5
287
285
  end
@@ -291,7 +289,7 @@ module ClientTests
291
289
 
292
290
  t3 = Thread.new do
293
291
  sleep 0.75
294
- locked = client.lock(TEST_KEY, 2) { success_counter << 1 }
292
+ locked = @client.lock { success_counter << 1 }
295
293
  failure_counter << 1 unless locked
296
294
  end
297
295
 
@@ -299,7 +297,7 @@ module ClientTests
299
297
 
300
298
  assert_equal 2, success_counter.size
301
299
  assert_equal 1, failure_counter.size
302
- assert_equal false, client.locked?(TEST_KEY)
300
+ assert_equal false, @client.locked?
303
301
  end
304
302
 
305
303
  def test_increment_reused_client
@@ -307,14 +305,14 @@ module ClientTests
307
305
 
308
306
  threads = 2.times.map do
309
307
  Thread.new do
310
- @client.lock(TEST_KEY) { i += 1 }
308
+ @client.lock { i += 1 }
311
309
  end
312
310
  end
313
311
 
314
312
  threads.each(&:join)
315
313
 
316
314
  assert_equal 2, i
317
- assert_equal false, client.locked?(TEST_KEY)
315
+ assert_equal false, @client.locked?
318
316
  end
319
317
 
320
318
  def test_increment_new_client
@@ -322,37 +320,38 @@ module ClientTests
322
320
 
323
321
  threads = 2.times.map do
324
322
  Thread.new do
325
- client.lock(TEST_KEY) { i += 1 }
323
+ # note this is the method that generates a *new* client
324
+ client.lock { i += 1 }
326
325
  end
327
326
  end
328
327
 
329
328
  threads.each(&:join)
330
329
 
331
330
  assert_equal 2, i
332
- assert_equal false, client.locked?(TEST_KEY)
331
+ assert_equal false, @client.locked?
333
332
  end
334
333
  end
335
334
 
336
335
  class TestBaseClient < Minitest::Test
337
336
  def setup
338
- @client = Suo::Client::Base.new(client: {})
337
+ @client = Suo::Client::Base.new(TEST_KEY, client: {})
339
338
  end
340
339
 
341
340
  def test_not_implemented
342
341
  assert_raises(NotImplementedError) do
343
- @client.send(:get, TEST_KEY)
342
+ @client.send(:get)
344
343
  end
345
344
 
346
345
  assert_raises(NotImplementedError) do
347
- @client.send(:set, TEST_KEY, "", "")
346
+ @client.send(:set, "", "")
348
347
  end
349
348
 
350
349
  assert_raises(NotImplementedError) do
351
- @client.send(:initial_set, TEST_KEY)
350
+ @client.send(:initial_set)
352
351
  end
353
352
 
354
353
  assert_raises(NotImplementedError) do
355
- @client.send(:clear, TEST_KEY)
354
+ @client.send(:clear)
356
355
  end
357
356
  end
358
357
  end
@@ -362,7 +361,7 @@ class TestMemcachedClient < Minitest::Test
362
361
 
363
362
  def setup
364
363
  @dalli = Dalli::Client.new("127.0.0.1:11211")
365
- @client = Suo::Client::Memcached.new
364
+ @client = Suo::Client::Memcached.new(TEST_KEY)
366
365
  teardown
367
366
  end
368
367
 
@@ -376,7 +375,7 @@ class TestRedisClient < Minitest::Test
376
375
 
377
376
  def setup
378
377
  @redis = Redis.new
379
- @client = Suo::Client::Redis.new
378
+ @client = Suo::Client::Redis.new(TEST_KEY)
380
379
  teardown
381
380
  end
382
381
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: suo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Elser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-14 00:00:00.000000000 Z
11
+ date: 2015-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dalli
@@ -122,7 +122,7 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: 0.4.7
125
- description: Distributed locks using Memcached or Redis.
125
+ description: Distributed locks (mutexes & semaphores) using Memcached or Redis.
126
126
  email:
127
127
  - nick.elser@gmail.com
128
128
  executables:
@@ -173,7 +173,7 @@ rubyforge_project:
173
173
  rubygems_version: 2.4.5
174
174
  signing_key:
175
175
  specification_version: 4
176
- summary: Distributed locks using Memcached or Redis.
176
+ summary: Distributed locks (mutexes & semaphores) using Memcached or Redis.
177
177
  test_files:
178
178
  - test/client_test.rb
179
179
  - test/test_helper.rb