suo 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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