litejob 0.1.0 → 0.2.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: f0cd6ee9a1f3896b0aa44f935258c92d3326661ffb09c42c143095fc60c8ea08
4
- data.tar.gz: 302ea0cbf170c7c07c4384983bb5801af794f2d185db77064ff1b855a3693c95
3
+ metadata.gz: 9235b916ceb5620f30e307e23adb1c3baf256625a3d3cedde1c2851faa9ed490
4
+ data.tar.gz: 553500fbb22d4ef729a6059277cb56738c87b671e4009ac9c3b7174293f7bfb8
5
5
  SHA512:
6
- metadata.gz: 8f3d9837dd1ef417645e6dd46e587a23565a0e0ac7e900410b44b009b89d4c8ecbe1b058aad6b5782df550992712ab25fde799b9c249bf153dc0c7beaa7016aa
7
- data.tar.gz: 2f9647c30eb7d8af132df8baf0266f409ed9523f1ec848a161919be3de7cec76de0b14003661b140c1b619f229c87df020d44fde6dae93e1db8fae4d38f81ada
6
+ metadata.gz: 86125548df029a4a0035314ff34f3a429394079d56b88075e7b05ec72a4975a9b7ee32ed130bef939cfe4edd6a6cbc02b46458e011df847fd942efbbdbd36562
7
+ data.tar.gz: 50f38790e97d1b19462a5926bd2a92fbcbc9485ccadf2c89a9c1f1effc4ccd566650bea57400793d3d11aec0390c647f81e04d2ead1963aa0889f9522918654a
data/.standard.yml CHANGED
@@ -1,3 +1,6 @@
1
1
  # For available configuration options, see:
2
2
  # https://github.com/testdouble/standard
3
- ruby_version: 2.6
3
+ ruby_version: 3.2
4
+ ignore:
5
+ - 'test/**/*':
6
+ - Style/GlobalVars
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2023-08-13
4
+
5
+ - Fix some bugs with Litejob::Server
6
+
7
+ ## [0.2.0] - 2023-08-13
8
+
9
+ - Initial release of usable code
10
+
3
11
  ## [0.1.0] - 2023-08-09
4
12
 
5
13
  - Initial release
data/Gemfile.lock CHANGED
@@ -1,15 +1,24 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- litejob (0.1.0)
4
+ litejob (0.2.1)
5
+ litequeue (>= 0.2.0)
6
+ litescheduler (>= 0.2.1)
5
7
 
6
8
  GEM
7
9
  remote: https://rubygems.org/
8
10
  specs:
9
11
  ast (2.4.2)
12
+ docile (1.4.0)
10
13
  json (2.6.3)
11
14
  language_server-protocol (3.17.0.3)
12
15
  lint_roller (1.1.0)
16
+ litedb (0.2.1)
17
+ litescheduler (>= 0.2.0)
18
+ sqlite3 (>= 1.5.0)
19
+ litequeue (0.2.0)
20
+ litedb (>= 0.2.1)
21
+ litescheduler (0.2.1)
13
22
  minitest (5.19.0)
14
23
  parallel (1.23.0)
15
24
  parser (3.2.2.3)
@@ -36,6 +45,14 @@ GEM
36
45
  rubocop (>= 1.7.0, < 2.0)
37
46
  rubocop-ast (>= 0.4.0)
38
47
  ruby-progressbar (1.13.0)
48
+ simplecov (0.22.0)
49
+ docile (~> 1.1)
50
+ simplecov-html (~> 0.11)
51
+ simplecov_json_formatter (~> 0.1)
52
+ simplecov-html (0.12.3)
53
+ simplecov_json_formatter (0.1.4)
54
+ sqlite3 (1.6.3-arm64-darwin)
55
+ sqlite3 (1.6.3-x86_64-linux)
39
56
  standard (1.30.1)
40
57
  language_server-protocol (~> 3.17.0.2)
41
58
  lint_roller (~> 1.0)
@@ -52,11 +69,13 @@ GEM
52
69
 
53
70
  PLATFORMS
54
71
  arm64-darwin-21
72
+ x86_64-linux
55
73
 
56
74
  DEPENDENCIES
57
75
  litejob!
58
76
  minitest (~> 5.0)
59
77
  rake (~> 13.0)
78
+ simplecov
60
79
  standard (~> 1.3)
61
80
 
62
81
  BUNDLED WITH
