belated 0.6.3 → 0.6.7

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: ae5f827cc80ba5fba59161a50ae5797ef774be28feeb478ea641dda6bca79b3a
4
- data.tar.gz: 01a61d9a44ae2b76ad27827e58dd8ba81e2040753925dc7172458c04495f010a
3
+ metadata.gz: e0c28df2d6dffdff23e48d5fba469ca7a67378bebdf6538067f748ebc28d8a67
4
+ data.tar.gz: 1805135d97859dd9d399b3e46d636141b014e102630eefa1e7d0a9dc4a2a19bc
5
5
  SHA512:
6
- metadata.gz: 73a7fe76eacd1334186ef70628a78f193a20a72373d4faaa6bc9b8688ff0d9786156a69b44c3dfb9405837f0f5d980d1317277ad87c33d239d5d8c13b73207a4
7
- data.tar.gz: bcc797637c63a712e81b77189ecd93c325a67fb67f91b392a86d8231b685394341447539c12012280fee392a52a71a6fb20713fdb0ae0be32201c2b2670f5a33
6
+ metadata.gz: '086a0dbc146fe3386a24bef59523aeca27dcb72506e83189f39553f63e20f867f6a094faab2ce07fdaadeb0f159bd740fde9bb8bc7f6bf8b2e4f04ea551f5e2e'
7
+ data.tar.gz: dea44b6d05c0eda6e5cb5d58c3b317c150d1d5b4399fcac55d80c8e044faecbfd1a21d6f96394950ab271e9db2eb346057c99de4cc02df4d25e8929f25487ff3
@@ -4,14 +4,19 @@ on: [push,pull_request]
4
4
 
5
5
  jobs:
6
6
  build:
7
- runs-on: ubuntu-latest
7
+ strategy:
8
+ fail-fast: false
9
+ matrix:
10
+ os: [ubuntu-latest, macos-latest]
11
+ # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
12
+ ruby: [2.6, 2.7, '3.0']
13
+ runs-on: ${{ matrix.os }}
8
14
  steps:
9
15
  - uses: actions/checkout@v2
10
16
  - name: Set up Ruby
11
17
  uses: ruby/setup-ruby@v1
12
18
  with:
13
- ruby-version: 3.0.2
14
- bundler-cache: true
15
- cache-version: 1
19
+ ruby-version: ${{ matrix.ruby }}
20
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
16
21
  - name: Run the default task
17
22
  run: bundle exec rake
data/.rubocop.yml CHANGED
@@ -1,9 +1,10 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.7
2
+ TargetRubyVersion: 2.6
3
3
  NewCops: enable
4
4
  Exclude:
5
5
  - 'dummy/**/*'
6
6
  - 'vendor/**/*'
7
+ - '*gemspec'
7
8
 
8
9
  Lint/HashCompareByIdentity:
9
10
  Enabled: false
@@ -37,6 +38,9 @@ Style/BlockDelimiters:
37
38
  Style/ModuleFunction:
38
39
  EnforcedStyle: extend_self
39
40
 
41
+ Metrics/AbcSize:
42
+ Max: 17
43
+
40
44
  Metrics/BlockLength:
41
45
  Exclude:
42
46
  - 'spec/**/*.rb'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.7] - 2021-08-25
4
+
5
+ - A bug fix for bad jobs bringing down client side.
6
+ - Heartbeat option for server side. It only affects jobs in the wait list, it's used to determine how often to check the future jobs queue.
7
+ ## [0.6.6] - 2021-08-25
8
+
9
+ - Tests now run agains Ruby 2.6, so relaxing the version constraint.
10
+ - Add client_heartbeat option, so you can define how often you want jobs sent to the Belated server.
11
+ ## [0.6.5] - 2021-08-23
12
+
13
+ - Timezone used inside Belated is all using the server time now, so it's up to the user to take care of that(using `Time.now` instead of `Time.now.utc`)
14
+ - Possible to configure host and port.
15
+ - No need to call `.start` on the client anymore.
16
+ - Logging some error and warn messages now too, instead of it all being info
17
+
18
+ ## [0.6.4] - 2021-08-22
19
+ - Inline jobs for testing!
20
+ ```ruby
21
+ `belated/testing`
22
+ Belated::Testing.inline!
23
+ ```
24
+ - Very much inspired by how Sidekiq is doing this.
25
+ - Read more in the testing part of README.md
26
+
3
27
  ## [0.6.3] - 2021-08-21
