async-job 0.3.0 → 0.4.0

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: 44a092dcad8310e5301d4c273e08c21a5eb046b530ff27cce0320fd56ccd3f8f
4
- data.tar.gz: 11c40eb020b847b46a933d8ff2b1f27bb015edf781e4d4ca3a4579269140a8cb
3
+ metadata.gz: 1e330cf8c0430796e25264e8c892f70423f1fa0ffbc645a19198774f51ef4893
4
+ data.tar.gz: 3c1ac69a6bde193876717c65dace5e97803b66ab9471d5baeffc33bb1762701a
5
5
  SHA512:
6
- metadata.gz: 69d052fe00161a40300410b95aea22dde2a4b6eb41bc320d915e72327324bad175b0c1ca1dd4953287e069cbc6755d222fe2de5346acda3b34ff5f9818b9606a
7
- data.tar.gz: 8087a4c82be53394574b1ab70affcdf3c5355a1db0c24f022ea012c771cc290d9a04d173e49cd10edd6b54d8a8aba1f0b78bc392124f9d401e6940452013d45f
6
+ metadata.gz: 956e76aadce5f21fd099ba72d38914f4a4a6981a1d4382eee34a7bc37a0d79cb7bedb6b93000146a752fdc829945a93c72ef873fc3012acda5f6ea50d58128ae
7
+ data.tar.gz: 8fb1c37bc2f065c08a08dbdc25f04dbfe2aab5d67eb57c297a50fb5665e9840014fe1c3bd7257fc2dfa463919507fdf450dcaa71d1c17dc5cde0441b949c108b
checksums.yaml.gz.sig CHANGED
Binary file
@@ -3,25 +3,26 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2024, by Samuel Williams.
5
5
 
6
+ require_relative '../../coder'
7
+
6
8
  module Async
7
9
  module Job
8
10
  module Backend
9
11
  module Inline
10
12
  class Server
11
- def initialize(handler)
12
- @handler = handler
13
- end
14
-
15
- def enqueue(job)
16
- Async do
17
- @handler.call(job)
18
- end
13
+ def initialize(delegate)
14
+ @delegate = delegate
19
15
  end
20
16
 
21
- def schedule(job, timestamp)
17
+ def call(job)
18
+ scheduled_at = Coder::Time(job["scheduled_at"])
19
+
22
20
  Async do
23
- sleep(timestamp - Time.now)
24
- @handler.call(job)
21
+ if scheduled_at
22
+ sleep(scheduled_at - Time.now)
23
+ end
24
+
25
+ @delegate.call(job)
25
26
  end
26
27
  end
27
28
  end
@@ -9,8 +9,8 @@ module Async
9
9
  module Job
10
10
  module Backend
11
11
  module Inline
12
- def self.new(handler)
13
- return Server.new(handler)
12
+ def self.new(delegate)
13
+ return Server.new(delegate)
14
14
  end
15
15
  end
16
16
  end
@@ -9,18 +9,21 @@ require_relative 'processing_queue'
9
9
  require_relative 'ready_queue'
10
10
 
11
11
  require 'securerandom'
12
+ require_relative '../../coder'
12
13
 
13
14
  module Async
14
15
  module Job
15
16
  module Backend
16
17
  module Redis
17
18
  class Server
18
- def initialize(handler, client, prefix)
19
- @handler = handler
19
+ def initialize(delegate, client, prefix: 'async-job', coder: Coder::DEFAULT, resolution: 10)
20
+ @delegate = delegate
20
21
 
21
22
  @id = SecureRandom.uuid
22
23
  @client = client
23
24
  @prefix = prefix
25
+ @coder = coder
26
+ @resolution = resolution
24
27
 
25
28
  @job_store = JobStore.new(@client, "#{@prefix}:jobs")
26
29
 
@@ -33,7 +36,7 @@ module Async
33
36
  def start
34
37
  Console.info(self, "Starting server...")
35
38
  # Start the delayed queue, which will move jobs to the ready queue when they are ready:
36
- @delayed_queue.start(@ready_queue)
39
+ @delayed_queue.start(@ready_queue, resolution: @resolution)
37
40
 
38
41
  # Start the processing queue, which will move jobs to the ready queue when they are abandoned:
39
42
  @processing_queue.start
@@ -43,12 +46,14 @@ module Async
43
46
  end
44
47
  end
45
48
 
