async-job 0.9.2 → 0.10.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: eeb1fabbd3ce8f2806c1b3c3c4fc5c81c0f661e92d49f29a26ed5dac46960ab9
4
- data.tar.gz: 4187e3a56169deffce40590a9ebda6e075ec66f0450e8808cb5af6bc2ab7ce13
3
+ metadata.gz: 225fd8c2429fb1d8cdcee418e89c4c8ecb09da8fbda4b46331973a92ae186e75
4
+ data.tar.gz: 2c555bc6168739cf76188a536fd57e0b3b89d900d82ca2a661e03ada8e1078cf
5
5
  SHA512:
6
- metadata.gz: 5d1ef79240e7ee4928675166a2e4ebf608910b559401aefb7eba8088edb47fa50a08b7a784dadb4b23708627c53d1275b2559fb70ed1a2e13387680dde14fbe2
7
- data.tar.gz: 5bbdefdc4c753d464a30f548f8a1d91f350f54d92248732de0e9c4bca012bda8422c803cea0c4547b708804b354677d18bb5df76c29fc42744a537fcb1f32e94
6
+ metadata.gz: 42a545a3279caf7c5ed5de25593993119375693184452eaf9a1794dedd197a27a0859e91325dbe0831c0734646f05a4d915dc479dfe2fcc850d577d39ed32683
7
+ data.tar.gz: '0063657068e88a81be3f7978ae4963dba29ffa0536bd48db202906a0a28608dc2971fc269a04873c1552de043e90364baf9ef5986718139d2a4a839c695cda5c'
checksums.yaml.gz.sig CHANGED
Binary file
@@ -40,7 +40,7 @@ module Async
40
40
  @delegate = delegate
41
41
  end
42
42
 
43
- def build(delegate = @delegate, &block)
43
+ def build(delegate = @delegate)
44
44
  # We then wrap the delegate with the middleware in reverse order:
45
45
  @dequeue.reverse_each do |middleware|
46
46
  delegate = middleware.call(delegate)
@@ -53,10 +53,6 @@ module Async
53
53
  client = middleware.call(client)
54
54
  end
55
55
 
56
- if block_given?
57
- client = yield(client) || client
58
- end
59
-
60
56
  return Queue.new(client, server, @delegate)
61
57
  end
62
58
  end
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module Job
8
- VERSION = "0.9.2"
8
+ VERSION = "0.10.0"
9
9
  end
10
10
  end
