belated 0.5.5 → 0.6.1

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: 94a7f0ce2c6f7c80dfe5b8410c72dac95dbc709937361b36aa7eaeea091d8cfb
4
- data.tar.gz: da41ab683e0a2729b1dc1e7228592e91a0d259cc46bbaf6ef4a2186abb379330
3
+ metadata.gz: 8c9e0e522a8de63f70d61dfeb7d02e8382c957f9e39858a5ce76fc063d4b2beb
4
+ data.tar.gz: 5cd83e71ab30ce2ceb5bc357bf53963399ed6b8aae5beae6930eff98a0f79b10
5
5
  SHA512:
6
- metadata.gz: cacbed28173905bf3fd5489bb8797abecfe4402096cea5d068dcec5a3bf27d9d5c1d048c7d9cc159aedef9cda981a038f4177d6ccd3648c87752d2bedb94390d
7
- data.tar.gz: f92f505a4d8a53c13b4411ae611d5584377f09b31f8e33f9f39a1daf77b3684b977e3c01a472a654c4c07251a1e0aba6fc2f9953eb7383ef1c15fd7be2385e33
6
+ metadata.gz: 3cc2687dd4cdbbae82dc205a5c2ea851dd8b6a7f24471b424e8e1aae547a3fb01be063adbf765f92de10fcf886cf5eb10ac23b2c1144fa7c2d0c5cff59acb803
7
+ data.tar.gz: 748fd28b0eed08dfb16f221e3519c11c41235cdfd7527fe0ce68c6cdd5b996614c7e61ca6b48f31b66c32f3e20d94dce681be307abbcc616f8df39f6752df3b3
@@ -10,7 +10,8 @@ jobs:
10
10
  - name: Set up Ruby
11
11
  uses: ruby/setup-ruby@v1
12
12
  with:
13
- ruby-version: 3.0.1
13
+ ruby-version: 3.0.2
14
14
  bundler-cache: true
15
+ cache-version: 1
15
16
  - name: Run the default task
16
17
  run: bundle exec rake
data/.rubocop.yml CHANGED
@@ -1,7 +1,12 @@
1
- require: rubocop-performance
2
1
  AllCops:
3
2
  TargetRubyVersion: 2.7
4
3
  NewCops: enable
4
+ Exclude:
5
+ - 'dummy/**/*'
6
+ - 'vendor/**/*'
7
+
8
+ Lint/HashCompareByIdentity:
9
+ Enabled: false
5
10
 
6
11
  Style/StringLiterals:
7
12
  Enabled: true
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.1] - 2021-08-20
4
+
5
+ When the client closes and worker has a reference to a proc, a `DRb::ConnError` is raised. Rescueing it and ignoring it.
6
+ ## [0.6.0] - 2021-08-19
7
+
8
+ - Only need to keep references on the client side for procs. Not needed for classes, as they are pass-by-value. However, you can only pass procs by reference, so need to keep track of them. They're removed from the client side when they're completed though.
9
+ - The client is now a singleton. This is because it had some overhead when pushing the jobs to dRuby, so I took the approach of also doing that in a background thread. You however do not want more than one client to be running at the same time, so making it a singleton is the best option. Call the `.instance` method to get the singleton and then `.start` to get it started.
10
+
11
+ ## [0.5.7] - 2021-08-18
12
+
13
+ - Got errors under heavy load and restarting. Hopefully fixed by rescuing the DRb connection error.
14
+ ## [0.5.6] - 2021-08-17
15
+
16
+ - Now the client has a hash table that holds references to the objects you push through it. This is to get by GC, otherwise the objects are collected on clientside, but are still referenced on server side. This means that you do not want to use two instances of Client at the same time. Also, might need to write a way to close the client...
17
+ - Should work a bit more safely now! Performance testing 0.5.5 online using a Rails project, it performed horribly, so it should be a bit better now.
3
18
  ## [0.5.5] - 2021-08-15
4
19
 
5
20
  - Use SortedSet for future jobs, to avoid having to go through the whole list every few seconds.
data/Gemfile CHANGED
@@ -13,6 +13,6 @@ gem 'database_cleaner-active_record'
13
13
  gem 'rails', '>= 6.1.3'
14
14
  gem 'rspec-rails'
15
15
  gem 'rubocop', '~> 1.7'
16
- gem 'rubocop-performance', require: false
16
+ gem 'rubocop-discourse'
17
17
  gem 'sqlite3'