4
28
 
5
29
  - Needed to have the hash inside the mutex when going over it; otherwise you still the get can't add key into hash during iteration error. Of course.
data/Gemfile CHANGED
@@ -15,4 +15,4 @@ gem 'rspec-rails'
15
15
  gem 'rubocop', '~> 1.7'
16
16
  gem 'rubocop-discourse'
17
17
  gem 'sqlite3'
18
- gem 'stackprof'
18
+ # gem 'stackprof'
data/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- belated (0.6.3)
4
+ belated (0.6.7)
5
5
  drb
6
6
  dry-configurable
7
+ ruby2_keywords
7
8
  sorted_set
8
9
 
9
10
  GEM
@@ -97,9 +98,15 @@ GEM
97
98
  marcel (1.0.1)
98
99
  method_source (1.0.0)
99
100
  mini_mime (1.1.0)
101
+ mini_portile2 (2.6.1)
100
102
  minitest (5.14.4)
101
103
  nio4r (2.5.7)
102
- nokogiri (1.11.7-x86_64-linux)
104
+ nokogiri (1.12.3)
105
+ mini_portile2 (~> 2.6.1)
106
+ racc (~> 1.4)
107
+ nokogiri (1.12.3-x86_64-darwin)
108
+ racc (~> 1.4)
109
+ nokogiri (1.12.3-x86_64-linux)
103
110
  racc (~> 1.4)
104
111
  parallel (1.20.1)
105
112
  parser (3.0.2.0)
@@ -178,6 +185,7 @@ GEM
178
185
  rubocop (~> 1.0)
179
186
  rubocop-ast (>= 1.1.0)
180
187
  ruby-progressbar (1.11.0)
188
+ ruby2_keywords (0.0.5)
181
189
  set (1.0.1)
182
190
  sorted_set (1.0.3)
183
191
  rbtree
@@ -190,7 +198,6 @@ GEM
190
198
  activesupport (>= 4.0)
191
199
  sprockets (>= 3.0.0)
192
200
  sqlite3 (1.4.2)
193
- stackprof (0.2.16)
194
201
  thor (1.1.0)
195
202
  tzinfo (2.0.4)
196
203
  concurrent-ruby (~> 1.0)
@@ -201,6 +208,8 @@ GEM
201
208
  zeitwerk (2.4.2)
202
209
 
203
210
  PLATFORMS
211
+ ruby
212
+ x86_64-darwin-19
204
213
  x86_64-linux
205
214
 
206
215
  DEPENDENCIES
@@ -214,7 +223,6 @@ DEPENDENCIES
214
223
  rubocop (~> 1.7)
215
224
  rubocop-discourse
216
225
  sqlite3
217
- stackprof
218
226
 
219
227
  BUNDLED WITH
220
228
  2.2.22
data/README.md CHANGED
@@ -10,19 +10,13 @@ Note that Belated used to be called HardWorker. That name was already in use in
10
10
 
11
11
  It uses dRuby to do the communication! Which is absolute great. No need for Redis or PostgreSQL, just Ruby standard libraries.
12
12
 
13
- Note that currently the timezone is hardcoded to UTC.
14
-
15
13
  Can be used with or without Rails.
16
14
 
17
15
  Can be used if you're on a normal instance such as EC2 or Digital Ocean drop. Not if you're on a Heroku or Docker, or anything with ephemeral storage.
18
16
 
19
17
  TODO LIST:
20
18
 
21
- - Improve thread safety.
22
- - Use GDBM for queue storage? That way could maybe get rid of YAML dumping and make things a bit safer. Not ordered though, so maybe keep a list of the jobs as YAML and update it sometimes? Just as backup. Or RocksDB?
23
- - Make DRb port configurable.
24
- - Don't hardcode timezone to UTC.
25
- - Add some checks to the client for proper jobs.
19
+ - Use GDBM for queue storage? That way could maybe get rid of YAML dumping and make things a bit safer. Not ordered though, so maybe keep a list of the jobs as YAML and update it sometimes? Just as backup. Or RocksDB? Would need to be configurable if you don't have something installed.
26
20
  - Maybe support ActiveJob?
27
21
  - Have a web UI.
28
22
  - Have a job history
@@ -87,8 +81,6 @@ Then,
87
81
  ```ruby
88
82
  # Get the client
89
83
  client = Belated::Client.instance
90
- # Start the client, only need to do this once
91
- client.start unless client.started?
92
84
  ```
