always 0.0.3 → 0.0.5

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: ed19cd7a3109c141b0537f2229876cc00f5d0a70740095b810b281c47fe44834
4
- data.tar.gz: 793763757135f8492edb8bb6b6abb6b9ee19459c7e35d3acde3ac13f0c97a230
3
+ metadata.gz: 3e58e6ba3b68a86d0f9fc12a7bc34580085f00b4511e5ea34167465338a51789
4
+ data.tar.gz: cac5d0bc4f8d7d16aff1ae095e4b204c431737603fa2a3be4245c10a44d0bb84
5
5
  SHA512:
6
- metadata.gz: 3c09324fe351d257d252e466e9be7d263ffb0e9781d1134b777ae207ee0321779273b82771d595fad47f4ca67ed2bebbc4a8864758c0f522f0233f66b5745b79
7
- data.tar.gz: 44c0a7f8d9117e69845c58577b6f40ccd29f7292e07c9df0cc18a05870a3fb4b5795c85cc0bfcd5434c543ed5575a081b35de6cc7a402ef59d70459ac87659de
6
+ metadata.gz: ec65e4cc308a110d2867ca647e197061b1811b15a09da8dd9e5e71259135808409c0e26d47c738d9e0c3187cf93050caec5779622cf9c5a8f14e895befaf18c9
7
+ data.tar.gz: 6fa3988429a11ef790c92715a23a9f31a4445ffd27fcbc5eaa8ecfd91d5d42f7c7da18d6ee18802d4f0000bd9c6b9ad86ee84a1a1e9e9c333e94a75633e48bd8
data/.rubocop.yml CHANGED
@@ -27,3 +27,8 @@ AllCops:
27
27
  TargetRubyVersion: 3.2
28
28
  SuggestExtensions: false
29
29
  NewCops: enable
30
+
31
+ Layout/EndOfLine:
32
+ EnforcedStyle: lf
33
+ Style/EvalWithLocation:
34
+ Enabled: false
data/Gemfile CHANGED
@@ -23,11 +23,11 @@
23
23
  source 'https://rubygems.org'
24
24
  gemspec
25
25
 
26
- gem 'minitest', '5.23.1', require: false
26
+ gem 'minitest', '5.24.1', require: false
27
27
  gem 'rake', '13.2.1', require: false
28
- gem 'rspec-rails', '6.1.2', require: false
28
+ gem 'rspec-rails', '6.1.3', require: false
29
29
  gem 'rubocop', '1.64.1', require: false
30
- gem 'rubocop-rspec', '2.29.2', require: false
30
+ gem 'rubocop-rspec', '3.0.2', require: false
31
31
  gem 'simplecov', '0.22.0', require: false
32
32
  gem 'simplecov-cobertura', '2.1.0', require: false
33
33
  gem 'yard', '0.9.36', require: false
data/Gemfile.lock CHANGED
@@ -7,9 +7,9 @@ PATH
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- actionpack (7.1.3.3)
11
- actionview (= 7.1.3.3)
12
- activesupport (= 7.1.3.3)
10
+ actionpack (7.1.3.4)
11
+ actionview (= 7.1.3.4)
12
+ activesupport (= 7.1.3.4)
13
13
  nokogiri (>= 1.8.5)
14
14
  racc
15
15
  rack (>= 2.2.4)
@@ -17,13 +17,13 @@ GEM
17
17
  rack-test (>= 0.6.3)
18
18
  rails-dom-testing (~> 2.2)
19
19
  rails-html-sanitizer (~> 1.6)
20
- actionview (7.1.3.3)
21
- activesupport (= 7.1.3.3)
20
+ actionview (7.1.3.4)
21
+ activesupport (= 7.1.3.4)
22
22
  builder (~> 3.1)
23
23
  erubi (~> 1.11)
24
24
  rails-dom-testing (~> 2.2)
25
25
  rails-html-sanitizer (~> 1.6)
26
- activesupport (7.1.3.3)
26
+ activesupport (7.1.3.4)
27
27
  base64
28
28
  bigdecimal
29
29
  concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -36,18 +36,18 @@ GEM
36
36
  ast (2.4.2)
37
37
  base64 (0.2.0)
38
38
  bigdecimal (3.1.8)
39
- builder (3.2.4)
40
- concurrent-ruby (1.3.1)
39
+ builder (3.3.0)
40
+ concurrent-ruby (1.3.3)
41
41
  connection_pool (2.4.1)
42
42
  crass (1.0.6)
43
43
  diff-lcs (1.5.1)
44
44
  docile (1.4.0)
45
45
  drb (2.2.1)
46
- erubi (1.12.0)
46
+ erubi (1.13.0)
47
47
  i18n (1.14.5)
48
48
  concurrent-ruby (~> 1.0)
49
49
  io-console (0.7.2)
