belated 0.4.1 → 0.5.0

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: 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