belated 0.3.1 → 0.4.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: 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
- --- []