lusnoc 0.1.0 → 0.1.2.16548

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
  SHA256:
3
- metadata.gz: e9d7357a1c2a8113aeb8609ee1dae3cae2ee40a75d5b7f89c33dd8dbb6025590
4
- data.tar.gz: 186e2664d1f8659482abad06570f25445e17946f81e57ce62ef5a4a831ee4165
3
+ metadata.gz: 7bbe9d1f94b450c09aaeb7fe5283bef89ee796a4b57712af40603ba8eb5a3f0c
4
+ data.tar.gz: 2ceec91d3f94a92631698ad391b5c7848e57d4033dbd1766ab37d0cd75a1f322
5
5
  SHA512:
6
- metadata.gz: d75571ec0a10776b116463c144a414dfa348848fe7671c4a57719179433d41cb0b833c905ca4d73eb84a28a5c17a80921f14b007613438ec88af9e37d862c4f0
7
- data.tar.gz: 550e11d35d35a614d50c7714752d750e24db7f990f401362df781ee5751be34a8f036bedcef39ce55905cb9e2324c2d16b6aa85f8accddb26385307dea50072f
6
+ metadata.gz: 11b1834bca1940eea5d5099cff6100587bc5fbcf75ea012866629d23daa6f43a4c2cceeb76c26b58689adad5cffaad33f62d104fe3d75a33645cb4e39607b8e8
7
+ data.tar.gz: 5b356bfa4e314d7da5e4cba97e24abbe5b5ccfaa2e98c977d3c8f6d583c3f5ad3d12a616cf96f6ad120a3382d48dac2b53d197667fbcc9fbcee3886ad11362de
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014-2019 Рнд Софт
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md CHANGED
@@ -1,6 +1,15 @@
1
1
  # Lusnoc