93
85
 
94
86
  and you can use the client!
@@ -138,8 +130,33 @@ Path to Rails project.
138
130
 
139
131
  $ bundle exec belated --workers=10
140
132
 
133
+ Other available settings:
134
+
135
+ $ bundle exec belated --host=1.1.1.1 --port=1234
136
+ # druby://1.1.1.1:1234
137
+ $ bundle exec belated --env=test
138
+ # environment
139
+ $ bundle exec belated --client_heartbeat=10
140
+ # how often client sends jobs to server, default is once every 5 seconds
141
+
142
+
141
143
  Number of workers.
142
144
 
145
+ ## Testing
146
+
147
+ When testing, you can require `belated/testing` and then call `Belated::Testing.inline!` to make your jobs perform inline.
148
+
149
+ ```ruby
150
+ `belated/testing`
151
+ c = Belated::Client.instance
152
+ c.perform(proc { 2/ 1}) # Tries to push the job to the drb backend
153
+ # <Belated::JobWrapper:0x00005654bc2db1f0 @at=nil, @completed=false, @id="95e4dc6a-1876-4adf-ae0f-5ae902f5f024", @job=#<Proc:0x00005654bc2db330 (irb):3>, @max_retries=5, @proc_klass=true, @retries=0>
154
+ Belated::Testing.inline! # Sidekiq-inspired, now jobs run inline
155
+ c.perform(proc { 2/ 1}) # Returns 2 right away
156
+ # 2
157
+ Belated::Client.test_mode_off! # Turn off inline job processing
158
+ ```
159
+
143
160
  ## Development
144
161
 
145
162
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/belated.gemspec CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
15
15
  ).gsub(/\s+/, ' ').strip
16
16
  spec.homepage = 'https://github.com/sampokuokkanen/belated'
17
17
  spec.license = 'MIT'
18
- spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
18
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0')
19
19
 
20
20
  spec.metadata['homepage_uri'] = spec.homepage
21
21
  spec.metadata['source_code_uri'] = spec.homepage
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
33
33
  # Uncomment to register a new dependency of your gem
34
34
  spec.add_dependency 'drb'
35
35
  spec.add_dependency 'dry-configurable'
36
+ spec.add_dependency 'ruby2_keywords'
36
37
  spec.add_dependency 'sorted_set'
37
38
  spec.add_development_dependency 'byebug'
38
39
 
data/bin/belated CHANGED
@@ -28,9 +28,17 @@ OptionParser.new { |opts|
28
28
  Belated.config.env = env
29
29
  end
30
30
 
31
- opts.on('-c=CONNECT', '--connect=CONNECT', 'Start dRuby connection, default true') do |connect|
31
+ opts.on('-c=CONNECT', '--connect=CONNECT', 'Start dRuby connection, default true, use for testing only') do |connect|
32
32
  Belated.config.connect = connect == 'true'
33
33
  end
34
+
35
+ opts.on('-h=HOST', '--host=HOST', 'dRuby host') do |host|
36
+ Belated.config.host = host
37
+ end
38
+
39
+ opts.on('-p=PORT', '--port=PORT', 'dRuby port') do |port|
40
+ Belated.config.port = port
41
+ end
34
42
  }.parse!
35
43
 
36
44
  instance = Belated.instance
@@ -1,4 +1,5 @@
1
1
  require 'belated/job_wrapper'
2
+ require 'belated/exceptions'
2
3
  require 'singleton'
3
4
  class Belated
4
5
  # The client class is responsible for managing the connection to the
@@ -16,6 +17,8 @@ class Belated
16
17
  # Connects to the queue through DRb.
17
18
  # @return [void]
18
19
  def start
20
+ return if started?
21
+
19
22
  server_uri = Belated::URI
20
23
  DRb.start_service
21
24
  self.proc_table = {}
@@ -30,32 +33,33 @@ class Belated
30
33
  @started
31
34
  end
32
35
 
36
+ # Makes it possible to reset the client
37
+ def turn_off
38
+ @started = false
39
+ banker_thread&.kill
40
+ end
41
+
33
42
  # Thread in charge of handling the bank queue.
34
43
  # You probably want to memoize the client in order to avoid
35
44
  # having many threads in the sleep state.
36
45
  # @return [void]