data/readme.md CHANGED
@@ -10,10 +10,13 @@ Please see the [project documentation](https://socketry.github.io/async-job/) fo
10
10
 
11
11
  - [Getting Started](https://socketry.github.io/async-job/guides/getting-started/index) - This guide gives you an overview of the `async-job` gem and explains the core concepts.
12
12
 
13
- - [Redis Processor](https://socketry.github.io/async-job/guides/redis-processor/index) - This guide gives a brief overview of the implementation of the Redis queue.
13
+ - [Inline Queue](https://socketry.github.io/async-job/guides/inline-queue/index) - This guide explains how to use the Inline queue to execute background jobs.
14
+
15
+ - [Aggregate Queue](https://socketry.github.io/async-job/guides/aggregate-queue/index) - This guide explains how to use the Aggregate queue to reduce input latency and improve the performance of your application.
14
16
 
15
17
  ## See Also
16
18
 
19
+ - [async-job-processor-redis](https://github.com/socketry/async-job-processor-redis) - Redis processor for `async-job` (similar to Sidekiq).
17
20
  - [async-job-adapter-active\_job](https://github.com/socketry/async-job-adapter-active_job) - ActiveJob adapter for `async-job`.
18
21
 
19
22
  ## Contributing
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-job
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -38,7 +38,7 @@ cert_chain:
38
38
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
39
39
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
40
40
  -----END CERTIFICATE-----
41
- date: 2024-08-09 00:00:00.000000000 Z
41
+ date: 2024-08-14 00:00:00.000000000 Z
42
42
  dependencies:
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: async
@@ -54,20 +54,6 @@ dependencies:
54
54
  - - "~>"
55
55
  - !ruby/object:Gem::Version
56
56
  version: '2.9'
57
- - !ruby/object:Gem::Dependency
58
- name: async-redis
59
- requirement: !ruby/object:Gem::Requirement
60
- requirements:
61
- - - ">="
62
- - !ruby/object:Gem::Version
63
- version: '0'
64
- type: :runtime
65
- prerelease: false
66
- version_requirements: !ruby/object:Gem::Requirement
67
- requirements:
68
- - - ">="
69
- - !ruby/object:Gem::Version
70
- version: '0'
71
57
  description:
72
58
  email:
73
59
  executables: []
@@ -86,12 +72,6 @@ files:
86
72
  - lib/async/job/processor/delayed.rb
87
73
  - lib/async/job/processor/generic.rb
88
74
  - lib/async/job/processor/inline.rb
89
- - lib/async/job/processor/redis.rb
90
- - lib/async/job/processor/redis/delayed_jobs.rb
91
- - lib/async/job/processor/redis/job_store.rb
92
- - lib/async/job/processor/redis/processing_list.rb
93
- - lib/async/job/processor/redis/ready_list.rb
94
- - lib/async/job/processor/redis/server.rb
95
75
  - lib/async/job/queue.rb
96
76
  - lib/async/job/version.rb
97
77
  - license.md
@@ -120,5 +100,5 @@ requirements: []
120
100
  rubygems_version: 3.5.11
121
101
  signing_key:
122
102
  specification_version: 4
123
- summary: A asynchronous job queue for Ruby.
103
+ summary: An asynchronous job queue for Ruby.
124
104
  test_files: []
metadata.gz.sig CHANGED
Binary file
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
5
-
6
- module Async
7
- module Job
8
- module Processor
9
- module Redis
10
- class DelayedJobs
11
- ADD = <<~LUA
12
- redis.call('HSET', KEYS[1], ARGV[1], ARGV[2])
13
- redis.call('ZADD', KEYS[2], ARGV[3], ARGV[1])
14
- LUA
15
-
16
- MOVE = <<~LUA
17
- local jobs = redis.call('ZRANGEBYSCORE', KEYS[1], 0, ARGV[1])
18
- redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[1])
19
- if #jobs > 0 then
20
- redis.call('LPUSH', KEYS[2], unpack(jobs))
21
- end
22
- return #jobs
23
- LUA
24
-
25
- def initialize(client, key)
26
- @client = client
27
- @key = key
28
-
29
- @add = @client.script(:load, ADD)
30
- @move = @client.script(:load, MOVE)
31
- end
32
-
33
- def start(ready_list, resolution: 10, parent: Async::Task.current)
34
- parent.async do
35
- while true
36
- count = move(destination: ready_list.key)
37
-
38
- if count > 0
39
- Console.debug(self, "Moved #{count} delayed jobs to ready list.")
40
- end
41
-
42
- sleep(resolution)
43
- end
44
- end
45
- end
46
-
47
- attr :key
48
-
49
- def add(job, timestamp, job_store)
50
- id = SecureRandom.uuid
51
-
52
- @client.evalsha(@add, 2, job_store.key, @key, id, job, timestamp.to_f)
53
-
54
- return id
55
- end
56
-
57
- def move(destination:, now: Time.now.to_i)
58
- @client.evalsha(@move, 2, @key, destination, now)
59
- end
60
- end
61
- end
62
- end
63
- end
64
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
5
-
6
- module Async
7
- module Job
8
- module Processor
9
- module Redis
10
- class JobStore
11
- def initialize(client, key)
12
- @client = client
13
- @key = key
14
- end
15
-
16
- attr :key
17
-
18
- def get(id)
19
- @client.hget(@key, id)
20
- end
21
- end
22
- end
23
- end
24
- end
25
- end
@@ -1,107 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
5
-
6
- module Async
7
- module Job
8
- module Processor
9
- module Redis
10
- class ProcessingList
11
- REQUEUE = <<~LUA
12
- local cursor = "0"
13
- local count = 0
14
-
15
- repeat
16
- -- Scan through all known server id -> job id mappings and requeue any jobs that have been abandoned:
17
- local result = redis.call('SCAN', cursor, 'MATCH', KEYS[1]..':*:pending')
18
- cursor = result[1]
19
- for _, pending_key in pairs(result[2]) do
20
- -- Check if the server is still active:
21
- local server_key = KEYS[1]..":"..pending_key:match("([^:]+):pending")
22
- local state = redis.call('GET', server_key)
23
- if state == false then
24
- while true do
25
- -- Requeue any pending jobs:
26
- local result = redis.call('RPOPLPUSH', pending_key, KEYS[2])
27
-
28
- if result == false then
29
- -- Delete the pending list:
30
- redis.call('DEL', pending_key)
31
- break
32
- end
33
-
34
- count = count + 1
35
- end
36
- end
37
- end
38
- until cursor == "0"
39
-
40
- return count
41
- LUA
42
-
43
- RETRY = <<~LUA
44
- redis.call('LREM', KEYS[1], 1, ARGV[1])
45
- redis.call('LPUSH', KEYS[2], ARGV[1])
46
- LUA
47
-
48
- COMPLETE = <<~LUA
49
- redis.call('LREM', KEYS[1], 1, ARGV[1])
50
- redis.call('HDEL', KEYS[2], ARGV[1])
51
- LUA
52
-
53
- def initialize(client, key, id, ready_list, job_store)
54
- @client = client
55
- @key = key
56
- @id = id
57
-
58
- @ready_list = ready_list
59
- @job_store = job_store
60
-
61
- @pending_key = "#{@key}:#{@id}:pending"
62
- @heartbeat_key = "#{@key}:#{@id}"
63
-
64
- @requeue = @client.script(:load, REQUEUE)
65
- @retry = @client.script(:load, RETRY)
66
- @complete = @client.script(:load, COMPLETE)
67
- end
68
-
69
- attr :key
70
-
71
- def fetch
72
- @client.brpoplpush(@ready_list.key, @pending_key, 0)
73
- end
74
-
75
- def complete(id)
76
- @client.evalsha(@complete, 2, @pending_key, @job_store.key, id)
77
- end
78
-
79
- def retry(id)
80
- Console.warn(self, "Retrying job: #{id}")
81
- @client.evalsha(@retry, 2, @pending_key, @ready_list.key, id)
82
- end
83
-
84
- def start(delay: 5, factor: 2, parent: Async::Task.current)
85
- heartbeat_key = "#{@key}:#{@id}"
86
- start_time = Time.now.to_f
87
-
88
- parent.async do
89
- while true
90
- uptime = (Time.now.to_f - start_time).round(2)
91
- @client.set(heartbeat_key, JSON.dump(uptime: uptime), seconds: delay*factor)
92
-
93
- # Requeue any jobs that have been abandoned:
94
- count = @client.evalsha(@requeue, 2, @key, @ready_list.key)
95
- if count > 0
96
- Console.warn(self, "Requeued #{count} abandoned jobs.")
97
- end
98
-
99
- sleep(delay)
100
- end
101
- end
102
- end
103
- end
104
- end
105
- end
106
- end
107
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
5
-
6
- module Async
7
- module Job
8
- module Processor
9
- module Redis
10
- class ReadyList
11
- ADD = <<~LUA
12
- redis.call('HSET', KEYS[1], ARGV[1], ARGV[2])
13
- redis.call('LPUSH', KEYS[2], ARGV[1])
14
- LUA
15
-
16
- def initialize(client, key)
17
- @client = client
18
- @key = key
19
-
20
- @add = @client.script(:load, ADD)
21
- end
22
-
23
- attr :key
24
-
25
- def add(job, job_store)
26
- id = SecureRandom.uuid
27
-
28
- @client.evalsha(@add, 2, job_store.key, @key, id, job)
29
-
30
- return id
31
- end
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,109 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
5
-
6
- require_relative 'delayed_jobs'
7
- require_relative 'job_store'
8
- require_relative 'processing_list'
9
- require_relative 'ready_list'
10
- require_relative '../../coder'
11
- require_relative '../generic'
12
-
13
- require 'securerandom'
14
- require 'async/idler'
15
-
16
- module Async
17
- module Job
18
- module Processor
19
- module Redis
20
- class Server < Generic
21
- def initialize(delegate, client, prefix: 'async-job', coder: Coder::DEFAULT, resolution: 10, parent: nil)
22
- super(delegate)
23
-
24
- @id = SecureRandom.uuid
25
- @client = client
26
- @prefix = prefix
27
- @coder = coder
28
- @resolution = resolution
29
-
30
- @job_store = JobStore.new(@client, "#{@prefix}:jobs")
31
- @delayed_jobs = DelayedJobs.new(@client, "#{@prefix}:delayed")
32
- @ready_list = ReadyList.new(@client, "#{@prefix}:ready")
33
- @processing_list = ProcessingList.new(@client, "#{@prefix}:processing", @id, @ready_list, @job_store)
34
-
35
- @parent = parent || Async::Idler.new
36
- end
37
-
38
- def start!
39
- return false if @task
40
-
41
- @task = true
42
-
43
- @parent.async(transient: true, annotation: self.class.name) do |task|
44
- @task = task
45
-
46
- while true
47
- self.dequeue(task)
48
- end
49
- ensure
50
- @task = nil
51
- end
52
- end
53
-
54
- def start
55
- super
56
-
57
- # Start the delayed processor, which will move jobs to the ready processor when they are ready:
58
- @delayed_jobs.start(@ready_list, resolution: @resolution)
59
-
60
- # Start the processing processor, which will move jobs to the ready processor when they are abandoned:
61
- @processing_list.start
62
-
63
- self.start!
64
- end
65
-
66
- def stop
67
- @task&.stop
68
-
69
- super
70
- end
71
-
72
- def call(job)
73
- scheduled_at = Coder::Time(job["scheduled_at"])
74
-
75
- if scheduled_at
76
- @delayed_jobs.add(@coder.dump(job), scheduled_at, @job_store)
77
- else
78
- @ready_list.add(@coder.dump(job), @job_store)
79
- end
80
- end
81
-
82
- protected
83
-
84
- # Dequeue a job from the ready list and process it.
85
- #
86
- # If the job fails for any reason, it will be retried.
87
- #
88
- # If you do not desire this behavior, you should catch exceptions in the delegate.
89
- def dequeue(parent)
90
- _id = @processing_list.fetch
91
-
92
- parent.async do
93
- id = _id; _id = nil
94
-
95
- job = @coder.load(@job_store.get(id))
96
- @delegate.call(job)
97
- @processing_list.complete(id)
98
- rescue => error
99
- Console::Event::Failure.for(error).emit(self, "Job failed with error!", id: id)
100
- @processing_list.retry(id)
101
- end
102
- ensure
103
- @processing_list.retry(_id) if _id
104
- end
105
- end
106
- end
107
- end
108
- end
109
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
5
-
6
- require_relative 'redis/server'
7
- require 'async/redis/client'
8
-
9
- module Async
10
- module Job
11
- module Processor
12
- module Redis
13
- def self.new(delegate, endpoint: Async::Redis.local_endpoint, **options)
14
- client = Async::Redis::Client.new(endpoint)
15
- return Server.new(delegate, client, **options)
16
- end
17
- end
18
- end
19
- end
20
- end