46
- def enqueue(job)
47
- @ready_queue.add(job, @job_store)
48
- end
49
-
50
- def schedule(job, timestamp)
51
- @delayed_queue.add(job, timestamp, @job_store)
49
+ def call(job)
50
+ scheduled_at = Coder::Time(job["scheduled_at"])
51
+
52
+ if scheduled_at
53
+ @delayed_queue.add(@coder.dump(job), scheduled_at, @job_store)
54
+ else
55
+ @ready_queue.add(@coder.dump(job), @job_store)
56
+ end
52
57
  end
53
58
 
54
59
  protected
@@ -56,8 +61,8 @@ module Async
56
61
  def dequeue
57
62
  id = @processing_queue.fetch
58
63
  begin
59
- job = @job_store.get(id)
60
- @handler.call(job)
64
+ job = @coder.load(@job_store.get(id))
65
+ @delegate.call(job)
61
66
  @processing_queue.complete(id)
62
67
  rescue => error
63
68
  @processing_queue.retry(id)
@@ -10,9 +10,9 @@ module Async
10
10
  module Job
11
11
  module Backend
12
12
  module Redis
13
- def self.new(handler, endpoint: Async::Redis.local_endpoint, prefix: "async:job")
13
+ def self.new(delegate, endpoint: Async::Redis.local_endpoint, **options)
14
14
  client = Async::Redis::Client.new(endpoint)
15
- return Server.new(handler, client, prefix)
15
+ return Server.new(delegate, client, **options)
16
16
  end
17
17
  end
18
18
  end
@@ -1,16 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
1
6
  require 'async/queue'
2
7
 
3
8
  module Async
4
9
  module Job
5
10
  class Buffer
6
- def initialize
11
+ def initialize(delegate = nil)
7
12
  @jobs = Async::Queue.new
13
+ @delegate = delegate
8
14
  end
9
15
 
10
16
  attr :jobs
11
17
 
12
18
  def call(job)
13
19
  @jobs.enqueue(job)
20
+ @delegate&.call(job)
14
21
  end
15
22
 
16
23
  def pop
@@ -0,0 +1,70 @@
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
+ class Builder
9
+ Pipeline = Struct.new(:producer, :consumer, :delegate)
10
+
11
+ def self.build(delegate = nil, &block)
12
+ builder = self.new(delegate)
13
+
14
+ builder.instance_eval(&block)
15
+
16
+ return builder.build
17
+ end
18
+
19
+ def initialize(delegate = nil)
20
+ @enqueue = []
21
+ @dequeue = []
22
+ @delegate = delegate
23
+
24
+ @queue = nil
25
+ end
26
+
27
+ def enqueue(middleware)
28
+ @enqueue << middleware
29
+ end
30
+
31
+ def queue(queue, *arguments, **options)
32
+ # The delegate is the output side of the queue, e.g. a Queuedelegate or similar wrapper.
33
+ # The queue itself is instantiated with the delegate.
34
+ @queue = ->(delegate){queue.new(delegate, *arguments, **options)}
35
+ end
36
+
37
+ def dequeue(middleware)
38
+ @dequeue << middleware
39
+ end
40
+
41
+ def delegate(delegate)
42
+ @delegate = delegate
43
+ end
44
+
45
+ def build(&block)
46
+ # To construct the queue, we need the delegate.
47
+ delegate = @delegate
48
+
49
+ # We then wrap the delegate with the middleware in reverse order:
50
+ @dequeue.reverse_each do |middleware|
51
+ delegate = middleware.new(delegate)
52
+ end
53
+
54
+ # We can now construct the queue with the delegate:
55
+ producer = consumer = @queue.call(delegate)
56
+
57
+ # We now construct the queue producer:
58
+ @enqueue.reverse_each do |middleware|
59
+ producer = middleware.new(queue)
60
+ end
61
+
62
+ if block_given?
63
+ producer = yield(producer)
64
+ end
65
+
66
+ return Pipeline.new(producer, consumer, @delegate)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require 'json'
7
+
8
+ module Async
9
+ module Job
10
+ module Coder
11
+ class JSON
12
+ def initialize
13
+ end
14
+
15
+ def dump(job)
16
+ ::JSON.dump(job)
17
+ end
18
+
19
+ def load(data)
20
+ ::JSON.parse(data, symbolize_names: true)
21
+ end
22
+
23
+ DEFAULT = self.new
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require 'msgpack'
7
+
8
+ module Async
9
+ module Job
10
+ module Coder
11
+ class Marshal
12
+ def dump(job)
13
+ ::Marshal.dump(job)
14
+ end
15
+
16
+ def load(data)
17
+ ::Marshal.load(data)
18
+ end
19
+
20
+ DEFAULT = self.new
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require 'msgpack'
7
+
8
+ module Async
9
+ module Job
10
+ module Coder
11
+ class MessagePack
12
+ def initialize
13
+ @factory = ::MessagePack::Factory.new
14
+
15
+ @factory.register_type(0x01, Symbol)
16
+
17
+ @factory.register_type(0x02, Time,
18
+ packer: ::MessagePack::Time::Packer,
19
+ unpacker: ::MessagePack::Time::Unpacker
20
+ )
21
+ end
22
+
23
+ def dump(job)
24
+ @factory.pack(job)
25
+ end
26
+
27
+ def load(data)
28
+ @factory.unpack(data)
29
+ end
30
+
31
+ DEFAULT = self.new
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require_relative 'coder/json'
7
+
8
+ module Async
9
+ module Job
10
+ module Coder
11
+ DEFAULT = JSON::DEFAULT
12
+
13
+ # Type-cast for time values. See <https://bugs.ruby-lang.org/issues/20298> for background.
14
+ # @parameter value [Time || Integer || String || nil]
15
+ def self.Time(value)
16
+ case value
17
+ when ::Time
18
+ value
19
+ when Integer
20
+ ::Time.at(value)
21
+ when String
22
+ ::Time.new(value)
23
+ when nil
24
+ nil
25
+ else
26
+ value.to_time
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -10,13 +10,13 @@ module Async
10
10
  self.new(...).enqueue