50
- irb (1.13.1)
50
+ irb (1.13.2)
51
51
  rdoc (>= 4.0.0)
52
52
  reline (>= 0.4.2)
53
53
  json (2.7.2)
@@ -55,30 +55,30 @@ GEM
55
55
  loofah (2.22.0)
56
56
  crass (~> 1.0.2)
57
57
  nokogiri (>= 1.12.0)
58
- minitest (5.23.1)
58
+ minitest (5.24.1)
59
59
  mutex_m (0.2.0)
60
- nokogiri (1.16.5-aarch64-linux)
60
+ nokogiri (1.16.6-aarch64-linux)
61
61
  racc (~> 1.4)
62
- nokogiri (1.16.5-arm-linux)
62
+ nokogiri (1.16.6-arm-linux)
63
63
  racc (~> 1.4)
64
- nokogiri (1.16.5-arm64-darwin)
64
+ nokogiri (1.16.6-arm64-darwin)
65
65
  racc (~> 1.4)
66
- nokogiri (1.16.5-x64-mingw-ucrt)
66
+ nokogiri (1.16.6-x64-mingw-ucrt)
67
67
  racc (~> 1.4)
68
- nokogiri (1.16.5-x86-linux)
68
+ nokogiri (1.16.6-x86-linux)
69
69
  racc (~> 1.4)
70
- nokogiri (1.16.5-x86_64-darwin)
70
+ nokogiri (1.16.6-x86_64-darwin)
71
71
  racc (~> 1.4)
72
- nokogiri (1.16.5-x86_64-linux)
72
+ nokogiri (1.16.6-x86_64-linux)
73
73
  racc (~> 1.4)
74
- parallel (1.24.0)
75
- parser (3.3.2.0)
74
+ parallel (1.25.1)
75
+ parser (3.3.3.0)
76
76
  ast (~> 2.4.1)
77
77
  racc
78
78
  psych (5.1.2)
79
79
  stringio
80
80
  racc (1.8.0)
81
- rack (3.0.11)
81
+ rack (3.1.6)
82
82
  rack-session (2.0.0)
83
83
  rack (>= 3.0.0)
84
84
  rack-test (2.1.0)
@@ -93,9 +93,9 @@ GEM
93
93
  rails-html-sanitizer (1.6.0)
94
94
  loofah (~> 2.21)
95
95
  nokogiri (~> 1.14)
96
- railties (7.1.3.3)
97
- actionpack (= 7.1.3.3)
98
- activesupport (= 7.1.3.3)
96
+ railties (7.1.3.4)
97
+ actionpack (= 7.1.3.4)
98
+ activesupport (= 7.1.3.4)
99
99
  irb
100
100
  rackup (>= 1.0.0)
101
101
  rake (>= 12.2)
@@ -106,19 +106,19 @@ GEM
106
106
  rdoc (6.7.0)
107
107
  psych (>= 4.0.0)
108
108
  regexp_parser (2.9.2)
109
- reline (0.5.8)
109
+ reline (0.5.9)
110
110
  io-console (~> 0.5)
111
- rexml (3.2.8)
112
- strscan (>= 3.0.9)
111
+ rexml (3.3.1)
112
+ strscan
113
113
  rspec-core (3.13.0)
114
114
  rspec-support (~> 3.13.0)
115
- rspec-expectations (3.13.0)
115
+ rspec-expectations (3.13.1)
116
116
  diff-lcs (>= 1.2.0, < 2.0)
117
117
  rspec-support (~> 3.13.0)
118
118
  rspec-mocks (3.13.1)
119
119
  diff-lcs (>= 1.2.0, < 2.0)
120
120
  rspec-support (~> 3.13.0)
121
- rspec-rails (6.1.2)
121
+ rspec-rails (6.1.3)
122
122
  actionpack (>= 6.1)
123
123
  activesupport (>= 6.1)
124
124
  railties (>= 6.1)
@@ -140,17 +140,8 @@ GEM
140
140
  unicode-display_width (>= 2.4.0, < 3.0)
141
141
  rubocop-ast (1.31.3)
142
142
  parser (>= 3.3.1.0)
143
- rubocop-capybara (2.20.0)
144
- rubocop (~> 1.41)
145
- rubocop-factory_bot (2.25.1)
146
- rubocop (~> 1.41)
147
- rubocop-rspec (2.29.2)
148
- rubocop (~> 1.40)
149
- rubocop-capybara (~> 2.17)
150
- rubocop-factory_bot (~> 2.22)
151
- rubocop-rspec_rails (~> 2.28)
152
- rubocop-rspec_rails (2.28.3)
153
- rubocop (~> 1.40)
143
+ rubocop-rspec (3.0.2)
144
+ rubocop (~> 1.61)
154
145
  ruby-progressbar (1.13.0)
155
146
  simplecov (0.22.0)