2
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.
3
+ [![Gem Version](https://badge.fury.io/rb/lusnoc.svg)](https://rubygems.org/gems/lusnoc)
4
+ [![Gem](https://img.shields.io/gem/dt/lusnoc.svg)](https://rubygems.org/gems/lusnoc/versions)
5
+
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/ed48b89a9793a074cd23/test_coverage)](https://codeclimate.com/github/RnD-Soft/lusnoc/test_coverage)
7
+ [![Maintainability](https://api.codeclimate.com/v1/badges/ed48b89a9793a074cd23/maintainability)](https://codeclimate.com/github/RnD-Soft/lusnoc/maintainability)
8
+ [![Quality](https://lysander.x.rnds.pro/api/v1/badges/lusnoc_quality.svg)](https://lysander.x.rnds.pro/api/v1/badges/lusnoc_quality.html)
9
+ [![Outdated](https://lysander.x.rnds.pro/api/v1/badges/lusnoc_outdated.svg)](https://lysander.x.rnds.pro/api/v1/badges/lusnoc_outdated.html)
10
+ [![Vulnerabilities](https://lysander.x.rnds.pro/api/v1/badges/lusnoc_vulnerable.svg)](https://lysander.x.rnds.pro/api/v1/badges/lusnoc_vulnerable.html)
11
+
12
+ Lusnoc is reliable gem to deal with [Consul](https://www.consul.io). It is designed to be simple and work without dark background magic.
4
13
  It is inspired by [consul-mutex](https://github.com/discourse/consul-mutex)(which has hard background magic).
5
14
 
6
15
  ## FAQ
@@ -13,20 +22,20 @@ Lusnoc allows you to interact with Consul to provide distributed locks(mutex) to
13
22
  * 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
23
  * diplomat provides the basic session/locks functionality but no automated control over it
15
24
 
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)
25
+ #### How Lusnoc deal with sessions/mutexes?
26
+ * Lusnoc ensures session creation/destruction upon block execution
27
+ * Lusnoc uses only sessions with TTL to protect you system from stale sessions/locks
28
+ * Lusnoc enforces you to manualy renew session(through callback or explicit check) but provide background session checker
29
+ * Lusnoc tries to carefuly handle timeouts and expiration using Consul [blocking queries](https://www.consul.io/api/features/blocking.html)
21
30
 
22
31
  # Usage
23
32
 
24
- Simply instantiate a new `Luscon::Mutex`, giving it the key you want to use
33
+ Simply instantiate a new `Lusnoc::Mutex`, giving it the key you want to use
25
34
  as the "lock":
26
35
 
27
36
  ```ruby
28
- require 'luscon/mutex'
29
- mutex = Luscon::Mutex.new('/locks/mx1', ttl: 20)
37
+ require 'lusnoc/mutex'
38
+ mutex = Lusnoc::Mutex.new('/locks/mx1', ttl: 20)
30
39
  ```
31
40
  TTL will be used in session creation on `#synchronize`:
32
41
  ```ruby
@@ -39,13 +48,13 @@ By default, the "value" of the lock resource will be the hostname of the
39
48
  machine that it's running on (so you know who has the lock). If, for some
40
49
  reason, you'd like to set the value to something else, you can do that, too:
41
50
  ```ruby
42
- Luscon::Mutex.new('/some/key', value: {time: Time.now}).synchronize do |mx|
51
+ Lusnoc::Mutex.new('/some/key', value: {time: Time.now}).synchronize do |mx|
43
52
  #...
44
53
  end
45
54
  ```
46
- Session invalidation/renewval handled through mutex instance:
55
+ Session invalidation/renewal handled through mutex instance:
47
56
  ```ruby
48
- Luscon::Mutex.new('/some/key').synchronize do |mx|
57
+ Lusnoc::Mutex.new('/some/key').synchronize do |mx|
49
58
  mx.time_to_expiration # seconds to session expiration in consul.
50
59
  mx.ttl # session ttl.
51
60
  mx.need_renew? # true when time_to_expiration less than half of ttl
@@ -84,7 +93,7 @@ You can use only Session:
84
93
  Typical usage scenario:
85
94
 
86
95
  ```ruby
87
- Luscon::Mutex.new('/some/key').synchronize do |mx|
96
+ Lusnoc::Mutex.new('/some/key').synchronize do |mx|
88
97
  # do some work
89
98
  mx.renew if mx.need_renew?
90
99
  # do other work
@@ -95,3 +104,15 @@ Typical usage scenario:
95
104
  end
96
105
  ```
97
106
 
107
+ # Installation
108
+
109
+ It's a gem:
110
+ ```bash
111
+ gem install lusnoc
112
+ ```
113
+ There's also the wonders of [the Gemfile](http://bundler.io):
114
+ ```ruby
115
+ gem 'lusnoc'
116
+ ```
117
+
118
+
data/lib/lusnoc.rb CHANGED
@@ -4,6 +4,7 @@ require 'json'
4
4
  require 'lusnoc/configuration'
5
5
  require 'lusnoc/session'
6
6
  require 'lusnoc/mutex'
7
+ require 'lusnoc/guard'
7
8
 
8
9
 
9
10
  module Lusnoc
@@ -0,0 +1,67 @@
1
+ require 'timeouter'
2
+ require 'lusnoc/helper'
3
+
4
+ module Lusnoc
5
+ class Guard
6
+
7
+ include Helper
8
+
9
+ def initialize(base_url)
10
+ @base_url = base_url
11
+ yield(self) if block_given?
12
+ end
13
+
14
+ def condition(&block)
15
+ @condition = block
16
+ self
17
+ end
18
+
19
+ def then(&block)
20
+ @callback = block
21
+ self
22
+ end
23
+
24
+ def run
25
+ th = Thread.new do
26
+ logger.info "Guard[#{@base_url.inspect}] thread started"
27
+ watch_forever(@base_url)
28
+ fire!
29
+ rescue StandardError => e
30
+ logger.error "Guard[#{@base_url.inspect}] error: #{e.inspect}"
31
+ fire!
32
+ ensure
33
+ logger.info "Guard[#{@base_url.inspect}] finihsed"
34
+ end
35
+
36
+ yield
37
+ ensure
38
+ th.kill rescue nil
39
+ end
40
+
41
+ private
42
+
43
+ def fire!
44
+ @callback&.tap do |cb|
45
+ @callback = nil
46
+ logger.info "Guard[#{@base_url.inspect}] fired"
47
+ cb.call
48
+ end
49
+ end
50
+
51
+ def watch_forever(base_url)
52
+ last_x_consul_index = 1
53
+
54
+ Kernel.loop do
55
+ resp = Lusnoc.http_get("#{base_url}?index=#{last_x_consul_index}&wait=10s", timeout: 15)
56
+ logger.debug "Guard[#{@base_url.inspect}] response: #{resp.body}"
57
+ return unless @condition.call(resp.body)
58
+
59
+ index = [Integer(resp['x-consul-index']), 1].max
60
+ last_x_consul_index = (index < last_x_consul_index ? 1 : index)
61
+ sleep 1
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+
data/lib/lusnoc/mutex.rb CHANGED
@@ -34,9 +34,7 @@ module Lusnoc
34
34
  end
35
35
 
36
36
  def synchronize(timeout: 0, &block)
37
- timeouter = Timeouter.new(timeout,
38
- exception_class: TimeoutError,
39
- exception_message: 'mutex acquisition expired')
37
+ t = Timeouter::Timer.new(timeout, eclass: TimeoutError, message: 'mutex acquisition expired')
40
38
 
41
39
  Session.new("mutex_session/#{key}", ttl: @ttl) do |session|
42
40
  @session = session
@@ -45,7 +43,7 @@ module Lusnoc
45
43
  @on_mutex_lost&.call(self)
46
44
  end
47
45
 
48
- return acquisition_loop! key, session, value, timeouter, &block
46
+ return acquisition_loop! key, session, value, t, &block
49
47
  ensure
50
48
  release(key, session.id, timeout: 2) rescue nil
51
49
  logger.info("Lock #{key} released for session #{session.name}[#{session.id}]")
@@ -70,13 +68,13 @@ module Lusnoc
70
68
  Lusnoc.http_put(build_url("/v1/kv/#{key}?release=#{session.id}"), timeout: 1)
71
69
  end
72
70
 
73
- def acquisition_loop!(key, session, value, timeouter)
71
+ def acquisition_loop!(key, session, value, t)
74
72
  return yield(self) if acquire(key, session, value)
75
73
 
76
74
  logger.debug("Start #{key} acquisition loop for session #{session.name}[#{session.id}]")
77
- timeouter.loop! do
75
+ t.loop! do
78
76
  session.alive!(TimeoutError)
79
- wait_for_key_released(key, timeouter.left)
77
+ wait_for_key_released(key, t.left)
80
78
 
81
79
  return yield(self) if acquire(key, session, value)
82
80
 
@@ -88,9 +86,9 @@ module Lusnoc
88
86
  def wait_for_key_released(key, timeout = nil)
89
87
  logger.debug "Waiting for key #{key} to be fre of any session"
90
88
  Lusnoc::Watcher.new(build_url("/v1/kv/#{key}"),
91
- timeout: timeout,
92
- exception_class: TimeoutError,
93
- exception_message: 'mutex acquisition expired').run do |body|
89
+ timeout: timeout,
90
+ eclass: TimeoutError,
91
+ emessage: 'mutex acquisition expired').run do |body|
94
92
  result = JSON.parse(body.empty? ? '[{}]' : body)
95
93
  return true if result.first['Session'].nil?
96
94
  end
@@ -1,4 +1,5 @@
1
1
  require 'lusnoc/watcher'
2
+ require 'lusnoc/guard'
2
3
 
3
4
  module Lusnoc
4
5
  class Session
@@ -12,9 +13,12 @@ module Lusnoc
12
13
  @ttl = ttl
13
14
 
14
15
  @id = create_session(name, ttl)
15
- yield(self)
16
+
17
+ prepare_guard(@id).run do
18
+ yield(self)
19
+ end
16
20
  ensure
17
- destroy_session(@id)
21
+ destroy_session(@id) if @id
18
22
  end
19
23
 
20
24
  def expired?
@@ -34,14 +38,14 @@ module Lusnoc
34
38
  end
35
39
 
36
40
  def alive!(exception_class = ExpiredError)
37
- alive? || (raise exception_class.new("Session #{id} expired"))
41
+ alive? || (raise exception_class.new("Session[#{@name}:#{@id}] expired"))
38
42
  end
39
43
 
40
44
  def renew
41
45
  alive!
42
46
  Lusnoc.http_put(build_url("/v1/session/renew/#{@id}"), nil, timeout: 1)
43
47
  @expired_at = Time.now + ttl
44
- logger.info "Session renewed: #{name}[#{@id}]. Next expiration: #{@expired_at}"
48
+ logger.info "Session[#{@name}:#{@id}] renewed. Next expiration: #{@expired_at}"
45
49
  end
46
50
 
47
51
  def on_session_die(&block)
@@ -56,50 +60,33 @@ module Lusnoc
56
60
  { timeout: 1 })
57
61
  session_id = JSON.parse(resp.body)['ID']
58
62
  @expired_at = Time.now + ttl
59
- logger.info "Session created: #{name}[#{session_id}]. TTL:#{ttl}s. Next expiration: #{@expired_at}"
63
+ logger.info "Session[#{name}:#{session_id}] created. TTL:#{ttl}s. Next expiration: #{@expired_at}"
60
64
  @alive = true
61
- @th = start_watch_thread(session_id)
62
65
  session_id
63
66
  end
64
67
 
65
- def destroy_session(session_id)
66
- @th.kill rescue nil
67
- Lusnoc.http_put(build_url("/v1/session/destroy/#{session_id}"),
68
- nil,
69
- timeout: 1) rescue nil
70
- logger.info "Session destroyed: #{name}[#{session_id}]"
71
- @alive = false
72
- @expired_at = nil
73
- end
74
-
75
- def start_watch_thread(session_id)
76
- Thread.new do
77
- logger.debug "Guard thread for Session #{name}[#{session_id}] started"
68
+ def prepare_guard(session_id)
69
+ Lusnoc::Guard.new(build_url("/v1/session/info/#{session_id}")) do |guard|
70
+ guard.condition do |body|
71
+ !JSON.parse(body).empty? rescue false
72
+ end
78
73
 
79
- if wait_forever_for_session_gone(session_id)
80
- logger.error "Session #{name}[#{session_id}] is gone"
74
+ guard.then do
81
75
  @alive = false
82
76
  @expired_at = nil
77
+ logger.info "Session[#{@name}:#{session_id}] is gone"
83
78
  @session_die_cb&.call(self)
84
- else
85
- logger.unknown 'Something is wrong with thread logic'
86
79
  end
87
- ensure
88
- logger.debug "Guard thread for Session #{name}[#{session_id}] finihsed"
89
80
  end
90
81
  end
91
82
 
92
- def wait_forever_for_session_gone(session_id)
93
- Lusnoc::Watcher.new(build_url("/v1/session/info/#{session_id}"), timeout: 0).run do |body|
94
- true if JSON.parse(body).empty?
95
- end
96
- rescue StandardError => e
97
- logger.error "Session #{name}[#{session_id}] watch exception: #{e.inspect}"
98
- logger.error e.backtrace.join("\n")
99
- true
83
+ def destroy_session(session_id)
84
+ @alive = false
85
+ @expired_at = nil
86
+ Lusnoc.http_put(build_url("/v1/session/destroy/#{session_id}"), nil, timeout: 1) rescue nil
87
+ logger.info "Session[#{@name}:#{session_id}] destroyed"
100
88
  end
101
89
 
102
-
103
90
  end
104
91
  end
105
92
 
@@ -1,6 +1,6 @@
1
1
  module Lusnoc
2
2
 
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '0.1.2'.freeze
4
4
 
5
5
  end
6
6
 
@@ -1,4 +1,5 @@
1
- require 'lusnoc/timeouter'
1
+ require 'timeouter'
2
+ require 'lusnoc/exceptions'
2
3
  require 'lusnoc/helper'
3
4
 
4
5
  module Lusnoc
@@ -8,12 +9,12 @@ module Lusnoc
8
9
 
9
10
  def initialize(base_url,
10
11
  timeout: 0,
11
- exception_class: TimeoutError,
12
- exception_message: 'watch timeout')
12
+ eclass: Lusnoc::TimeoutError,
13
+ emessage: 'watch timeout')
13
14
  @base_url = base_url
14
15
  @timeout = timeout
15
- @exception_class = exception_class
16
- @exception_message = exception_message
16
+ @eclass = eclass
17
+ @emessage = emessage
17
18
  end
18
19
 
19
20
  # run Consul blocking request in a loop with timeout support.
@@ -22,13 +23,11 @@ module Lusnoc
22
23
  logger.debug "Watch #{@base_url} with #{@timeout.inspect} timeout"
23
24
  last_x_consul_index = 1
24
25
 
25
- Timeouter.new(@timeout,
26
- exception_class: @exception_class,
27
- exception_message: @exception_message).loop! do |timeouter|
28
- wait_condition = timeouter.left ? "&wait=#{timeouter.left.to_i}s" : ''
26
+ Timeouter.loop!(@timeout, eclass: @eclass, message: @emessage) do |t|
27
+ wait_condition = t.left ? "&wait=#{t.left.to_i}s" : ''
29
28
  url = "#{@base_url}?index=#{last_x_consul_index}#{wait_condition}"
30
29
 
31
- resp = Lusnoc.http_get(url, timeout: timeouter.left)
30
+ resp = Lusnoc.http_get(url, timeout: t.left)
32
31
  return true if yield(resp.body)
33
32
 
34
33
  logger.debug "Watch #{@base_url} response: #{resp.body}"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lusnoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2.16548
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samoilenko Yuri
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-22 00:00:00.000000000 Z
11
+ date: 2019-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -59,7 +59,35 @@ dependencies:
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
61
  - !ruby/object:Gem::Dependency
62
- name: rubocop
62
+ name: rspec_junit_formatter
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: simplecov
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: simplecov-console
63
91
  requirement: !ruby/object:Gem::Requirement
64
92
  requirements:
65
93
  - - ">="
@@ -100,6 +128,20 @@ dependencies:
100
128
  - - ">="
101
129
  - !ruby/object:Gem::Version
102
130
  version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: timeouter
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :runtime
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
103
145
  description: Lusnoc is reliable gem to deal with consul
104
146
  email:
105
147
  - kinnalru@gmail.com
@@ -107,14 +149,15 @@ executables: []
107
149
  extensions: []
108
150
  extra_rdoc_files: []
109
151
  files:
152
+ - LICENSE
110
153
  - README.md
111
154
  - lib/lusnoc.rb
112
155
  - lib/lusnoc/configuration.rb
113
156
  - lib/lusnoc/exceptions.rb
157
+ - lib/lusnoc/guard.rb
114
158
  - lib/lusnoc/helper.rb
115
159
  - lib/lusnoc/mutex.rb
116
160
  - lib/lusnoc/session.rb
117
- - lib/lusnoc/timeouter.rb
118
161
  - lib/lusnoc/version.rb
119
162
  - lib/lusnoc/watcher.rb
120
163
  homepage: https://github.com/RnD-Soft/lusnoc
@@ -136,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
179
  - !ruby/object:Gem::Version
137
180
  version: '0'
138
181
  requirements: []
139
- rubygems_version: 3.0.4
182
+ rubygems_version: 3.0.3
140
183
  signing_key:
141
184
  specification_version: 4
142
185
  summary: Lusnoc is reliable gem to deal with consul
@@ -1,51 +0,0 @@
1
- require 'lusnoc/exceptions'
2
-
3
- module Lusnoc
4
- class Timeouter
5
-
6
- attr_reader :exhausted_at, :started_at
7
-
8
- def initialize(timeout = 0,
9
- exception_class: TimeoutError,
10
- exception_message: 'execution expired')
11
- timeout ||= 0
12
- timeout = [timeout, 0].max
13
-
14
- @default_exception_class = exception_class
15
- @default_exception_message = exception_message
16
-
17
- @started_at = Time.now.to_f
18
- @exhausted_at = timeout > 0 ? @started_at + timeout : nil
19
- end
20
-
21
- def self.timeout(timeout = 0)
22
- self.new(timeout)
23
- end
24
-
25
- def elapsed
26
- Time.now.to_f - @started_at
27
- end
28
-
29
- def left
30
- @exhausted_at && [@exhausted_at - Time.now.to_f, 0].max
31
- end
32
-
33
- def check
34
- !@exhausted_at || (@exhausted_at > Time.now.to_f)
35
- end
36
-
37
- def check!(exception_class = @default_exception_class)
38
- check || (raise exception_class.new(@default_exception_message))
39
- end
40
-
41
- def loop
42
- yield(self) while self.check
43
- end
44
-
45
- def loop!(exception_class = @default_exception_class)
46
- yield(self) while self.check!(exception_class)
47
- end
48
-
49
- end
50
- end
51
-