belated 0.4.1 → 0.5.0

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: ed06672798a42b07c69c9dc84f32d1ba12a58a7b95028f0c4a2f5d1a5f0b04c4
4
- data.tar.gz: 0ecf13b8abf076c5fbe8a0037017840e55f32c674f4a40092a6e35845503f4e9
3
+ metadata.gz: f63b50cb41ebaa52127b51ef4a38f70467bd6527d16efb6869965d5b3c9d3bab
4
+ data.tar.gz: 03ffefae27d2a9f76854b18abad3e2bd46fe3012e987008b5514fd86f699a5ba
5
5
  SHA512:
6
- metadata.gz: 1e16e639db3c05d9112a4b31232d0cb78dbe6fe93ed05cd33f1d9a7856ded7c40319c145c20cddf1dd5a8bafb2894d8f96d46fc423a6718180e4d5e4584fdc41
7
- data.tar.gz: 86793a533285a0ae5761b3eb11d4f16fe44793cc83d2a24c6f7aebae6a4d27ff7795b3ef2e69ce7e2b12f5c86fb9e108d5252f2aa863c645262f880f0ad56cf7
6
+ metadata.gz: 619b6fc1269319b827dcfe48cc84caa951b00bea7fcd4086ebb73ab4ab72c431a3d2b02c2032e02e305913d2613c6b1f67be9c5f1edbd3a2432a0fdec3667c57
7
+ data.tar.gz: ccbed4ecf9df5627e28929c66287700160f3b9a31dd40b107f186eb1a8fe917cfc80edaa7b756743440d4c643ffa60b93cb06f463b06541929a62639b0fae1fb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+
4
+ ## [0.5.0] - 2021-08-07
5
+
6
+ - Job retries! The jobs now have ids, so you can follow the job and it's retries from the log.
7
+ - Quite a lot has changed internally, so if you were not using the Belated::Queue class to enqueue your jobs, you will need to update your code.
8
+
9
+ ## [0.4.4] - 2021-08-07
10
+
11
+ - Now if you pass something with a syntax error in it as a job, it should not bring down the whole app!
12
+
13
+ ## [0.4.3] - 2021-08-06
14
+
15
+ - Client now starts the banker thread to execute jobs that were enqueued when there was no connection to Belated only if necessary.
16
+ ## [0.4.2] - 2021-08-05
17
+
18
+ - Client also handles no connection, now it saves jobs to a bank and adds them to the queue once it has a connection.
3
19
  ## [0.4.1] - 2021-08-05
4
20
 
5
21
  - Now handles saving future jobs too! So if you have a job enqueued for tomorrow, and restart Belated, it should still be enqueued.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- belated (0.4.1)
4
+ belated (0.5.0)
5
5
  drb
6
6
  dry-configurable
7
7
 
data/README.md CHANGED
@@ -2,34 +2,33 @@
2
2
 