156
147
  docile (~> 1.1)
@@ -161,7 +152,7 @@ GEM
161
152
  simplecov (~> 0.19)
162
153
  simplecov-html (0.12.3)
163
154
  simplecov_json_formatter (0.1.4)
164
- stringio (3.1.0)
155
+ stringio (3.1.1)
165
156
  strscan (3.1.0)
166
157
  thor (1.3.1)
167
158
  tzinfo (2.0.6)
@@ -169,7 +160,7 @@ GEM
169
160
  unicode-display_width (2.5.0)
170
161
  webrick (1.8.1)
171
162
  yard (0.9.36)
172
- zeitwerk (2.6.15)
163
+ zeitwerk (2.6.16)
173
164
 
174
165
  PLATFORMS
175
166
  aarch64-linux
@@ -182,11 +173,11 @@ PLATFORMS
182
173
 
183
174
  DEPENDENCIES
184
175
  always!
185
- minitest (= 5.23.1)
176
+ minitest (= 5.24.1)
186
177
  rake (= 13.2.1)
187
- rspec-rails (= 6.1.2)
178
+ rspec-rails (= 6.1.3)
188
179
  rubocop (= 1.64.1)
189
- rubocop-rspec (= 2.29.2)
180
+ rubocop-rspec (= 3.0.2)
190
181
  simplecov (= 0.22.0)
191
182
  simplecov-cobertura (= 2.1.0)
192
183
  yard (= 0.9.36)
data/README.md CHANGED
@@ -15,9 +15,9 @@ This simple Ruby gem helps you run a loop forever, in a background thread.
15
15
 
16
16
  ```ruby
17
17
  require 'always'
18
- # Prepare, with five threads and 30-seconds delay between loop cycles:
19
- a = Always.new(5, 30)
20
- # Start them all together spinning forever:
18
+ # Prepare, with five threads:
19
+ a = Always.new(5)
20
+ # Start them all together spinning forever with 30-seconds delay between cycles:
21
21
  a.start do
22
22
  puts "I'm alive"
23
23
  end
@@ -25,6 +25,18 @@ end
25
25
  a.stop
26
26
  ```
27
27
 
28
+ You may be interested to get the backtraces of the exceptions that
29
+ happened most recently:
30
+
31
+ ```ruby
32
+ # Keep the last 10 error backtraces in memory:
33
+ a = Always.new(5, max_backtraces: 10)
34
+ # Retrieve them:
35
+ p a.backtraces
36
+ ```
37
+
38
+ That's it.
39
+
28
40
  ## How to contribute
29
41
 
30
42
  Read
data/always.gemspec CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
26
26
  s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
27
27
  s.required_ruby_version = '>=3.2'
28
28
  s.name = 'always'
29
- s.version = '0.0.3'
29
+ s.version = '0.0.5'
30
30
  s.license = 'MIT'
31
31
  s.summary = 'A simple Ruby framework that spins a loop forever, in a background thread'
32
32
  s.description =
data/lib/always.rb CHANGED
@@ -33,29 +33,37 @@ require 'concurrent/atom'
33
33
  # puts 'Hello, world!
34
34
  # end
35
35
  #
36
- # Then, in order stop them all together:
36
+ # Then, in order to stop them all together:
37
37
  #
38
38
  # a.stop
39
39
  #
40
40
  # It's possible to get a quick summary of the thread pool, by calling +to_s+.
41
+ # The result will be a +"T/C/E"+ string, where +T+ is the total number of
42
+ # currently running threads, +C+ is the total number of all cycles
43
+ # so far, and +E+ is the total number of all errors seen so far.
41
44
  #
42
45
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
43
46
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
44
47
  # License:: MIT
45
48
  class Always
49
+ attr_reader :backtraces
50
+
46
51
  # The version of the framework.
47
- VERSION = '0.0.3'
52
+ VERSION = '0.0.5'
48
53
 
49
54
  # Constructor.
50
55
  # @param [Integer] total The number of threads to run
51
- def initialize(total)
56
+ # @param [Integer] max_backtraces How many backtraces to keep in memory?
57
+ def initialize(total, max_backtraces: 32)
52
58
  raise "The number of threads (#{total}) must be positive" unless total.positive?
53
59
 
54
60
  @total = total
55
61
  @on_error = nil
56
62
  @threads = []
63
+ @backtraces = []
57
64
  @cycles = Concurrent::Atom.new(0)
58
65
  @errors = Concurrent::Atom.new(0)
66
+ @max_backtraces = max_backtraces
59
67
  end
60
68
 
61
69
  # What to do when an exception occurs?
@@ -76,23 +84,29 @@ class Always
76
84
  self
77
85
  end
78
86
 
79
- # Start them all.
87
+ # Start them all and let them run forever (until the +stop+ method is called).
80
88
  # @param [Integer] pause The delay between cycles, in seconds