18
18
  gem 'stackprof'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- belated (0.5.5)
4
+ belated (0.6.1)
5
5
  drb
6
6
  dry-configurable
7
7
  sorted_set
@@ -160,20 +160,23 @@ GEM
160
160
  rspec-mocks (~> 3.10)
161
161
  rspec-support (~> 3.10)
162
162
  rspec-support (3.10.2)
163
- rubocop (1.18.3)
163
+ rubocop (1.19.0)
164
164
  parallel (~> 1.10)
165
165
  parser (>= 3.0.0.0)
166
166
  rainbow (>= 2.2.2, < 4.0)
167
167
  regexp_parser (>= 1.8, < 3.0)
168
168
  rexml
169
- rubocop-ast (>= 1.7.0, < 2.0)
169
+ rubocop-ast (>= 1.9.1, < 2.0)
170
170
  ruby-progressbar (~> 1.7)
171
171
  unicode-display_width (>= 1.4.0, < 3.0)
172
- rubocop-ast (1.7.0)
172
+ rubocop-ast (1.10.0)
173
173
  parser (>= 3.0.1.1)
174
- rubocop-performance (1.11.4)
175
- rubocop (>= 1.7.0, < 2.0)
176
- rubocop-ast (>= 0.4.0)
174
+ rubocop-discourse (2.4.2)
175
+ rubocop (>= 1.1.0)
176
+ rubocop-rspec (>= 2.0.0)
177
+ rubocop-rspec (2.4.0)
178
+ rubocop (~> 1.0)
179
+ rubocop-ast (>= 1.1.0)
177
180
  ruby-progressbar (1.11.0)
178
181
  set (1.0.1)
179
182
  sorted_set (1.0.3)
@@ -209,7 +212,7 @@ DEPENDENCIES
209
212
  rspec (~> 3.0)
210
213
  rspec-rails
211
214
  rubocop (~> 1.7)
212
- rubocop-performance
215
+ rubocop-discourse
213
216
  sqlite3
214
217
  stackprof
215
218
 
data/README.md CHANGED
@@ -14,22 +14,22 @@ Note that currently the timezone is hardcoded to UTC.
14
14
 
15
15
  Can be used with or without Rails.
16
16
 
17
+ 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
+
17
19
  TODO LIST:
18
20
 
19
- - Use GDBM for queeue storage? That way could maybe get rid of YAML dumping and make things a bit safer.
20
- - Rescue `DRb::DRbRemoteError` when shutting down, might not need to if using GDBM?
21
- - Don't use class instance variables.
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?
22
23
  - Make DRb port configurable.
23
- - Don't hardcode timezone.
24
+ - Don't hardcode timezone to UTC.
24
25
  - Add some checks to the client for proper jobs.
25
- - Have multiple queues?
26
26
  - Maybe support ActiveJob?
27
27
  - Have a web UI.
28
28
  - Have a job history
29
29
  - Do some performance testing.
30
30
  - Deploy a Rails app to production that is using Belated
31
31
  and mention it in the readme. (Capistrano support?)