3
3
  [![CodeFactor](https://www.codefactor.io/repository/github/sampokuokkanen/belated/badge)](https://www.codefactor.io/repository/github/sampokuokkanen/belated) [![Gem Version](https://badge.fury.io/rb/belated.svg)](https://badge.fury.io/rb/belated)
4
4
 
5
- This is Belated, a new Ruby backend job library! It supports running procs and classes in the background. To deal with restarts, it uses YAML to load the queue into a file, which it then calls at startup to find the previous jobs.
5
+ This is Belated, a new Ruby backend job library! It supports running procs, lambdas and classes in the background. To deal with restarts, it uses YAML to load the queue into a file, which it then calls at startup to find the previous jobs. There is no way in Ruby to save procs or lambdas to a file, so they are discarded when the process restarts.
6
+
7
+ Belated uses the Ruby Queue class, so it's First In, First Out (FIFO).
6
8
 
7
9
  Note that Belated used to be called HardWorker. That name was already in use in Sidekiq documentation and a bit too generic anyway.
8
10
 
9
11
  It uses dRuby to do the communication! Which is absolute great. No need for Redis or PostgreSQL, just Ruby standard libraries.
10
12
 
13
+ Note that currently the timezone is hardcoded to UTC.
14
+
11
15
  Can be used with or without Rails.
12
16
 
13
17
  TODO LIST:
14
18
 
15
- - Catch SIGTERM and friends
16
- - Now supports it, partly.
17
- - Don't crash on errors (Partially done)
19
+ - Don't use class instance variables.
20
+ - Make port configurable.
21
+ - Don't hardcode timezone.
22
+ - Add some checks to the client for proper jobs.
18
23
  - Have multiple queues?
19
24
  - Maybe support ActiveJob?
20
- - Have a web UI
21
- - Do some performance testing
25
+ - Have a web UI.
26
+ - Have a job history
27
+ - Do some performance testing.
28
+ - Deploy a Rails app to production that is using Belated
29
+ and mention it in the readme. (Capistrano support?)
22
30
  - Add a section telling people to use Sidekiq if they can
23
31
 
24
- DONE
25
-
26
- - ~~Make it possible to schedule jobs~~
27
- - ~~Marshal the job queue into a file so you don't lose all progress~~
28
- (Ended up using YAML)
29
- - ~~Add a logger~~
30
- - ~~Support Rails~~ (Supported!)
31
- - ~~Parse options from command line, eg. `--workers 10`~~(Done!)
32
-
33
32
  ## Installation
34
33
 
35
34
  Add this line to your application's Gemfile:
@@ -56,15 +55,6 @@ Then, in another program, connect to Belated and give it a job to do.
56
55
  Sample below:
57
56
 
58
57
  ```ruby
59
- class DummyWorker
60
- attr_accessor :queue
61
-
62
- def initialize
63
- server_uri = Belated::URI
64
- self.queue = DRbObject.new_with_uri(server_uri)
65
- end
66
- end
67
-
68
58
  class DumDum
69
59
  # classes need to have a perform method
70
60
  def perform
@@ -72,11 +62,14 @@ class DumDum
72
62
  end
73
63
  end
74
64
 
75
- # Need to start dRuby on the client side
76
- DRb.start_service
77
- dummy = DummyWorker.new
78
- dummy.queue.push(proc { 2 / 1 })
79
- dummy.queue.push(DumDum.new)
65
+ client = Belated::Client.new
66
+ client.perform_belated(proc { 2 / 1 })
67
+ client.perform_belated(DumDum.new)
68
+ # client.perform, client.perform_later are also good
69
+ # if you want to do something later:
70
+ client.perform_belated(DumDum.new, at: Time.now + 5 * 60)
71
+ # max retries:
72
+ client.perform_belated(DumDum.new, max_retries: 3) # default 5
80
73
  ```
81
74
 
82
75
  Belated runs on localhost, port 8788.
@@ -96,6 +89,7 @@ and you can use the client!
96
89
  Call
97
90
 
98
91
  ```ruby
92
+ job = proc { 2 / 1 }
99
93
  client.perform_belated(job)
100
94
  ```
101
95
 
@@ -107,6 +101,8 @@ If you don't want the job to run right away, you can also pass it a keyword para
107
101
  client.perform_belated(job, Time.now + 1.month)
108
102
  ```
109
103
 
104
+ 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`)
105
+
110
106
  # Settings
111
107
 
112
108
  Configuring Belated:
data/lib/belated.rb CHANGED
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'belated/logging'
3
4
  require_relative 'belated/version'
4
5
  require_relative 'belated/worker'
5
- require_relative 'belated/logging'
6
+ require 'belated/client'
7
+ require 'belated/job_wrapper'
8
+ require 'belated/queue'
6
9
  require 'drb'
7
- require 'yaml'
8
- require 'singleton'
9
10
  require 'dry-configurable'
10
- require 'belated/client'
11
11
  require 'logger'
12
- require 'belated/queue'
12
+ require 'singleton'
13
+ require 'yaml'
13
14
 
14
15
  # Belated is a pure Ruby job backend.
15
16
  # It has limited functionality, as it only accepts
@@ -64,7 +65,11 @@ class Belated
64
65
  @@queue.push(:shutdown)
65
66
  end
66
67
  Thread.new { stop_workers }
67
- sleep 0.1 until @@queue.empty? || $TESTING
68
+ # Max 30 seconds to shutdown
69
+ timeout = 0
70
+ until (timeout += 0.1) >= 30 || @@queue.empty? || $TESTING
71
+ sleep 0.1
72
+ end
68
73
  exit
69
74
  end
70
75
  end
@@ -87,9 +92,9 @@ class Belated
87
92
  log 'starting future jobs thread'
88
93
  loop do
89
94
  @@queue.future_jobs.each_with_index do |job, i|
90
- if job[:at] <= Time.now.utc
91
- log @@queue.future_jobs.delete_at(i)
92
- @@queue.push(job[:klass])
95
+ if job.at <= Time.now.utc
96
+ log "Deleting #{@@queue.future_jobs.delete_at(i)} from future jobs"
97
+ @@queue.push(job)
93
98
  end
94
99
  end
95
100
  sleep 0.01
@@ -151,12 +156,16 @@ class Belated
151
156
  @@queue.clear
152
157
  end
153
158
 
159
+ def self.fetch_job
160
+ @@queue.pop
161
+ end
162
+
154
163
  def job_list
155
164
  @@queue
156
165
  end
157
166
 
158
- def self.fetch_job
159
- @@queue.pop
167
+ def self.job_list
168
+ @@queue
160
169
  end
161
170
 
162
171
  class Error < StandardError; end
@@ -1,29 +1,56 @@
1
+ require 'belated/job_wrapper'
1
2
  class Belated
2
3
  # The client class is responsible for managing the connection to the
3
- # DRb server.
4
+ # DRb server. If it has no connection, it adds the jobs to a bank queue.
4
5
  # You can enqueue jobs to be processed by the server.
5
6
  # Example:
6
7
  # client = Belated::Client.new
7
8
  # client.enqueue(JubJub.new, at: Time.now + 5.seconds)
8
9
  class Client
9
- attr_accessor :queue
10
+ attr_accessor :queue, :bank, :banker_thread
10
11
 
11
12
  # Starts up the client.
12
13
  # Connects to the queue through DRb.
13
14
  # @return [void]
14
15
  def initialize
15
16
  server_uri = Belated::URI
16
- # @bank =
17
17
  DRb.start_service
18
+ self.bank = Thread::Queue.new
18
19
  self.queue = DRbObject.new_with_uri(server_uri)
19
20
  end
20
21
 
22
+ # Thread in charge of handling the bank queue.
23
+ # You probably want to memoize the client in order to avoid
24
+ # having many threads in the sleep state.
25
+ # @return [void]
26
+ def start_banker_thread
27
+ self.banker_thread = Thread.new do
28
+ loop do
29
+ job = bank.pop
30
+
31
+ perform(job)
32
+ end
33
+ end
34
+ end
35
+
21
36
  # The method that pushes the jobs to the queue.
37
+ # If there is no connection, it pushes the job to the bank.
22
38
  # @param job [Object] - The the job to be pushed.
23
39
  # @param at [Date] - The time at which the job should be executed.
24
- # @return [Object] - The job that was pushed.
25
- def perform(job, at: nil)
26
- queue.push(job, at: at)
40
+ # @param max_retries [Integer] - Times the job should be retried if it fails.
41
+ # @return [JobWrapper] - The job wrapper for the queue.
42
+ def perform(job, at: nil, max_retries: 5)
43
+ job_wrapper = if job.is_a?(JobWrapper)
44
+ job
45
+ else
46
+ JobWrapper.new(job: job, at: at, max_retries: max_retries)
47
+ end
48
+ pp queue.push(job_wrapper)
49
+ job_wrapper
50
+ rescue DRb::DRbConnError
51
+ bank.push(job_wrapper)
52
+ start_banker_thread if banker_thread.nil?
53
+ banker_thread.wakeup if banker_thread.status == 'sleep'
27
54
  end
28
55
  alias perform_belated perform
29
56
  alias perform_later perform
@@ -0,0 +1,44 @@
1
+ require 'securerandom'
2
+ require_relative 'logging'
3
+
4
+ class Belated
5
+ class JobWrapper
6
+ include Logging
7
+ attr_accessor :retries, :max_retries, :id, :job, :at
8
+
9
+ def initialize(job:, max_retries: 5, at: nil)
10
+ self.retries = 0
11
+ self.max_retries = max_retries
12
+ self.id = SecureRandom.uuid
13
+ self.job = job
14
+ self.at = at
15
+ end
16
+
17
+ # rubocop:disable Lint/RescueException
18
+ def perform
19
+ if job.respond_to?(:call)
20
+ job.call
21
+ else
22
+ job.perform
23
+ end
24
+ rescue Exception => e
25
+ case e.class
26
+ when Interrupt, SignalException
27
+ raise e
28
+ else
29
+ retry_job
30
+ "Error while executing job, #{e.inspect}. Retry #{retries} of #{max_retries}"
31
+ end
32
+ end
33
+ # rubocop:enable Lint/RescueException
34
+
35
+ def retry_job
36
+ self.retries += 1
37
+ return if retries > max_retries
38
+
39
+ self.at = Time.now.utc + (retries.next**4)
40
+ log "Job #{id} failed, retrying at #{at}"
41
+ Belated.job_list.push(self)
42
+ end
43
+ end
44
+ end
data/lib/belated/queue.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'belated/job'
4
4
  require 'belated/logging'
5
+ require 'belated/job_wrapper'
5
6
  class Belated
6
7
  class Queue
7
8
  include Logging
@@ -14,11 +15,11 @@ class Belated
14
15
  self.future_jobs = future_jobs
15
16
  end
16
17
 
17
- def push(job, at: nil)
18
- if at.nil?
18
+ def push(job)
19
+ if job.at.nil? || job.at <= Time.now.utc
19
20
  @queue.push(job)
20
21
  else
21
- @future_jobs << Job.new(job, at)
22
+ @future_jobs << job
22
23
  end
23
24
  end
24
25
 
@@ -45,7 +46,7 @@ class Belated
45
46
 
46
47
  jobs = YAML.load(File.binread(FILE_NAME))
47
48
  jobs.each do |job|
48
- if job.is_a?(Job)
49
+ if job.at && job.at > Time.now.utc
49
50
  future_jobs.push(job)
50
51
  else
51
52
  @queue.push(job)
@@ -73,7 +74,7 @@ class Belated
73
74
  private
74
75
 
75
76
  def proc_or_shutdown?(job)
76
- job.instance_of?(Proc) || job == :shutdown
77
+ job.job.instance_of?(Proc) || job == :shutdown
77
78
  end
78
79
  end
79
80
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Belated
4
- VERSION = '0.4.1'
4
+ VERSION = '0.5.0'
5
5
  end
@@ -18,18 +18,9 @@ class Belated
18
18
 
19
19
  break if job == :shutdown
20
20
 
21
- log call_job(job)
22
- end
23
- end
24
-
25
- def call_job(job)
26
- if job.respond_to?(:call)
27
- job.call
28
- else
21
+ log "Worker #{@number} got job: #{job.inspect}"
29
22
  job.perform
30
23
  end
31
- rescue StandardError => e
32
- e.inspect
33
24
  end
34
25
  end
35
26
  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.4.1
4
+ version: 0.5.0
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-05 00:00:00.000000000 Z
11
+ date: 2021-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: drb
@@ -81,6 +81,7 @@ files:
81
81
  - lib/belated.rb
82
82
  - lib/belated/client.rb
83
83
  - lib/belated/job.rb
84
+ - lib/belated/job_wrapper.rb
84
85
  - lib/belated/logging.rb
85
86
  - lib/belated/queue.rb
86
87
  - lib/belated/rails.rb