ruby_job 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'json'
5
+ require 'time'
6
+ require 'bigdecimal/util'
7
+
8
+ module RubyJob
9
+ class Job < FibonacciHeap::Node
10
+ attr_reader :worker_class_name, :args, :start_at, :jobstore, :uuid
11
+
12
+ def initialize(
13
+ worker_class_name:,
14
+ args:,
15
+ start_at: Time.now,
16
+ uuid: nil,
17
+ jobstore: Job.send(:default_jobstore, worker_class_name)
18
+ )
19
+ @worker_class_name = worker_class_name
20
+ @args = args
21
+ @start_at = Time.at(start_at.to_f.round(3))
22
+ @uuid_id = uuid
23
+ @jobstore = jobstore
24
+ end
25
+
26
+ def perform
27
+ worker_class.perform(*@args)
28
+ end
29
+
30
+ def ==(other)
31
+ return false unless other.is_a? Job
32
+
33
+ @start_at == other.start_at &&
34
+ @uuid == other.uuid &&
35
+ @args == other.args &&
36
+ @worker_class_name == other.worker_class_name
37
+ end
38
+
39
+ def to_h
40
+ {
41
+ 'json_class' => self.class.name,
42
+ 'data' => {
43
+ 'worker_class_name' => @worker_class_name,
44
+ 'args_json' => JSON.dump(@args),
45
+ 'start_at' => @start_at.iso8601(9),
46
+ 'uuid' => @uuid
47
+ }
48
+ }
49
+ end
50
+
51
+ def to_json(*args)
52
+ to_h.to_json(*args)
53
+ end
54
+
55
+ def self.json_create(hash)
56
+ worker_class_name = hash['data']['worker_class_name']
57
+ args = JSON.parse(hash['data']['args_json'])
58
+ start_at = Time.iso8601(hash['data']['start_at'])
59
+ uuid = hash['data']['uuid']
60
+ new(worker_class_name: worker_class_name, args: args, start_at: start_at, uuid: uuid)
61
+ end
62
+
63
+ def enqueue
64
+ raise 'job has already been enqueued' if @uuid
65
+
66
+ @uuid = @jobstore.next_uuid
67
+ @jobstore.enqueue(self)
68
+ self
69
+ end
70
+
71
+ def dequeue
72
+ raise 'job was not queued' unless @uuid
73
+
74
+ @jobstore.dequeue(self)
75
+ @uuid = nil
76
+ self
77
+ end
78
+
79
+ private
80
+
81
+ def worker_class
82
+ @worker_class ||= @worker_class_name.split('::').reduce(Module, :const_get)
83
+ end
84
+
85
+ class << self
86
+ private
87
+
88
+ def default_jobstore(worker_class_name)
89
+ worker_class = worker_class_name.split('::').reduce(Module, :const_get)
90
+ class_with_jobstore_method = worker_class.respond_to?(:jobstore) ? worker_class : Worker
91
+ class_with_jobstore_method.jobstore
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJob
4
+ class JobProcessor
5
+ def initialize(jobstore)
6
+ @jobstore = jobstore
7
+ end
8
+
9
+ def run(**options)
10
+ loop do
11
+ job = @jobstore.set(**options).fetch
12
+ job ? job.perform : break
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJob
4
+ class JobStore
5
+ def initialize
6
+ @options = {
7
+ wait: true,
8
+ wait_delay: 0.5
9
+ }
10
+ end
11
+
12
+ def set(**options)
13
+ @options.merge!(**options)
14
+ self
15
+ end
16
+
17
+ def enqueue(_job)
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def dequeue(_job)
22
+ raise NotImplementedError
23
+ end
24
+
25
+ def pause_at(_time)
26
+ raise NotImplementedError
27
+ end
28
+
29
+ def fetch(*)
30
+ raise NotImplementedError
31
+ end
32
+
33
+ def size
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def next_uuid
38
+ raise NotImplementedError
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJob
4
+ class ThreadedServer
5
+ attr_reader :options
6
+
7
+ def initialize(num_threads:, jobstore:)
8
+ @num_threads = num_threads
9
+ @jobstore = jobstore
10
+ @options = { wait: true, wait_delay: 0.5 }
11
+ end
12
+
13
+ def set(**options)
14
+ @options.merge!(options)
15
+ self
16
+ end
17
+
18
+ def start
19
+ Thread.new do
20
+ @num_threads.times.map do
21
+ Thread.new do
22
+ JobProcessor.new(@jobstore).run(**@options)
23
+ end
24
+ end.each(&:join)
25
+ end
26
+ end
27
+
28
+ def halt_at(time)
29
+ @jobstore.pause_at(time)
30
+ self
31
+ end
32
+
33
+ def halt
34
+ halt_at(Time.now)
35
+ self
36
+ end
37
+
38
+ def resume
39
+ halt_at(nil)
40
+ self
41
+ end
42
+
43
+ def resume_until(time)
44
+ resume
45
+ halt_at(time)
46
+ self
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJob
4
+ VERSION = '0.1.1'
5
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJob
4
+ module Worker
5
+ class << self
6
+ def included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ attr_reader :jobstore
11
+
12
+ def jobstore=(jobstore)
13
+ raise ArgumentError, 'argument provided is not a JobStore' unless jobstore.is_a?(JobStore)
14
+
15
+ @jobstore = jobstore
16
+ end
17
+ end
18
+
19
+ def retry?(*)
20
+ false
21
+ end
22
+
23
+ private
24
+
25
+ def do_perform(*args)
26
+ @attempt ||= 1
27
+ perform(*args)
28
+ rescue StandardError => e
29
+ (do_retry, retry_delay) = retry?(attempt: @attempt, error: e)
30
+ raise unless do_retry
31
+
32
+ sleep(retry_delay || 0)
33
+ @attempt += 1
34
+ retry
35
+ end
36
+
37
+ module ClassMethods
38
+ def jobstore=(jobstore)
39
+ raise ArgumentError, 'argument provided is not a JobStore' unless jobstore.is_a?(JobStore)
40
+
41
+ @jobstore = jobstore
42
+ end
43
+
44
+ def jobstore
45
+ @jobstore || Worker.jobstore
46
+ end
47
+
48
+ def perform(*args)
49
+ worker = new
50
+ worker.send(:do_perform, *args)
51
+ end
52
+
53
+ def perform_async(*args)
54
+ Job.new(worker_class_name: name, args: args).enqueue
55
+ end
56
+
57
+ def perform_at(at, *args)
58
+ Job.new(worker_class_name: name, args: args, start_at: at).enqueue
59
+ end
60
+
61
+ def perform_in(in_ms, *args)
62
+ at = Time.now + in_ms.to_f / 1000
63
+ Job.new(worker_class_name: name, args: args, start_at: at).enqueue
64
+ end
65
+ end
66
+ end
67
+ end
data/lib/ruby_job.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_job/version'
4
+ require 'ruby_job/job_store'
5
+ require 'ruby_job/in_memory_job_store'
6
+ require 'ruby_job/job'
7
+ require 'ruby_job/worker'
8
+ require 'ruby_job/job_processor'
9
+ require 'ruby_job/threaded_server'
data/ruby_job.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'ruby_job/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'ruby_job'
9
+ spec.version = RubyJob::VERSION
10
+ spec.authors = ['Marco Imperatore']
11
+ spec.email = ['mimperatore@gmail.com']
12
+
13
+ spec.summary = <<~ENDOFSTRING
14
+ RubyJob is a framework for running jobs.
15
+ ENDOFSTRING
16
+ spec.description = <<~ENDOFSTRING
17
+ RubyJob is a framework for running jobs.
18
+ ENDOFSTRING
19
+ spec.homepage = 'https://mimperatore.github.io/ruby_job/'
20
+ spec.license = 'LGPL-3.0'
21
+
22
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
23
+ f.match(%r{^(test|spec|features)/})
24
+ end
25
+ spec.bindir = 'exe'
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.add_development_dependency 'bundler', '~> 2.1'
30
+ spec.add_development_dependency 'byebug', '~> 11.0'
31
+ spec.add_development_dependency 'codecov', '~> 0.1'
32
+ spec.add_development_dependency 'guard', '~> 2.16'
33
+ spec.add_development_dependency 'guard-rspec', '~> 4.7'
34
+ spec.add_development_dependency 'rake', '~> 13.0'
35
+ spec.add_development_dependency 'rspec', '~> 3.9'
36
+ spec.add_development_dependency 'rubocop', '~> 0.78'
37
+ spec.add_development_dependency 'timecop', '~> 0.9'
38
+ spec.add_dependency 'fibonacci_heap'
39
+ end
metadata ADDED
@@ -0,0 +1,212 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_job
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Marco Imperatore
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-01-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '11.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '11.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: codecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.16'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.16'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '4.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '4.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '13.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '13.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.9'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.9'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.78'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.78'
125
+ - !ruby/object:Gem::Dependency
126
+ name: timecop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.9'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.9'
139
+ - !ruby/object:Gem::Dependency
140
+ name: fibonacci_heap
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: 'RubyJob is a framework for running jobs.
154
+
155
+ '
156
+ email:
157
+ - mimperatore@gmail.com
158
+ executables: []
159
+ extensions: []
160
+ extra_rdoc_files: []
161
+ files:
162
+ - ".gitignore"
163
+ - ".rspec"
164
+ - ".rubocop.yml"
165
+ - ".ruby-version"
166
+ - ".travis.yml"
167
+ - CODE_OF_CONDUCT.md
168
+ - COPYING
169
+ - COPYING.LESSER
170
+ - Gemfile
171
+ - Guardfile
172
+ - LICENSE
173
+ - LICENSE.txt
174
+ - README.md
175
+ - Rakefile
176
+ - _config.yml
177
+ - bin/console
178
+ - bin/setup
179
+ - codecov.yml
180
+ - lib/ruby_job.rb
181
+ - lib/ruby_job/in_memory_job_store.rb
182
+ - lib/ruby_job/job.rb
183
+ - lib/ruby_job/job_processor.rb
184
+ - lib/ruby_job/job_store.rb
185
+ - lib/ruby_job/threaded_server.rb
186
+ - lib/ruby_job/version.rb
187
+ - lib/ruby_job/worker.rb
188
+ - ruby_job.gemspec
189
+ homepage: https://mimperatore.github.io/ruby_job/
190
+ licenses:
191
+ - LGPL-3.0
192
+ metadata: {}
193
+ post_install_message:
194
+ rdoc_options: []
195
+ require_paths:
196
+ - lib
197
+ required_ruby_version: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ required_rubygems_version: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - ">="
205
+ - !ruby/object:Gem::Version
206
+ version: '0'
207
+ requirements: []
208
+ rubygems_version: 3.1.2
209
+ signing_key:
210
+ specification_version: 4
211
+ summary: RubyJob is a framework for running jobs.
212
+ test_files: []