belated 0.3.1 → 0.4.1

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: 99491dbba348e9e991f00436b70faa69d7ee3646961172014499a09276e9cb1e
4
- data.tar.gz: ebb40194db4cf9893edb0b06f6b940a4076e5ec328ea00ba2ab40bd839ebe7e6
3
+ metadata.gz: ed06672798a42b07c69c9dc84f32d1ba12a58a7b95028f0c4a2f5d1a5f0b04c4
4
+ data.tar.gz: 0ecf13b8abf076c5fbe8a0037017840e55f32c674f4a40092a6e35845503f4e9
5
5
  SHA512:
6
- metadata.gz: 0b40900814f6cae2396025fd6ebda46b6fedc11ae7bca92ffcd534cdd6a782e0cca581a264c6f0b3bac05c1c925de8d32590270370445dab62a8c50f9749df90
7
- data.tar.gz: 397925608f6083f3722e69661e7731d9f56557ee44d8b79e811b655a7d25dd2f58755f607ad00137296607dd6f1f3fe88045237152134769bb4b5b09aea5cb73
6
+ metadata.gz: 1e16e639db3c05d9112a4b31232d0cb78dbe6fe93ed05cd33f1d9a7856ded7c40319c145c20cddf1dd5a8bafb2894d8f96d46fc423a6718180e4d5e4584fdc41
7
+ data.tar.gz: 86793a533285a0ae5761b3eb11d4f16fe44793cc83d2a24c6f7aebae6a4d27ff7795b3ef2e69ce7e2b12f5c86fb9e108d5252f2aa863c645262f880f0ad56cf7
data/.gitignore CHANGED
@@ -12,4 +12,6 @@
12
12
  .byebug_history
13
13
  /dummy/tmp/cache/
14
14
  /dummy/log/
15
- belated_dump
15
+ /dummy/log/test.log
16
+ belated_dump
17
+ stackprof*
data/.rubocop.yml CHANGED
@@ -41,5 +41,8 @@ Metrics/BlockLength:
41
41
  Metrics/ClassLength:
42
42
  Max: 200
43
43
 
44
+ Metrics/MethodLength:
45
+ Max: 15
46
+
44
47
  Security/YAMLLoad:
45
48
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,6 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.1] - 2021-08-05
3
4
 
5
+ - Now handles saving future jobs too! So if you have a job enqueued for tomorrow, and restart Belated, it should still be enqueued.
6
+
7
+ ## [0.4.0] - 2021-08-03
8
+
9
+ - Now you can enqueue jobs to be done at a later time. Just pass an `at:` keyword param to the client.
10
+ - Does not save the jobs when you quit.
11
+
12
+ ## [0.3.3] - 2021-08-01
13
+
14
+ - Shutdown trapped signal thread, make sure :shutdown is not recorded as a job.
15
+
16
+ ## [0.3.2] - 2021-07-31
17
+
18
+ - Trap INT and TERM, so now the shutdown is a little bit more graceful.
4
19
 
5
20
  ## [0.3.1] - 2021-07-29
6
21
 
data/Gemfile CHANGED
@@ -11,5 +11,8 @@ gem 'rspec', '~> 3.0'
11
11
 
12
12
  gem 'rubocop', '~> 1.7'
13
13
 
14
+ gem 'database_cleaner-active_record'
14
15
  gem 'rails', '>= 6.1.3'
16
+ gem 'rspec-rails'
15
17
  gem 'sqlite3'
18
+ gem 'stackprof'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- belated (0.3.1)
4
+ belated (0.4.1)
5
5
  drb
6
6
  dry-configurable
7
7
 
@@ -72,6 +72,10 @@ GEM
72
72
  byebug (11.1.3)
73
73
  concurrent-ruby (1.1.9)
74
74
  crass (1.0.6)
75
+ database_cleaner-active_record (2.0.1)
76
+ activerecord (>= 5.a)
77
+ database_cleaner-core (~> 2.0.0)
78
+ database_cleaner-core (2.0.1)
75
79
  diff-lcs (1.4.4)
76
80
  drb (2.0.4)
77
81
  dry-configurable (0.12.1)
@@ -145,6 +149,14 @@ GEM
145
149
  rspec-mocks (3.10.2)
146
150
  diff-lcs (>= 1.2.0, < 2.0)
