lusnoc 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|