lusnoc 0.0.2 → 0.1.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 +4 -4
- data/README.md +97 -0
- data/lib/lusnoc/mutex.rb +8 -3
- data/lib/lusnoc/session.rb +10 -10
- data/lib/lusnoc/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9d7357a1c2a8113aeb8609ee1dae3cae2ee40a75d5b7f89c33dd8dbb6025590
|
4
|
+
data.tar.gz: 186e2664d1f8659482abad06570f25445e17946f81e57ce62ef5a4a831ee4165
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d75571ec0a10776b116463c144a414dfa348848fe7671c4a57719179433d41cb0b833c905ca4d73eb84a28a5c17a80921f14b007613438ec88af9e37d862c4f0
|
7
|
+
data.tar.gz: 550e11d35d35a614d50c7714752d750e24db7f990f401362df781ee5751be34a8f036bedcef39ce55905cb9e2324c2d16b6aa85f8accddb26385307dea50072f
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Lusnoc
|
2
|
+
|
3
|
+
Lusnoc is reliable gem to deal with [Consul](https://www.consul.io). It is designed to be simple and without any background magic.
|
4
|
+
It is inspired by [consul-mutex](https://github.com/discourse/consul-mutex)(which has hard background magic).
|
5
|
+
|
6
|
+
## FAQ
|
7
|
+
|
8
|
+
#### What's Lusnoc for?
|
9
|
+
|
10
|
+
Lusnoc allows you to interact with Consul to provide distributed locks(mutex) to your application.
|
11
|
+
|
12
|
+
#### What's the difference between lusnoc and [consul-mutex](https://github.com/discourse/consul-mutex) or [diplomat](https://github.com/WeAreFarmGeek/diplomat)
|
13
|
+
* consul-mutex starts background thread and ***the block of code that you pass to #synchronize runs on a separate thread, and can be killed without warning if the mutex determines that it no longer holds the lock.***
|
14
|
+
* diplomat provides the basic session/locks functionality but no automated control over it
|
15
|
+
|
16
|
+
#### How luscon deal with sessions/mutexes?
|
17
|
+
* Luscon ensures session creation/destruction upon block execution
|
18
|
+
* Luscon uses only sessions with TTL to protect you system from stale sessions/locks
|
19
|
+
* Luscon enforces you to manualy renew session(through callback or explicit check) but provide background session checker
|
20
|
+
* Luscon tries to carefuly handle timeouts and expiration using Consul [blocking queries](https://www.consul.io/api/features/blocking.html)
|
21
|
+
|
22
|
+
# Usage
|
23
|
+
|
24
|
+
Simply instantiate a new `Luscon::Mutex`, giving it the key you want to use
|
25
|
+
as the "lock":
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'luscon/mutex'
|
29
|
+
mutex = Luscon::Mutex.new('/locks/mx1', ttl: 20)
|
30
|
+
```
|
31
|
+
TTL will be used in session creation on `#synchronize`:
|
32
|
+
```ruby
|
33
|
+
mutex.synchronize(timeout: 10) do |mx|
|
34
|
+
puts "We are exclusively owns resource"
|
35
|
+
end
|
36
|
+
```
|
37
|
+
If mutex cannot be acquired within given timeout Lusnoc::TimeoutError is raised.
|
38
|
+
By default, the "value" of the lock resource will be the hostname of the
|
39
|
+
machine that it's running on (so you know who has the lock). If, for some
|
40
|
+
reason, you'd like to set the value to something else, you can do that, too:
|
41
|
+
```ruby
|
42
|
+
Luscon::Mutex.new('/some/key', value: {time: Time.now}).synchronize do |mx|
|
43
|
+
#...
|
44
|
+
end
|
45
|
+
```
|
46
|
+
Session invalidation/renewval handled through mutex instance:
|
47
|
+
```ruby
|
48
|
+
Luscon::Mutex.new('/some/key').synchronize do |mx|
|
49
|
+
mx.time_to_expiration # seconds to session expiration in consul.
|
50
|
+
mx.ttl # session ttl.
|
51
|
+
mx.need_renew? # true when time_to_expiration less than half of ttl
|
52
|
+
|
53
|
+
mx.need_renew? # false
|
54
|
+
sleep (mx.ttl / 2) + 1
|
55
|
+
mx.need_renew? # true
|
56
|
+
|
57
|
+
mx.on_mutex_lost do |mutex|
|
58
|
+
# this callback will be called from other(guard) thread when mutex is lost(session invalidated)
|
59
|
+
end
|
60
|
+
|
61
|
+
mx.locked? # true while session is not expired or invalidated by admin
|
62
|
+
mx.owned? # true while session is not expired or invalidated by admin and owner is a Thread.current
|
63
|
+
mx.session_id # id of Consul session
|
64
|
+
mx.expired? # is session expired?
|
65
|
+
mx.alive? # is session alive?
|
66
|
+
mx.alive! # ensures session alive or raise Lusnoc::ExpiredError
|
67
|
+
mx.renew # renew session or raise Lusnoc::ExpiredError if session already expired
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
You can use only Session:
|
72
|
+
```ruby
|
73
|
+
Session.new("session_name", ttl: 20) do |session|
|
74
|
+
session.on_session_die do
|
75
|
+
# this callback will be called from other(guard) thread when session invalidated
|
76
|
+
end
|
77
|
+
|
78
|
+
session.expired? # is session expired?
|
79
|
+
session.alive? # is session alive?
|
80
|
+
session.alive! # ensures session alive or raise Lusnoc::ExpiredError
|
81
|
+
session.renew # renew session or raise Lusnoc::ExpiredError if session already expired
|
82
|
+
end
|
83
|
+
```
|
84
|
+
Typical usage scenario:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
Luscon::Mutex.new('/some/key').synchronize do |mx|
|
88
|
+
# do some work
|
89
|
+
mx.renew if mx.need_renew?
|
90
|
+
# do other work
|
91
|
+
mx.renew if mx.need_renew?
|
92
|
+
# ...
|
93
|
+
rescue Lusnoc::ExpiredError => e
|
94
|
+
# Session was invalidated and mutex was lost!
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
data/lib/lusnoc/mutex.rb
CHANGED
@@ -7,7 +7,7 @@ module Lusnoc
|
|
7
7
|
include Helper
|
8
8
|
attr_reader :key, :value, :owner
|
9
9
|
|
10
|
-
def initialize(key, value
|
10
|
+
def initialize(key, value: Socket.gethostname, ttl: 20)
|
11
11
|
@key = key
|
12
12
|
@value = value
|
13
13
|
@ttl = ttl
|
@@ -25,10 +25,14 @@ module Lusnoc
|
|
25
25
|
@session&.id
|
26
26
|
end
|
27
27
|
|
28
|
-
[:time_to_expiration, :need_renew?, :ttl, :expired?, :
|
28
|
+
[:time_to_expiration, :need_renew?, :ttl, :expired?, :alive?, :alive!, :renew].each do |m|
|
29
29
|
define_method(m) { @session&.public_send(m) }
|
30
30
|
end
|
31
31
|
|
32
|
+
def on_mutex_lost(&block)
|
33
|
+
@on_mutex_lost = block
|
34
|
+
end
|
35
|
+
|
32
36
|
def synchronize(timeout: 0, &block)
|
33
37
|
timeouter = Timeouter.new(timeout,
|
34
38
|
exception_class: TimeoutError,
|
@@ -38,6 +42,7 @@ module Lusnoc
|
|
38
42
|
@session = session
|
39
43
|
session.on_session_die do
|
40
44
|
@owner = nil
|
45
|
+
@on_mutex_lost&.call(self)
|
41
46
|
end
|
42
47
|
|
43
48
|
return acquisition_loop! key, session, value, timeouter, &block
|
@@ -70,7 +75,7 @@ module Lusnoc
|
|
70
75
|
|
71
76
|
logger.debug("Start #{key} acquisition loop for session #{session.name}[#{session.id}]")
|
72
77
|
timeouter.loop! do
|
73
|
-
session.
|
78
|
+
session.alive!(TimeoutError)
|
74
79
|
wait_for_key_released(key, timeouter.left)
|
75
80
|
|
76
81
|
return yield(self) if acquire(key, session, value)
|
data/lib/lusnoc/session.rb
CHANGED
@@ -5,7 +5,7 @@ module Lusnoc
|
|
5
5
|
|
6
6
|
include Helper
|
7
7
|
|
8
|
-
attr_reader :id, :name, :ttl, :
|
8
|
+
attr_reader :id, :name, :ttl, :alive, :expired_at
|
9
9
|
|
10
10
|
def initialize(name, ttl: 20)
|
11
11
|
@name = name
|
@@ -18,7 +18,7 @@ module Lusnoc
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def expired?
|
21
|
-
!
|
21
|
+
!alive?
|
22
22
|
end
|
23
23
|
|
24
24
|
def time_to_expiration
|
@@ -29,16 +29,16 @@ module Lusnoc
|
|
29
29
|
time_to_expiration && time_to_expiration < (@ttl / 2.0)
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
33
|
-
@
|
32
|
+
def alive?
|
33
|
+
@alive
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
37
|
-
|
36
|
+
def alive!(exception_class = ExpiredError)
|
37
|
+
alive? || (raise exception_class.new("Session #{id} expired"))
|
38
38
|
end
|
39
39
|
|
40
40
|
def renew
|
41
|
-
|
41
|
+
alive!
|
42
42
|
Lusnoc.http_put(build_url("/v1/session/renew/#{@id}"), nil, timeout: 1)
|
43
43
|
@expired_at = Time.now + ttl
|
44
44
|
logger.info "Session renewed: #{name}[#{@id}]. Next expiration: #{@expired_at}"
|
@@ -57,7 +57,7 @@ module Lusnoc
|
|
57
57
|
session_id = JSON.parse(resp.body)['ID']
|
58
58
|
@expired_at = Time.now + ttl
|
59
59
|
logger.info "Session created: #{name}[#{session_id}]. TTL:#{ttl}s. Next expiration: #{@expired_at}"
|
60
|
-
@
|
60
|
+
@alive = true
|
61
61
|
@th = start_watch_thread(session_id)
|
62
62
|
session_id
|
63
63
|
end
|
@@ -68,7 +68,7 @@ module Lusnoc
|
|
68
68
|
nil,
|
69
69
|
timeout: 1) rescue nil
|
70
70
|
logger.info "Session destroyed: #{name}[#{session_id}]"
|
71
|
-
@
|
71
|
+
@alive = false
|
72
72
|
@expired_at = nil
|
73
73
|
end
|
74
74
|
|
@@ -78,7 +78,7 @@ module Lusnoc
|
|
78
78
|
|
79
79
|
if wait_forever_for_session_gone(session_id)
|
80
80
|
logger.error "Session #{name}[#{session_id}] is gone"
|
81
|
-
@
|
81
|
+
@alive = false
|
82
82
|
@expired_at = nil
|
83
83
|
@session_die_cb&.call(self)
|
84
84
|
else
|
data/lib/lusnoc/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lusnoc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samoilenko Yuri
|
@@ -107,6 +107,7 @@ executables: []
|
|
107
107
|
extensions: []
|
108
108
|
extra_rdoc_files: []
|
109
109
|
files:
|
110
|
+
- README.md
|
110
111
|
- lib/lusnoc.rb
|
111
112
|
- lib/lusnoc/configuration.rb
|
112
113
|
- lib/lusnoc/exceptions.rb
|