belated 0.6.1 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c9e0e522a8de63f70d61dfeb7d02e8382c957f9e39858a5ce76fc063d4b2beb
4
- data.tar.gz: 5cd83e71ab30ce2ceb5bc357bf53963399ed6b8aae5beae6930eff98a0f79b10
3
+ metadata.gz: b76db91c6812b59cfd2ede6c6e8d5664cde8547de40b0d05345f624e9cba4729
4
+ data.tar.gz: 99e886f91004d6d467db2aa7103e59c0da5bedaa5d2359fd9e91bd69b17a424d
5
5
  SHA512:
6
- metadata.gz: 3cc2687dd4cdbbae82dc205a5c2ea851dd8b6a7f24471b424e8e1aae547a3fb01be063adbf765f92de10fcf886cf5eb10ac23b2c1144fa7c2d0c5cff59acb803
7
- data.tar.gz: 748fd28b0eed08dfb16f221e3519c11c41235cdfd7527fe0ce68c6cdd5b996614c7e61ca6b48f31b66c32f3e20d94dce681be307abbcc616f8df39f6752df3b3
6
+ metadata.gz: a1c095daefe085f5913c363609a3a5b038a0025e133ed0ab16ad131d09a26c4c4d69489b87e5a4939dd6da105b428f6ea9ab186ef38a53bb2ec3605bdbb8086d
7
+ data.tar.gz: 79532774d5169e6781828792b94ef1dd38c0cedbf68b838b95e2c2d64d01d922e0cb9ac093e76524f3c784f9b346bb0e87bda319fb50e1bb1ac8ecd9abd81fbc
data/.rubocop.yml CHANGED
@@ -37,6 +37,9 @@ Style/BlockDelimiters:
37
37
  Style/ModuleFunction:
38
38
  EnforcedStyle: extend_self
39
39
 
40
+ Metrics/AbcSize:
41
+ Max: 17
42
+
40
43
  Metrics/BlockLength:
41
44
  Exclude:
42
45
  - 'spec/**/*.rb'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.5] - 2021-08-23
4
+
5
+ - 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`)
6
+ - Possible to configure host and port.
7
+ - No need to call `.start` on the client anymore.
8
+ - Logging some error and warn messages now too, instead of it all being info
9
+
10
+ ## [0.6.4] - 2021-08-22
11
+ - Inline jobs for testing!
12
+ ```ruby
13
+ `belated/testing`
14
+ Belated::Testing.inline!
15
+ ```
16
+ - Very much inspired by how Sidekiq is doing this.
17
+ - Read more in the testing part of README.md
18
+
19
+ ## [0.6.3] - 2021-08-21
20
+
21
+ - 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.
22
+
23
+ ## [0.6.2] - 2021-08-20
24
+
25
+ - Use a mutex for the proc_table used by the client. Fixes
26
+ `RuntimeError: can't add a new key into hash during iteration (Most recent call first)`, so starting improving thread safety with this fix.
27
+
3
28
  ## [0.6.1] - 2021-08-20
4
29
 
5
30
  When the client closes and worker has a reference to a proc, a `DRb::ConnError` is raised. Rescueing it and ignoring it.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- belated (0.6.1)
4
+ belated (0.6.5)
5
5
  drb
6
6
  dry-configurable
7
7
  sorted_set
data/README.md CHANGED
@@ -18,11 +18,7 @@ Can be used if you're on a normal instance such as EC2 or Digital Ocean drop. No
18
18
 
19
19
  TODO LIST:
20
20
 
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.
21
+ - 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
22
  - Maybe support ActiveJob?
27
23
  - Have a web UI.
28
24
  - Have a job history
@@ -87,8 +83,6 @@ Then,
87
83
  ```ruby
88
84
  # Get the client
89
85
  client = Belated::Client.instance
90
- # Start the client, only need to do this once
91
- client.start unless client.started?
92
86
  ```
93
87
 
94
88
  and you can use the client!
@@ -138,8 +132,31 @@ Path to Rails project.
138
132
 
139
133
  $ bundle exec belated --workers=10
140
134
 
135
+ Other available settings:
136
+
137
+ $ bundle exec belated --host=1.1.1.1 --port=1234
138
+ # druby://1.1.1.1:1234
139
+ $ bundle exec belated --env
140
+ # environment
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/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
data/lib/belated.rb CHANGED
@@ -22,16 +22,18 @@ 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
+ URI = "druby://#{Belated.host}:#{Belated.port}"
35
37
 
36
38
  # Since it's running as a singleton, we need something to start it up.
37
39
  # Aliased for testing purposes.
@@ -55,7 +57,7 @@ class Belated
55
57
  def connect!
56
58
  DRb.start_service(URI, @@queue, verbose: true)
57
59
  rescue DRb::DRbConnError, Errno::EADDRINUSE
58
- Belated.logger.error 'Could not connect to DRb server.'
60
+ error 'Could not connect to DRb server.'
59
61
  end
60
62
 
61
63
  def trap_signals
@@ -96,12 +98,12 @@ class Belated
96
98
  sleep 5
97
99
  next
98
100
  end