37
46
  def start_banker_thread
38
- self.banker_thread = Thread.new do
47
+ Thread.new do
39
48
  loop do
40
- delete_from_table if proc_table.length > 20
41
- if bank.empty?
42
- sleep 10
43
- next
44
- end
45
- until bank.empty?
46
- begin
47
- queue.push(wrapper = bank.pop)
48
- rescue DRb::DRbConnError
49
- bank.push(wrapper)
50
- sleep 5
51
- end
49
+ delete_from_table
50
+ sleep Belated.client_heartbeat and next if bank.empty?
51
+
52
+ bank.length.times do
53
+ queue.push(wrapper = bank.pop)
54
+ rescue DRb::DRbConnError
55
+ bank.push(wrapper)
52
56
  end
53
57
  end
54
58
  end
55
59
  end
56
60
 
57
61
  def delete_from_table
58
- return if proc_table.empty?
62
+ return if proc_table.length < 25
59
63
 
60
64
  @mutex.synchronize do
61
65
  proc_table.select { |_k, v| v.completed }.each do |key, _value|
@@ -71,12 +75,15 @@ class Belated
71
75
  # @param max_retries [Integer] - Times the job should be retried if it fails.
72
76
  # @return [JobWrapper] - The job wrapper for the queue.
73
77
  def perform(job, at: nil, max_retries: 5)
78
+ start unless started?
79
+ return unless proper_job?(job)
80
+
74
81
  job_wrapper = wrap_job(job, at: at, max_retries: max_retries)
75
82
  bank.push(job_wrapper)
76
83
  @mutex.synchronize do
77
84
  proc_table[job_wrapper.object_id] = job_wrapper if job_wrapper.proc_klass
78
85
  end
79
- start_banker_thread if banker_thread.nil?
86
+ self.banker_thread = start_banker_thread if banker_thread.nil?
80
87
  job_wrapper
81
88
  end
82
89
  alias perform_belated perform
@@ -84,6 +91,13 @@ class Belated
84
91
 
85
92
  private
86
93
 
94
+ def proper_job?(job)
95
+ return true if job.respond_to?(:call) || job.respond_to?(:perform)
96
+
97
+ warn 'job does not implement .call nor .perform!'
98
+ false
99
+ end
100
+
87
101
  def wrap_job(job, at:, max_retries:)
88
102
  return job if job.is_a?(JobWrapper)
89
103
 
@@ -0,0 +1,3 @@
1
+ class Belated
2
+ class JobError < StandardError; end
3
+ end
@@ -53,7 +53,7 @@ class Belated
53
53
  self.retries += 1
54
54
  return if retries > max_retries
55
55
 
56
- self.at = Time.now.utc + (retries.next**4)
56
+ self.at = Time.now + (retries.next**4)
57
57
  log "Job #{id} failed, retrying at #{at}"
58
58
  Belated.job_list.push(self)
59
59
  end
@@ -12,6 +12,14 @@ class Belated
12
12
  logger.__send__(Belated.log_level, message)
13
13
  end
14
14
 
15
+ def warn(message)
16
+ logger.warn(message)
17
+ end
18
+
19
+ def error(message)
20
+ logger.error(message)
21
+ end
22
+
15
23
  def logger=(logger)
16
24
  @logger = logger
17
25
  end
data/lib/belated/queue.rb CHANGED
@@ -19,6 +19,7 @@ class Belated
19
19
 
20
20
  def initialize(queue: Thread::Queue.new, future_jobs: SortedSet.new)
21
21
  @queue = queue
22
+ @mutex = Mutex.new
22
23
  self.future_jobs = future_jobs
23
24
  end
24
25
 
@@ -27,7 +28,9 @@ class Belated
27
28
  job.at <= Time.now.utc
28
29
  @queue.push(job)
29
30
  else
30
- @future_jobs << job
31
+ @mutex.synchronize do
32
+ @future_jobs << job
33
+ end
31
34
  end
32
35
  end
33
36
 