32
- ([Wasurechatta](wasurechatta.com))
32
+ ([Wasurechatta](https://wasurechatta.com/))
33
33
  - Add a section telling people to use Sidekiq if they can
34
34
 
35
35
  ## Installation
@@ -85,10 +85,17 @@ First, start up Belated.
85
85
  Then,
86
86
 
87
87
  ```ruby
88
- client = Belated::Client.new
88
+ # Get the client
89
+ client = Belated::Client.instance
90
+ # Start the client, only need to do this once
91
+ client.start unless client.started?
89
92
  ```
90
93
 
91
94
  and you can use the client!
95
+ Note that the client is a singleton.
96
+ This means that you can only have one client running at a time,
97
+ but it also means you only have one connection to dRuby, and that the number of threads in charge of queuing the jobs is only one.
98
+
92
99
  Call
93
100
 
94
101
  ```ruby
@@ -104,7 +111,7 @@ If you don't want the job to run right away, you can also pass it a keyword para
104
111
  client.perform_belated(job, Time.now + 1.month)
105
112
  ```
106
113
 
107
- Note that you probably want to memoize the client, as it always creates a 'banker thread' now if you have no connection and there is the overhead of connecting to dRuby. Maybe even use it as a global!(`$client`)
114
+ The client also holds references to the jobs that are instances of `Proc` that have been pushed so that they are not collected by GC. This is because procs are passed by reference, and the client needs to keep them alive. They are removed from the list when the job is done.
108
115
 
109
116
  # Settings
110
117
 
data/lib/belated.rb CHANGED
@@ -65,9 +65,9 @@ class Belated
65
65
  @@queue.push(:shutdown)
66
66
  end
67
67
  Thread.new { stop_workers }
68
- # Max 30 seconds to shutdown
68
+ # Max 40 seconds to shutdown
69
69
  timeout = 0
70
- until (timeout += 0.1) >= 30 || @@queue.empty? || $TESTING
70
+ until (timeout += 0.1) >= 40 || @@queue.empty? || $TESTING
71
71
  sleep 0.1
72
72
  end
73
73
  exit
@@ -89,19 +89,20 @@ class Belated
89
89
  end
90
90
 
91
91
  def enqueue_future_jobs
92
- log 'starting future jobs thread'
93
92
  loop do
94
93
  sleep 0.1
95
- job = @@queue.future_jobs.at(0)
94
+ job = @@queue.future_jobs.min
96
95
  if job.nil?
97
96
  sleep 5
98
97
  next
99
98
  end
100
-
101
99
  if job.at <= Time.now.utc
102
100
  log "Deleting #{@@queue.future_jobs.delete(job)} from future jobs"
103
101
  @@queue.push(job)
104
102
  end
103
+ rescue DRb::DRbConnError
104
+ log 'DRb connection error!!!!!!'
105
+ log stats
105
106
  end
106
107
  end
107
108
 
@@ -112,7 +113,8 @@ class Belated
112
113
 
113
114
  def stop_workers
114
115
  @worker_list&.each do |worker|
115
- sleep 0.1 if worker.alive?
116
+ i = 0
117
+ sleep 0.1 while worker.alive? || (i + 0.1) < 10
116
118
  Thread.kill(worker)
117
119
  end
118
120
  @@queue.save_jobs
@@ -138,7 +140,7 @@ class Belated
138
140
 
139
141
  def banner_and_info
140
142
  log banner
141
- log "Currently running Belated version #{Belated::VERSION}"
143
+ log "Currently running Belated version #{Belated::VERSION} in #{Belated.config.environment}"
142
144
  log %(Belated running #{@worker_list&.length.to_i} workers on #{URI}...)
143
145
  end
144
146
 
@@ -1,4 +1,5 @@
1
1
  require 'belated/job_wrapper'
2
+ require 'singleton'
2
3
  class Belated
3
4
  # The client class is responsible for managing the connection to the
4
5
  # DRb server. If it has no connection, it adds the jobs to a bank queue.
@@ -7,16 +8,25 @@ class Belated
7
8
  # client = Belated::Client.new
8
9
  # client.enqueue(JubJub.new, at: Time.now + 5.seconds)
9
10
  class Client
10
- attr_accessor :queue, :bank, :banker_thread
11
+ include Singleton unless $TESTING
12
+
13
+ attr_accessor :queue, :bank, :banker_thread, :proc_table
11
14
 
12
15
  # Starts up the client.
13
16
  # Connects to the queue through DRb.
14
17
  # @return [void]
15
- def initialize
18
+ def start
16
19
  server_uri = Belated::URI
17
20
  DRb.start_service
21
+ self.proc_table = {}
18
22
  self.bank = Thread::Queue.new
19
23
  self.queue = DRbObject.new_with_uri(server_uri)
24
+ @started = true
25
+ end
26
+ alias initialize start
27
+
28
+ def started?
29
+ @started
20
30
  end
21
31
 
22
32
  # Thread in charge of handling the bank queue.
@@ -26,16 +36,25 @@ class Belated
26
36
  def start_banker_thread
27
37
  self.banker_thread = Thread.new do
28
38
  loop do
29
- sleep 0.01
30
- unless drb_connected?
31
- sleep(10)
39
+ delete_from_table
40
+ if bank.empty?
41
+ sleep 10
32
42
  next
33
43
  end
44
+ begin
45
+ queue.push(wrapper = bank.pop)
46
+ rescue DRb::DRbConnError
47
+ bank.push(wrapper)
48
+ end
49
+ end
50
+ end
51
+ end
34
52
 
35
- job = bank.pop
53
+ def delete_from_table
54
+ return if proc_table.empty?
36
55
 
37
- perform(job)
38
- end
56
+ proc_table.select { |_k, v| v.completed }.each do |key, _value|
57
+ proc_table.delete(key)
39
58
  end
40
59
  end
41
60
 
@@ -46,16 +65,17 @@ class Belated
46
65
  # @param max_retries [Integer] - Times the job should be retried if it fails.
47
66
  # @return [JobWrapper] - The job wrapper for the queue.
48
67
  def perform(job, at: nil, max_retries: 5)
68
+ log 'Passing a proc and at time is deprecated and will be removed in 0.6' if job.instance_of?(Proc) && !at.nil?
69
+
49
70
  job_wrapper = if job.is_a?(JobWrapper)
50
71
  job
51
72
  else
52
73
  JobWrapper.new(job: job, at: at, max_retries: max_retries)
53
74
  end
54
- pp queue.push(job_wrapper)
55
- job_wrapper
56
- rescue DRb::DRbConnError
57
75
  bank.push(job_wrapper)
76
+ proc_table[job_wrapper.object_id] = job_wrapper if job_wrapper.proc_klass
58
77
  start_banker_thread if banker_thread.nil?
78
+ job_wrapper
59
79
  end
60
80
  alias perform_belated perform
61
81
  alias perform_later perform
@@ -2,10 +2,18 @@ require 'securerandom'
2
2
  require_relative 'logging'
3
3
 
4
4
  class Belated
5
+ # JobWrapper is a wrapper for a job. It is responsible for
6
+ # - logging
7
+ # - error handling
8
+ # - job execution
9
+ # - job result handling
10
+ # - job result logging
11
+ # - job retries
12
+ # - job retry delay
5
13
  class JobWrapper
6
14
  include Comparable
7
15
  include Logging
8
- attr_accessor :retries, :max_retries, :id, :job, :at
16
+ attr_accessor :retries, :max_retries, :id, :job, :at, :completed, :proc_klass
9
17
 
10
18
  def initialize(job:, max_retries: 5, at: nil)
11
19
  self.retries = 0
@@ -13,19 +21,23 @@ class Belated
13
21
  self.id = SecureRandom.uuid
14
22
  self.job = job
15
23
  self.at = at
24
+ self.completed = false
25
+ self.proc_klass = job.instance_of?(Proc)
16
26
  end
17
27
 
18
- def <=>(another)
19
- at <=> another.at
28
+ def <=>(other)
29
+ at <=> other.at
20
30
  end
21
31
 
22
32
  # rubocop:disable Lint/RescueException
23
33
  def perform
24
- if job.respond_to?(:call)
25
- job.call
26
- else
27
- job.perform
28
- end
34
+ resp = if job.respond_to?(:call)
35
+ job.call
36
+ else
37
+ job.perform
38
+ end
39
+ self.completed = true
40
+ resp
29
41
  rescue Exception => e
30
42
  case e.class
31
43
  when Interrupt, SignalException
@@ -1,4 +1,6 @@
1
1
  class Belated
2
+ # Logger for Belated.
3
+ # Include this module in your class to get a logger.
2
4
  module Logging
3
5
  extend self
4
6
 
data/lib/belated/queue.rb CHANGED
@@ -6,6 +6,11 @@ require 'belated/job_wrapper'
6
6
  require 'sorted_set'
7
7
 
8
8
  class Belated
9
+ # Job queues that Belated uses.
10
+ # queue is the jobs that are currenly
11
+ # waiting for a worker to start working on them.
12
+ # future_jobs is a SortedSet of jobs that are going
13
+ # to be added to queue at some point in the future.
9
14
  class Queue
10
15
  include Logging
11
16
  attr_accessor :future_jobs
@@ -81,7 +86,7 @@ class Belated
81
86
  private
82
87
 
83
88
  def proc_or_shutdown?(job)
84
- job.job.instance_of?(Proc) || job.is_a?(Symbol)
89
+ job.is_a?(Symbol) || job.job.instance_of?(Proc)
85
90
  end
86
91
  end
87
92
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Belated
4
- VERSION = '0.5.5'
4
+ VERSION = '0.6.1'
5
5
  end
@@ -19,7 +19,9 @@ class Belated
19
19
  break if job.is_a?(Symbol)
20
20
 
21
21
  log "Worker #{@number} got job: #{job.inspect}"
22
- job.perform
22
+ log job.perform
23
+ rescue DRb::DRbConnError, Errno::ECONNREFUSED, RangeError => e
24
+ log e
23
25
  end
24
26
  end
25
27
  end
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.5.5
4
+ version: 0.6.1
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-15 00:00:00.000000000 Z
11
+ date: 2021-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: drb