147
151
  rspec-support (~> 3.10.0)
152
+ rspec-rails (5.0.1)
153
+ actionpack (>= 5.2)
154
+ activesupport (>= 5.2)
155
+ railties (>= 5.2)
156
+ rspec-core (~> 3.10)
157
+ rspec-expectations (~> 3.10)
158
+ rspec-mocks (~> 3.10)
159
+ rspec-support (~> 3.10)
148
160
  rspec-support (3.10.2)
149
161
  rubocop (1.18.3)
150
162
  parallel (~> 1.10)
@@ -166,6 +178,7 @@ GEM
166
178
  activesupport (>= 4.0)
167
179
  sprockets (>= 3.0.0)
168
180
  sqlite3 (1.4.2)
181
+ stackprof (0.2.16)
169
182
  thor (1.1.0)
170
183
  tzinfo (2.0.4)
171
184
  concurrent-ruby (~> 1.0)
@@ -181,11 +194,14 @@ PLATFORMS
181
194
  DEPENDENCIES
182
195
  belated!
183
196
  byebug
197
+ database_cleaner-active_record
184
198
  rails (>= 6.1.3)
185
199
  rake (~> 13.0)
186
200
  rspec (~> 3.0)
201
+ rspec-rails
187
202
  rubocop (~> 1.7)
188
203
  sqlite3
204
+ stackprof
189
205
 
190
206
  BUNDLED WITH
191
207
  2.2.17
data/README.md CHANGED
@@ -8,21 +8,28 @@ Note that Belated used to be called HardWorker. That name was already in use in
8
8
 
9
9
  It uses dRuby to do the communication! Which is absolute great. No need for Redis or PostgreSQL, just Ruby standard libraries.
10
10
 
11
+ Can be used with or without Rails.
12
+
11
13
  TODO LIST:
12
14
 
13
- - ~~Marshal the job queue into a file so you don't lose all progress~~
14
- (Ended up using YAML)
15
15
  - Catch SIGTERM and friends
16
- - ~~Support Rails~~ (Supported!)
17
- - ~~Parse options from command line, eg. `--workers 10`~~(Done!)
16
+ - Now supports it, partly.
18
17
  - Don't crash on errors (Partially done)
19
- - ~~Add a logger~~
20
- - Make it possible to schedule jobs
18
+ - Have multiple queues?
21
19
  - Maybe support ActiveJob?
22
20
  - Have a web UI
23
21
  - Do some performance testing
24
22
  - Add a section telling people to use Sidekiq if they can
25
23
 
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
+
26
33
  ## Installation
27
34
 
28
35
  Add this line to your application's Gemfile:
@@ -94,6 +101,12 @@ client.perform_belated(job)
94
101
 
95
102
  If you want to pass a job to Belated.
96
103
 
104
+ If you don't want the job to run right away, you can also pass it a keyword param `at:` like so:
105
+
106
+ ```ruby
107
+ client.perform_belated(job, Time.now + 1.month)
108
+ ```
109
+
97
110
  # Settings
98
111
 
99
112
  Configuring Belated:
@@ -109,15 +122,15 @@ end
109
122
 
110
123
  From command line:
111
124
 
112
- $ bundle exec belated --rails=true
125
+ $ bundle exec belated --rails=true
113
126
 
114
127
  Use Rails or not.
115
128
 
116
- $ bundle exec belated --rails_path=/my_rails_project
129
+ $ bundle exec belated --rails_path=/my_rails_project
117
130
 
118
131
  Path to Rails project.
119
132
 
120
- $ bundle exec belated --workers=10
133
+ $ bundle exec belated --workers=10
121
134
 
122
135
  Number of workers.
123
136
 
data/lib/belated.rb CHANGED
@@ -9,6 +9,7 @@ require 'singleton'
9
9
  require 'dry-configurable'
10
10
  require 'belated/client'
11
11
  require 'logger'
12
+ require 'belated/queue'
12
13
 
13
14
  # Belated is a pure Ruby job backend.
14
15
  # It has limited functionality, as it only accepts
@@ -21,8 +22,7 @@ class Belated
21
22
  include Logging
22
23
  include Singleton unless $TESTING
23
24
  URI = 'druby://localhost:8788'