99
- if job.at <= Time.now.utc
101
+ if job.at <= Time.now
100
102
  log "Deleting #{@@queue.future_jobs.delete(job)} from future jobs"
101
103
  @@queue.push(job)
102
104
  end
103
105
  rescue DRb::DRbConnError
104
- log 'DRb connection error!!!!!!'
106
+ error 'DRb connection error!!!!!!'
105
107
  log stats
106
108
  end
107
109
  end
@@ -173,8 +175,6 @@ class Belated
173
175
  def self.job_list
174
176
  @@queue
175
177
  end
176
-
177
- class Error < StandardError; end
178
178
  end
179
179
 
180
180
  require 'belated/rails' if defined?(::Rails::Engine)
@@ -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,12 +17,15 @@ 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 = {}
22
25
  self.bank = Thread::Queue.new
23
26
  self.queue = DRbObject.new_with_uri(server_uri)
24
27
  @started = true
28
+ @mutex = Mutex.new
25
29
  end
26
30
  alias initialize start
27
31
 
@@ -29,32 +33,41 @@ class Belated
29
33
  @started
30
34
  end
31
35
 
36
+ # Makes it possible to reset the client
37
+ def turn_off
38
+ @started = false
39
+ banker_thread&.kill
40
+ end
41
+
32
42
  # Thread in charge of handling the bank queue.
33
43
  # You probably want to memoize the client in order to avoid
34
44
  # having many threads in the sleep state.
35
45
  # @return [void]
36
46
  def start_banker_thread
37
- self.banker_thread = Thread.new do
47
+ Thread.new do
38
48
  loop do
39
49
  delete_from_table
40
- if bank.empty?
41
- sleep 10
42
- next
43
- end
44
- begin
45
- queue.push(wrapper = bank.pop)
46
- rescue DRb::DRbConnError
47
- bank.push(wrapper)
50
+ sleep 10 and next if bank.empty?
51
+
52
+ until bank.empty?
53
+ begin
54
+ queue.push(wrapper = bank.pop)
55
+ rescue DRb::DRbConnError
56
+ bank.push(wrapper)
57
+ sleep 5
58
+ end
48
59
  end
49
60
  end
50
61
  end
51
62
  end
52
63
 
53
64
  def delete_from_table
54
- return if proc_table.empty?
65
+ return if proc_table.length < 25
55
66
 
56
- proc_table.select { |_k, v| v.completed }.each do |key, _value|
57
- proc_table.delete(key)
67
+ @mutex.synchronize do
68
+ proc_table.select { |_k, v| v.completed }.each do |key, _value|
69
+ proc_table.delete(key)
70
+ end
58
71
  end
59
72
  end
60
73
 
@@ -65,16 +78,14 @@ class Belated
65
78
  # @param max_retries [Integer] - Times the job should be retried if it fails.
66
79
  # @return [JobWrapper] - The job wrapper for the queue.
67
80
  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
-
70
- job_wrapper = if job.is_a?(JobWrapper)
71
- job
72
- else
73
- JobWrapper.new(job: job, at: at, max_retries: max_retries)
74
- end
81
+ start unless started?
82
+ check_if_proper_job!(job)
83
+ job_wrapper = wrap_job(job, at: at, max_retries: max_retries)
75
84
  bank.push(job_wrapper)
76
- proc_table[job_wrapper.object_id] = job_wrapper if job_wrapper.proc_klass
77
- start_banker_thread if banker_thread.nil?
85
+ @mutex.synchronize do
86
+ proc_table[job_wrapper.object_id] = job_wrapper if job_wrapper.proc_klass
87
+ end
88
+ self.banker_thread = start_banker_thread if banker_thread.nil?
78
89
  job_wrapper
79
90
  end
80
91
  alias perform_belated perform
@@ -82,6 +93,18 @@ class Belated
82
93
 
83
94
  private
84
95
 
96
+ def check_if_proper_job!(job)
97
+ return if job.respond_to?(:call) || job.respond_to?(:perform)
98
+
99
+ raise JobError, 'job does not implement .call nor .perform!'
100
+ end
101
+
102
+ def wrap_job(job, at:, max_retries:)
103
+ return job if job.is_a?(JobWrapper)
104
+
105
+ JobWrapper.new(job: job, at: at, max_retries: max_retries)
106
+ end
107
+
85
108
  def drb_connected?
86
109
  queue.connected?
87
110
  rescue StandardError
@@ -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
@@ -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.1'
4
+ VERSION = '0.6.5'
5
5
  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.6.1
4
+ version: 0.6.5
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-20 00:00:00.000000000 Z
11
+ date: 2021-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: drb
@@ -94,11 +94,13 @@ files:
94
94
  - bin/setup
95
95
  - lib/belated.rb
96
96
  - lib/belated/client.rb
97
+ - lib/belated/exceptions.rb
97
98
  - lib/belated/job.rb
98
99
  - lib/belated/job_wrapper.rb
99
100
  - lib/belated/logging.rb
100
101
  - lib/belated/queue.rb
101
102
  - lib/belated/rails.rb
103
+ - lib/belated/testing.rb
102
104
  - lib/belated/version.rb
103
105
  - lib/belated/worker.rb
104
106
  homepage: https://github.com/sampokuokkanen/belated