@@ -0,0 +1,37 @@
1
+ class Belated
2
+ # Testing helpers
3
+ # Enable or disable testing
4
+ class Testing
5
+ @@testing = false
6
+
7
+ def self.inline?
8
+ @@testing == true
9
+ end
10
+
11
+ def self.inline!
12
+ @@testing = true
13
+ end
14
+
15
+ def self.test_mode_off!
16
+ @@testing = false
17
+ end
18
+ end
19
+ end
20
+
21
+ class Belated
22
+ # A client that can perform jobs inline
23
+ class Client
24
+ alias old_perform perform
25
+ def perform(job, at: nil, max_retries: 5)
26
+ if Belated::Testing.inline?
27
+ if job.respond_to?(:call)
28
+ job.call
29
+ else
30
+ job.perform
31
+ end
32
+ else
33
+ old_perform(job, at: at, max_retries: max_retries)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Belated
4
- VERSION = '0.6.3'
4
+ VERSION = '0.6.7'
5
5
  end
data/lib/belated.rb CHANGED
@@ -22,16 +22,20 @@ class Belated
22
22
  extend Dry::Configurable
23
23
  include Logging
24
24
  include Singleton unless $TESTING
25
- URI = 'druby://localhost:8788'
26
25
  @@queue = Belated::Queue.new
27
26
 
28
27
  setting :rails, true
29
28
  setting :rails_path, '.'
30
29
  setting :workers, 1
31
30
  setting :connect, true
32
- setting :environment, 'development'
31
+ setting :environment, 'development', reader: true
33
32
  setting :logger, Logger.new($stdout), reader: true
34
33
  setting :log_level, :info, reader: true
34
+ setting :host, 'localhost', reader: true
35
+ setting :port, '8788', reader: true
36
+ setting :heartbeat, 1, reader: true
37
+ setting :client_heartbeat, 5, reader: true
38
+ URI = "druby://#{Belated.host}:#{Belated.port}"
35
39
 
36
40
  # Since it's running as a singleton, we need something to start it up.
37
41
  # Aliased for testing purposes.
@@ -53,9 +57,11 @@ class Belated
53
57
 
54
58
  # Handles connection to DRb server.
55
59
  def connect!
60
+ i = 0
56
61
  DRb.start_service(URI, @@queue, verbose: true)
57
62
  rescue DRb::DRbConnError, Errno::EADDRINUSE
58
- Belated.logger.error 'Could not connect to DRb server.'
63
+ sleep 0.1 and retry if (i += 1) < 5
64
+ error 'Could not connect to DRb server.'
59
65
  end
60
66
 
61
67
  def trap_signals
@@ -90,18 +96,17 @@ class Belated
90
96
 
91
97
  def enqueue_future_jobs
92
98
  loop do
93
- sleep 0.1
94
99
  job = @@queue.future_jobs.min
95
100
  if job.nil?
96
- sleep 5
101
+ sleep Belated.heartbeat
97
102
  next
98
103
  end
99
- if job.at <= Time.now.utc
104
+ if job.at <= Time.now
100
105
  log "Deleting #{@@queue.future_jobs.delete(job)} from future jobs"
101
106
  @@queue.push(job)
102
107
  end
103
108
  rescue DRb::DRbConnError
104
- log 'DRb connection error!!!!!!'
109
+ error 'DRb connection error!!!!!!'
105
110
  log stats
106
111
  end
107
112
  end
@@ -173,8 +178,6 @@ class Belated
173
178
  def self.job_list
174
179
  @@queue
175
180
  end
176
-
177
- class Error < StandardError; end
178
181
  end
179
182
 
180
183
  require 'belated/rails' if defined?(::Rails::Engine)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: belated
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sampo Kuokkanen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-21 00:00:00.000000000 Z
11
+ date: 2021-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: drb
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ruby2_keywords
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: sorted_set
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -94,11 +108,13 @@ files:
94
108
  - bin/setup
95
109
  - lib/belated.rb
96
110
  - lib/belated/client.rb
111
+ - lib/belated/exceptions.rb
97
112
  - lib/belated/job.rb
98
113
  - lib/belated/job_wrapper.rb
99
114
  - lib/belated/logging.rb
100
115
  - lib/belated/queue.rb
101
116
  - lib/belated/rails.rb
117
+ - lib/belated/testing.rb
102
118
  - lib/belated/version.rb
103
119
  - lib/belated/worker.rb
104
120
  homepage: https://github.com/sampokuokkanen/belated
@@ -116,7 +132,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
132
  requirements:
117
133
  - - ">="
118
134
  - !ruby/object:Gem::Version
119
- version: 2.7.0
135
+ version: 2.6.0
120
136
  required_rubygems_version: !ruby/object:Gem::Requirement
121
137
  requirements:
122
138
  - - ">="