24
- FILE_NAME = 'belated_dump'
25
- @@queue = Queue.new
25
+ @@queue = Belated::Queue.new
26
26
 
27
27
  setting :rails, true
28
28
  setting :rails_path, '.'
@@ -36,17 +36,17 @@ class Belated
36
36
  # Aliased for testing purposes.
37
37
  # This is only run from the bin file.
38
38
  def start
39
- boot_app
40
- load_jobs
39
+ boot_app && @@queue.load_jobs
41
40
  @worker_list = []
42
- Belated.config.workers.times do |_i|
43
- @worker_list << Thread.new { Worker.new }
41
+ Belated.config.workers.times do |i|
42
+ @worker_list << Thread.new { Worker.new(number: i.next) }
44
43
  end
45
44
  return unless Belated.config.connect
46
45
 
47
46
  connect!
48
47
  banner_and_info
49
- DRb.thread.join
48
+ trap_signals
49
+ enqueue_future_jobs
50
50
  end
51
51
  alias initialize start
52
52
 
@@ -55,10 +55,19 @@ class Belated
55
55
  DRb.start_service(URI, @@queue, verbose: true)
56
56
  rescue DRb::DRbConnError, Errno::EADDRINUSE
57
57
  Belated.logger.error 'Could not connect to DRb server.'
58
- uri = "druby://localhost:#{Array.new(4) { rand(10) }.join}"
59
- self.class.send(:remove_const, 'URI')
60
- self.class.const_set('URI', uri)
61
- retry
58
+ end
59
+
60
+ def trap_signals
61
+ %w[INT TERM].each do |signal|
62
+ Signal.trap(signal) do
63
+ @worker_list.length.times do
64
+ @@queue.push(:shutdown)
65
+ end
66
+ Thread.new { stop_workers }
67
+ sleep 0.1 until @@queue.empty? || $TESTING
68
+ exit
69
+ end
70
+ end
62
71
  end
63
72
 
64
73
  def boot_app
@@ -74,33 +83,31 @@ class Belated
74
83
  Belated.config.rails
75
84
  end
76
85
 
77
- def load_jobs
78
- log "reloading... if file exists #{File.exist?(Belated::FILE_NAME)}"
79
- return unless File.exist?(Belated::FILE_NAME)
80
-
81
- jobs = YAML.load(File.binread(FILE_NAME))
82
- jobs.each do |job|
83
- @@queue.push(job)
86
+ def enqueue_future_jobs
87
+ log 'starting future jobs thread'
88
+ loop do
89
+ @@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])
93
+ end
94
+ end
95
+ sleep 0.01
84
96
  end
85
- File.delete(Belated::FILE_NAME)
86
97
  end
87
98
 
88
99
  def reload
89
100
  log 'reloading...'
90
- load_jobs
101
+ @@queue.load_jobs
91
102
  end
92
103
 
93
104
  def stop_workers
94
105
  @worker_list&.each do |worker|
106
+ sleep 0.1 if worker.alive?
95
107
  Thread.kill(worker)
96
108
  end
97
- class_array = []
98
- @@queue.size.times do |_i|
99
- next if (klass = @@queue.pop).instance_of?(Proc)
100
-
101
- class_array << klass
102
- end
103
- pp File.open(FILE_NAME, 'wb') { |f| f.write(YAML.dump(class_array)) }
109
+ @@queue.save_jobs
110
+ exit unless $TESTING
104
111
  end
105
112
 
106
113
  def banner
@@ -1,7 +1,16 @@
1
1
  class Belated
2
+ # The client class is responsible for managing the connection to the
3
+ # DRb server.
4
+ # You can enqueue jobs to be processed by the server.
5
+ # Example:
6
+ # client = Belated::Client.new
7
+ # client.enqueue(JubJub.new, at: Time.now + 5.seconds)
2
8
  class Client
3
9
  attr_accessor :queue
4
10
 
11
+ # Starts up the client.
12
+ # Connects to the queue through DRb.
13
+ # @return [void]
5
14
  def initialize
6
15
  server_uri = Belated::URI
7
16
  # @bank =
@@ -9,8 +18,12 @@ class Belated
9
18
  self.queue = DRbObject.new_with_uri(server_uri)
10
19
  end
11
20
 