11
11
  end
12
12
 
13
- def initialize(id, perform_at: nil)
13
+ def initialize(id, scheduled_at: nil)
14
14
  @id = id
15
- @perform_at = perform_at
15
+ @scheduled_at = scheduled_at
16
16
  end
17
17
 
18
18
  attr :id
19
- attr :perform_at
19
+ attr :scheduled_at
20
20
 
21
21
  def serialize
22
22
  raise NotImplementedError
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module Job
8
- VERSION = "0.3.0"
8
+ VERSION = "0.4.0"
9
9
  end
10
10
  end
data/lib/async/job.rb CHANGED
@@ -5,3 +5,4 @@
5
5
 
6
6
  require_relative 'job/version'
7
7
  require_relative 'job/backend'
8
+ require_relative 'job/builder'
data/readme.md CHANGED
@@ -6,7 +6,11 @@ Provides an asynchronous job server.
6
6
 
7
7
  ## Usage
8
8
 
9
- The current implementation is incomplete, but with `redis` running on localhost, you can run several instances of `client.rb` and `server.rb` to show the general operation.
9
+ Please see the [project documentation](https://socketry.github.io/async-job/) for more details.
10
+
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
+
13
+ - [Redis Backend](https://socketry.github.io/async-job/guides/redis-backend/index) - This guide gives a brief overview of the implementation of the Redis backend.
10
14
 
11
15
  ## Contributing
12
16
 
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.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -37,7 +37,7 @@ cert_chain:
37
37
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
38
38
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
39
39
  -----END CERTIFICATE-----
40
- date: 2024-02-25 00:00:00.000000000 Z
40
+ date: 2024-02-26 00:00:00.000000000 Z
41
41
  dependencies:
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: async
@@ -84,6 +84,11 @@ files:
84
84
  - lib/async/job/backend/redis/ready_queue.rb
85
85
  - lib/async/job/backend/redis/server.rb
86
86
  - lib/async/job/buffer.rb
87
+ - lib/async/job/builder.rb
88
+ - lib/async/job/coder.rb
89
+ - lib/async/job/coder/json.rb
90
+ - lib/async/job/coder/marshal.rb
91
+ - lib/async/job/coder/message_pack.rb
87
92
  - lib/async/job/generic.rb
88
93
  - lib/async/job/version.rb
89
94
  - license.md
@@ -92,7 +97,7 @@ homepage:
92
97
  licenses:
93
98
  - MIT
94
99
  metadata:
95
- documentation_uri: https://socketry.github.io/async-job
100
+ documentation_uri: https://socketry.github.io/async-job/
96
101
  post_install_message:
97
102
  rdoc_options: []
98
103
  require_paths:
metadata.gz.sig CHANGED
Binary file