data/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Litejob
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/litejob.svg)](https://rubygems.org/gems/litejob)
4
+ [![Gem Downloads](https://img.shields.io/gem/dt/litejob)](https://rubygems.org/gems/litejob)
5
+ ![Tests](https://github.com/litestack-ruby/litejob/actions/workflows/main.yml/badge.svg)
6
+ ![Coverage](https://img.shields.io/badge/code_coverage-100%25-brightgreen)
7
+
3
8
  TODO: Delete this and the text below, and describe your gem
4
9
 
5
10
  Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/litejob`. To experiment with that code, run `bin/console` for an interactive prompt.
data/Rakefile CHANGED
@@ -9,6 +9,34 @@ Rake::TestTask.new(:test) do |t|
9
9
  t.test_files = FileList["test/**/test_*.rb"]
10
10
  end
11
11
 
12
+ desc "Update the README code coverage badge"
13
+ task :update_readme_coverage_badge do
14
+ require "json"
15
+
16
+ next unless File.exist?("coverage/.last_run.json")
17
+
18
+ last_run_coverage = JSON.load_file("coverage/.last_run.json")
19
+ line_coverage = last_run_coverage.dig("result", "line")
20
+ branch_coverage = last_run_coverage.dig("result", "branch")
21
+ average_coverage = [(branch_coverage * 1), (line_coverage * 1.5)].sum.fdiv(2.5).round
22
+ badge_color = if average_coverage >= 75
23
+ :brightgreen
24
+ else
25
+ :red
26
+ end
27
+
28
+ coverage_badge_re = /!\[Coverage\]\(https:\/\/img.shields.io\/badge\/code_coverage-(.*?)\)/
29
+ last_run_coverage_badge = "![Coverage](https://img.shields.io/badge/code_coverage-#{average_coverage}%25-#{badge_color})"
30
+
31
+ new_readme = File.read("README.md").gsub(coverage_badge_re, last_run_coverage_badge)
32
+
33
+ File.write("README.md", new_readme)
34
+
35
+ puts "Updated README code coverage badge to show #{average_coverage}% coverage."
36
+ end
37
+
38
+ task cov: %i[test update_readme_coverage_badge]
39
+
12
40
  require "standard/rake"
13
41
 
14
42
  task default: %i[test standard]
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "litequeue"
5
+
6
+ module Litejob
7
+ # Litejob::Client is responsible for pushing job payloads to the SQLite queue.
8
+ class Client
9
+ def initialize
10
+ @queue = Litequeue.instance
11
+ end
12
+
13
+ def push(jobclass, params, options = {})
14
+ delay = options[:delay] || 0
15
+ attempts = options[:attempts] || 5
16
+ queue = options[:queue]
17
+ payload = JSON.dump({class: jobclass, params: params, attempts: attempts, queue: queue})
18
+ atomic_push(payload, delay, queue)
19
+ end
20
+
21
+ def delete(id)
22
+ payload = @queue.delete(id)
23
+ JSON.parse(payload)
24
+ end
25
+
26
+ private
27
+
28
+ def atomic_push(payload, delay, queue)
29
+ retryable = true
30
+ begin
31
+ @queue.push(payload, queue: queue, delay: delay)
32
+ rescue => exception
33
+ # Retry once retryable exceptions
34
+ # https://github.com/sparklemotion/sqlite3-ruby/blob/master/lib/sqlite3/errors.rb
35
+ if retryable && exception.is_a?(SQLite3::BusyException)
36
+ retryable = false
37
+ retry
38
+ else
39
+ raise exception
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "litequeue"
5
+
6
+ module Litejob
7
+ # Litejob::Processor is responsible for processing job payloads
8
+ class Processor
9
+ def initialize(payload)
10
+ @payload = payload
11
+ @queue = Litequeue.instance
12
+ end
13
+
14
+ def repush(id, job, delay = 0, queue = nil)
15
+ @queue.repush(id, JSON.dump(job), queue: queue, delay: delay)
16
+ end
17
+
18
+ def process!
19
+ id, serialized_job = @payload
20
+ job_hash = JSON.parse(serialized_job)
21
+ klass = Object.const_get(job_hash["class"])
22
+ instance = klass.new
23
+
24
+ begin
25
+ instance.perform(*job_hash["params"])
26
+ rescue
27
+ if job_hash["retries_left"] == 0
28
+ repush(id, job_hash, 0, "_dead")
29
+ else
30
+ job_hash["retries_left"] ||= job_hash["attempts"]
31
+ job_hash["retries_left"] -= 1
32
+ retry_delay = (job_hash["attempts"] - job_hash["retries_left"]) * 0.1
33
+ repush(id, job_hash, retry_delay, job_hash["queue"])
34
+ end
35
+ end
36
+ rescue => exception # standard:disable Lint/UselessRescue
37
+ # this is an error in the extraction of job info, retrying here will not be useful
38
+ raise exception
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "litequeue"
4
+ require "litescheduler"
5
+ require_relative "processor"
6
+
7
+ module Litejob
8
+ # Litejob::Server is responsible for popping job payloads from the SQLite queue.
9
+ # :nocov:
10
+ class Server
11
+ # TODO: make queues use [["default", 1]]
12
+ # TODO: make queue priorities optional
13
+ def initialize(queues)
14
+ @queue = Litequeue.instance
15
+ @scheduler = Litescheduler.instance
16
+ @queues = queues
17
+ # group and order queues according to their priority
18
+ @prioritized_queues = queues.each_with_object({}) do |(name, priority, spawns), memo|
19
+ memo[priority] ||= []
20
+ memo[priority] << [name, spawns == "spawn"]
21
+ end.sort_by do |priority, _|
22
+ -priority
23
+ end
24
+ @running = true
25
+ @sleep_intervals = [0.001, 0.005, 0.025, 0.125, 0.625, 1.0, 2.0]
26
+ run!
27
+ end
28
+
29
+ def pop(queue)
30
+ result = @queue.pop(queue: queue)
31
+
32
+ return result unless result.is_a?(Array)
33
+ return false if result.empty?
34
+
35
+ result
36
+ end
37
+
38
+ def run!
39
+ @scheduler.spawn do
40
+ worker_sleep_index = 0
41
+ while @running
42
+ processed = 0
43
+ @prioritized_queues.each do |priority, queues|
44
+ queues.each do |queue, spawns|
45
+ batched = 0
46
+ while (batched < priority) && (payload = pop(queue))
47
+ batched += 1
48
+ processed += 1
49
+
50
+ processor = Processor.new(payload)
51
+ processor.process!
52
+
53
+ # give other contexts a chance to run here
54
+ @scheduler.switch
55
+ end
56
+ end
57
+
58
+ if processed == 0
59
+ sleep @sleep_intervals[worker_sleep_index]
60
+ worker_sleep_index += 1 if worker_sleep_index < @sleep_intervals.length - 1
61
+ else
62
+ worker_sleep_index = 0 # reset the index
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ # :nocov:
70
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Litejob
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/litejob.rb CHANGED
@@ -1,8 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "litejob/version"
4
+ require_relative "litejob/client"
5
+ require_relative "litejob/server"
4
6
 
7
+ # Litejob is responsible for providing an interface to job classes
5
8
  module Litejob
6
- class Error < StandardError; end
7
- # Your code goes here...
9
+ def self.included(klass)
10
+ klass.extend(ClassMethods)
11
+ end
12
+
13
+ module ClassMethods
14
+ def perform_async(*params)
15
+ @litejob_options ||= {}
16
+ client.push(name, params, @litejob_options.merge(delay: 0, queue: queue_name))
17
+ end
18
+
19
+ def perform_at(time, *params)
20
+ @litejob_options ||= {}
21
+ delay = time.to_i - Time.now.to_i
22
+ client.push(name, params, @litejob_options.merge(delay: delay, queue: queue_name))
23
+ end
24
+
25
+ def perform_in(delay, *params)
26
+ @litejob_options ||= {}
27
+ client.push(name, params, @litejob_options.merge(delay: delay, queue: queue_name))
28
+ end
29
+ alias_method :perform_after, :perform_in
30
+
31
+ def delete(id)
32
+ client.delete(id)
33
+ end
34
+
35
+ def queue_as(queue_name)
36
+ @queue_name = queue_name.to_s
37
+ end
38
+
39
+ def litejob_options(options)
40
+ @litejob_options = options
41
+ end
42
+
43
+ private
44
+
45
+ def queue_name
46
+ @queue_name || "default"
47
+ end
48
+
49
+ def client
50
+ @client ||= Client.new
51
+ end
52
+ end
8
53
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: litejob
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mohamed Hassan
@@ -9,8 +9,50 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-08-09 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2023-08-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: litescheduler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 0.2.1
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: 0.2.1
28
+ - !ruby/object:Gem::Dependency
29
+ name: litequeue
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 0.2.0
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 0.2.0
42
+ - !ruby/object:Gem::Dependency
43
+ name: simplecov
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
14
56
  description:
15
57
  email:
16
58
  - oldmoe@gmail.com
@@ -28,6 +70,9 @@ files:
28
70
  - README.md
29
71
  - Rakefile
30
72
  - lib/litejob.rb
73
+ - lib/litejob/client.rb
74
+ - lib/litejob/processor.rb
75
+ - lib/litejob/server.rb
31
76
  - lib/litejob/version.rb
32
77
  - sig/litejob.rbs
33
78
  homepage: https://github.com/litestack-ruby/litejob