12
- def perform(job)
13
- queue.push(job)
21
+ # The method that pushes the jobs to the queue.
22
+ # @param job [Object] - The the job to be pushed.
23
+ # @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)
14
27
  end
15
28
  alias perform_belated perform
16
29
  alias perform_later perform
@@ -0,0 +1 @@
1
+ Job = Struct.new(:klass, :at)
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'belated/job'
4
+ require 'belated/logging'
5
+ class Belated
6
+ class Queue
7
+ include Logging
8
+ attr_accessor :future_jobs
9
+
10
+ FILE_NAME = 'belated_dump'
11
+
12
+ def initialize(queue: Thread::Queue.new, future_jobs: [])
13
+ @queue = queue
14
+ self.future_jobs = future_jobs
15
+ end
16
+
17
+ def push(job, at: nil)
18
+ if at.nil?
19
+ @queue.push(job)
20
+ else
21
+ @future_jobs << Job.new(job, at)
22
+ end
23
+ end
24
+
25
+ def pop
26
+ @queue.pop
27
+ end
28
+
29
+ def clear
30
+ @queue.clear
31
+ self.future_jobs = []
32
+ end
33
+
34
+ def empty?
35
+ @queue.empty?
36
+ end
37
+
38
+ def length
39
+ @queue.length
40
+ end
41
+
42
+ def load_jobs
43
+ log "reloading... if file exists #{File.exist?(FILE_NAME)}"
44
+ return unless File.exist?(FILE_NAME)
45
+
46
+ jobs = YAML.load(File.binread(FILE_NAME))
47
+ jobs.each do |job|
48
+ if job.is_a?(Job)
49
+ future_jobs.push(job)
50
+ else
51
+ @queue.push(job)
52
+ end
53
+ end
54
+ File.delete(FILE_NAME)
55
+ end
56
+
57
+ def save_jobs
58
+ class_array = []
59
+ @queue.length.times do |_i|
60
+ unless proc_or_shutdown?(klass = @queue.pop)
61
+ class_array << klass
62
+ end
63
+ end
64
+ future_jobs.each do |_job|
65
+ unless proc_or_shutdown?(klass = future_jobs.pop)
66
+ class_array << klass
67
+ end
68
+ end
69
+
70
+ pp File.open(FILE_NAME, 'wb') { |f| f.write(YAML.dump(class_array)) }
71
+ end
72
+
73
+ private
74
+
75
+ def proc_or_shutdown?(job)
76
+ job.instance_of?(Proc) || job == :shutdown
77
+ end
78
+ end
79
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Belated
4
- VERSION = '0.3.1'
4
+ VERSION = '0.4.1'
5
5
  end
@@ -1,21 +1,24 @@
1
1
  require_relative 'logging'
2
2
  class Belated
3
3
  # The worker class that actually gets the jobs from the queue
4
- # and calls them. Expects the jobs to be procs.
4
+ # and calls them. Expects the jobs to be procs or
5
+ # classes that have a perform method.
5
6
  class Worker
6
7
  include Logging
7
8
 
8
- def initialize
9
+ def initialize(number: 1)
10
+ @number = number
9
11
  start_working
10
12
  end
11
13
 
12
14
  def start_working
13
15
  loop do
14
- job = Belated.fetch_job
15
- next unless job
16
+ log "Worker #{@number} fetching jobs!"
17
+ next unless (job = Belated.fetch_job)
18
+
19
+ break if job == :shutdown
16
20
 
17
21
  log call_job(job)
18
- log 'fetching jobs...'
19
22
  end
20
23
  end
21
24
 
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.3.1
4
+ version: 0.4.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-07-29 00:00:00.000000000 Z
11
+ date: 2021-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: drb
@@ -78,10 +78,11 @@ files:
78
78
  - bin/bundle
79
79
  - bin/console
80
80
  - bin/setup
81
- - hard_worker_dump
82
81
  - lib/belated.rb
83
82
  - lib/belated/client.rb
83
+ - lib/belated/job.rb
84
84
  - lib/belated/logging.rb
85
+ - lib/belated/queue.rb
85
86
  - lib/belated/rails.rb
86
87
  - lib/belated/version.rb
87
88
  - lib/belated/worker.rb
data/hard_worker_dump DELETED
@@ -1 +0,0 @@
1
- --- []