81
89
  def start(pause = 0, &)
82
90
  raise 'It is running now, call .stop() first' unless @threads.empty?
83
91
 
84
92
  (0..@total - 1).each do |i|
85
93
  @threads[i] = Thread.new do
86
- body(i, pause, &)
87
- @cycles.swap { |c| c + 1 }
94
+ body(pause, &)
88
95
  end
89
96
  end
90
97
  end
91
98
 
92
99
  # Stop them all.
93
100
  def stop
94
- @threads.each(&:terminate)
95
- @threads = []
101
+ raise 'It is not running now, call .start() first' if @threads.empty?
102
+
103
+ @threads.delete_if do |t|
104
+ t.kill
105
+ sleep(0.001) while t.alive?
106
+ true
107
+ end
108
+ @cycles.swap { |_| 0 }
109
+ @errors.swap { |_| 0 }
96
110
  end
97
111
 
98
112
  # Represent its internal state as a string.
@@ -106,15 +120,11 @@ class Always
106
120
  private
107
121
 
108
122
  # rubocop:disable Lint/RescueException
109
- def body(idx, pause)
123
+ def body(pause, &)
110
124
  loop do
111
- begin
112
- yield
113
- rescue Exception => e
114
- @errors.swap { |c| c + 1 }
115
- @on_error&.call(e, idx)
116
- end
117
- sleep(pause)
125
+ one(&)
126
+ @cycles.swap { |c| c + 1 }
127
+ sleep(pause) unless pause.zero?
118
128
  rescue Exception
119
129
  # If we reach this point, we must not even try to
120
130
  # do anything. Here we must quietly ignore everything
@@ -122,4 +132,15 @@ class Always
122
132
  end
123
133
  end
124
134
  # rubocop:enable Lint/RescueException
135
+
136
+ # rubocop:disable Lint/RescueException
137
+ def one
138
+ yield
139
+ rescue Exception => e
140
+ @errors.swap { |c| c + 1 }
141
+ @backtraces << e
142
+ @backtraces.shift if @backtraces.size > @max_backtraces
143
+ @on_error&.call(e)
144
+ end
145
+ # rubocop:enable Lint/RescueException
125
146
  end
data/test/test_always.rb CHANGED
@@ -39,7 +39,7 @@ class TestAlways < Minitest::Test
39
39
  def test_with_error
40
40
  a = Always.new(5)
41
41
  failures = 0
42
- a.on_error { |_e, i| failures += i }.start do
42
+ a.on_error { |_e| failures += 1 }.start do
43
43
  raise 'intentionally'
44
44
  end
45
45
  sleep(0.1)
@@ -47,13 +47,37 @@ class TestAlways < Minitest::Test
47
47
  assert(failures.positive?)
48
48
  end
49
49
 
50
- def test_converts_to_string
51
- a = Always.new(5)
52
- a.start do
53
- # nothing
50
+ def test_read_backtraces
51
+ max = 5
52
+ a = Always.new(5, max_backtraces: max)
53
+ failures = 0
54
+ a.on_error { |_e| failures += 1 }.start do
55
+ raise 'intentionally'
54
56
  end
55
- assert(a.to_s.start_with?('5/'))
57
+ sleep(0.1)
58
+ a.stop
59
+ assert(failures.positive?)
60
+ assert_equal(max, a.backtraces.size)
61
+ end
62
+
63
+ def test_converts_to_string
64
+ n = 6
65
+ a = Always.new(6)
66
+ a.start { sleep(0.01) }
67
+ sleep(0.1)
68
+ threads, cycles, errors = a.to_s.split('/')
69
+ assert_equal(n, threads.to_i)
70
+ assert(cycles.to_i.positive?)
71
+ assert(errors.to_i.zero?)
72
+ a.stop
73
+ end
74
+
75
+ def test_stops_correctly
76
+ a = Always.new(6)
77
+ a.start { sleep(0.01) }
78
+ sleep(0.01)
56
79
  a.stop
80
+ assert_equal('0/0/0', a.to_s)
57
81
  end
58
82
 
59
83
  def test_with_counter
@@ -66,4 +90,17 @@ class TestAlways < Minitest::Test
66
90
  a.stop
67
91
  assert(done.positive?)
68
92
  end
93
+
94
+ def test_with_broken_syntax
95
+ a = Always.new(1)
96
+ failures = 0
97
+ a.on_error { |_e| failures += 1 }.start do
98
+ eval('broken$ruby$syntax')
99
+ end
100
+ sleep(0.1)
101
+ _, _, errors = a.to_s.split('/')
102
+ assert(!errors.to_i.zero?)
103
+ assert(!failures.zero?)
104
+ a.stop
105
+ end
69
106
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: always
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-31 00:00:00.000000000 Z
11
+ date: 2024-07-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby