whoosh 1.0.2 → 1.1.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 +4 -4
- data/lib/whoosh/app.rb +2 -2
- data/lib/whoosh/cache.rb +10 -10
- data/lib/whoosh/job.rb +32 -1
- data/lib/whoosh/jobs/memory_backend.rb +39 -3
- data/lib/whoosh/jobs/redis_backend.rb +117 -0
- data/lib/whoosh/jobs/worker.rb +45 -11
- data/lib/whoosh/jobs.rb +29 -6
- data/lib/whoosh/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 03b756c310f71435839a3628bfdfa9499bb70869b9475e75eb6cce861caa9583
|
|
4
|
+
data.tar.gz: a848d792014e80cc6bee413ad14277881895a62a40854644f3a088bfe0ea164a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b764fa79a9e8297c66e0ce0c56be1127c1d1780ccb3840a0868a55841c4cb1108efffeff90cdaf4105a7df9259764aff7a8afe1047a42809e0c039b1e74be11d
|
|
7
|
+
data.tar.gz: 297cc80e8ca6ba22df946e07204f114bda7634480f5af72520caa0bdfde7b8604e6b66bf1b081f3527bede997a320b9ffc1b44c14fba21c2a05e5778101451c5
|
data/lib/whoosh/app.rb
CHANGED
|
@@ -303,7 +303,7 @@ module Whoosh
|
|
|
303
303
|
end
|
|
304
304
|
|
|
305
305
|
def auto_configure_jobs
|
|
306
|
-
backend = Jobs
|
|
306
|
+
backend = Jobs.build_backend(@config.data)
|
|
307
307
|
Jobs.configure(backend: backend, di: @di)
|
|
308
308
|
end
|
|
309
309
|
|
|
@@ -321,7 +321,7 @@ module Whoosh
|
|
|
321
321
|
worker = Jobs::Worker.new(
|
|
322
322
|
backend: Jobs.backend, di: @di,
|
|
323
323
|
max_retries: max_retries, retry_delay: retry_delay,
|
|
324
|
-
instrumentation: @instrumentation
|
|
324
|
+
instrumentation: @instrumentation, logger: @logger
|
|
325
325
|
)
|
|
326
326
|
thread = Thread.new { worker.run_loop }
|
|
327
327
|
thread.abort_on_exception = false
|
data/lib/whoosh/cache.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# lib/whoosh/cache.rb
|
|
2
1
|
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
module Whoosh
|
|
@@ -6,20 +5,21 @@ module Whoosh
|
|
|
6
5
|
autoload :MemoryStore, "whoosh/cache/memory_store"
|
|
7
6
|
autoload :RedisStore, "whoosh/cache/redis_store"
|
|
8
7
|
|
|
8
|
+
# Auto-detect: REDIS_URL set → Redis, otherwise → Memory
|
|
9
9
|
def self.build(config_data = {})
|
|
10
10
|
cache_config = config_data["cache"] || {}
|
|
11
|
-
store = cache_config["store"] || "memory"
|
|
12
11
|
default_ttl = cache_config["default_ttl"] || 300
|
|
12
|
+
redis_url = ENV["REDIS_URL"] || cache_config["url"]
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
if redis_url && cache_config["store"] != "memory"
|
|
15
|
+
begin
|
|
16
|
+
RedisStore.new(url: redis_url, default_ttl: default_ttl)
|
|
17
|
+
rescue Errors::DependencyError
|
|
18
|
+
# Redis gem not installed, fall back to memory
|
|
19
|
+
MemoryStore.new(default_ttl: default_ttl)
|
|
20
|
+
end
|
|
21
21
|
else
|
|
22
|
-
|
|
22
|
+
MemoryStore.new(default_ttl: default_ttl)
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
end
|
data/lib/whoosh/job.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# lib/whoosh/job.rb
|
|
2
1
|
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
module Whoosh
|
|
@@ -12,9 +11,41 @@ module Whoosh
|
|
|
12
11
|
@dependencies || []
|
|
13
12
|
end
|
|
14
13
|
|
|
14
|
+
def queue(name = nil)
|
|
15
|
+
if name
|
|
16
|
+
@queue_name = name.to_s
|
|
17
|
+
else
|
|
18
|
+
@queue_name || "default"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def retry_limit(n = nil)
|
|
23
|
+
if n
|
|
24
|
+
@retry_limit = n
|
|
25
|
+
else
|
|
26
|
+
@retry_limit
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def retry_backoff(strategy = nil)
|
|
31
|
+
if strategy
|
|
32
|
+
@retry_backoff = strategy
|
|
33
|
+
else
|
|
34
|
+
@retry_backoff || :linear
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
15
38
|
def perform_async(**args)
|
|
16
39
|
Jobs.enqueue(self, **args)
|
|
17
40
|
end
|
|
41
|
+
|
|
42
|
+
def perform_in(delay_seconds, **args)
|
|
43
|
+
Jobs.enqueue(self, run_at: Time.now.to_f + delay_seconds, **args)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def perform_at(time, **args)
|
|
47
|
+
Jobs.enqueue(self, run_at: time.to_f, **args)
|
|
48
|
+
end
|
|
18
49
|
end
|
|
19
50
|
|
|
20
51
|
def perform(**args)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# lib/whoosh/jobs/memory_backend.rb
|
|
2
1
|
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
module Whoosh
|
|
@@ -6,6 +5,7 @@ module Whoosh
|
|
|
6
5
|
class MemoryBackend
|
|
7
6
|
def initialize
|
|
8
7
|
@queue = []
|
|
8
|
+
@scheduled = []
|
|
9
9
|
@records = {}
|
|
10
10
|
@mutex = Mutex.new
|
|
11
11
|
@cv = ConditionVariable.new
|
|
@@ -13,14 +13,25 @@ module Whoosh
|
|
|
13
13
|
|
|
14
14
|
def push(job_data)
|
|
15
15
|
@mutex.synchronize do
|
|
16
|
-
|
|
16
|
+
if job_data[:run_at] && job_data[:run_at] > Time.now.to_f
|
|
17
|
+
@scheduled << job_data
|
|
18
|
+
@scheduled.sort_by! { |j| j[:run_at] }
|
|
19
|
+
else
|
|
20
|
+
@queue << job_data
|
|
21
|
+
end
|
|
17
22
|
@cv.signal
|
|
18
23
|
end
|
|
19
24
|
end
|
|
20
25
|
|
|
21
26
|
def pop(timeout: 5)
|
|
22
27
|
@mutex.synchronize do
|
|
23
|
-
|
|
28
|
+
# Promote scheduled jobs that are ready
|
|
29
|
+
promote_scheduled
|
|
30
|
+
|
|
31
|
+
if @queue.empty?
|
|
32
|
+
@cv.wait(@mutex, timeout)
|
|
33
|
+
promote_scheduled
|
|
34
|
+
end
|
|
24
35
|
@queue.shift
|
|
25
36
|
end
|
|
26
37
|
end
|
|
@@ -34,12 +45,37 @@ module Whoosh
|
|
|
34
45
|
end
|
|
35
46
|
|
|
36
47
|
def size
|
|
48
|
+
@mutex.synchronize { @queue.size + @scheduled.size }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def pending_count
|
|
37
52
|
@mutex.synchronize { @queue.size }
|
|
38
53
|
end
|
|
39
54
|
|
|
55
|
+
def scheduled_count
|
|
56
|
+
@mutex.synchronize { @scheduled.size }
|
|
57
|
+
end
|
|
58
|
+
|
|
40
59
|
def shutdown
|
|
41
60
|
@mutex.synchronize { @cv.broadcast }
|
|
42
61
|
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def promote_scheduled
|
|
66
|
+
now = Time.now.to_f
|
|
67
|
+
ready = []
|
|
68
|
+
remaining = []
|
|
69
|
+
@scheduled.each do |job|
|
|
70
|
+
if job[:run_at] <= now
|
|
71
|
+
ready << job
|
|
72
|
+
else
|
|
73
|
+
remaining << job
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
@scheduled = remaining
|
|
77
|
+
@queue.concat(ready)
|
|
78
|
+
end
|
|
43
79
|
end
|
|
44
80
|
end
|
|
45
81
|
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whoosh
|
|
4
|
+
module Jobs
|
|
5
|
+
class RedisBackend
|
|
6
|
+
@redis_available = nil
|
|
7
|
+
|
|
8
|
+
def self.available?
|
|
9
|
+
if @redis_available.nil?
|
|
10
|
+
@redis_available = begin
|
|
11
|
+
require "redis"
|
|
12
|
+
true
|
|
13
|
+
rescue LoadError
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
@redis_available
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(url:, prefix: "whoosh:jobs")
|
|
21
|
+
unless self.class.available?
|
|
22
|
+
raise Errors::DependencyError, "Jobs Redis backend requires the 'redis' gem"
|
|
23
|
+
end
|
|
24
|
+
@redis = Redis.new(url: url)
|
|
25
|
+
@prefix = prefix
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def push(job_data)
|
|
29
|
+
serialized = Serialization::Json.encode(job_data)
|
|
30
|
+
if job_data[:run_at] && job_data[:run_at] > Time.now.to_f
|
|
31
|
+
# Scheduled: use sorted set with run_at as score
|
|
32
|
+
@redis.zadd("#{@prefix}:scheduled", job_data[:run_at], serialized)
|
|
33
|
+
else
|
|
34
|
+
@redis.lpush("#{@prefix}:queue:#{job_data[:queue] || "default"}", serialized)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def pop(timeout: 5, queues: ["default"])
|
|
39
|
+
# First, promote scheduled jobs
|
|
40
|
+
promote_scheduled
|
|
41
|
+
|
|
42
|
+
# Try each queue in priority order
|
|
43
|
+
queues.each do |queue|
|
|
44
|
+
result = @redis.rpop("#{@prefix}:queue:#{queue}")
|
|
45
|
+
if result
|
|
46
|
+
return Serialization::Json.decode(result).transform_keys(&:to_sym)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Block-wait on default queue
|
|
51
|
+
result = @redis.brpop("#{@prefix}:queue:#{queues.first}", timeout: timeout)
|
|
52
|
+
if result
|
|
53
|
+
Serialization::Json.decode(result[1]).transform_keys(&:to_sym)
|
|
54
|
+
end
|
|
55
|
+
rescue => e
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def save(record)
|
|
60
|
+
serialized = Serialization::Json.encode(record)
|
|
61
|
+
@redis.set("#{@prefix}:record:#{record[:id]}", serialized, ex: 86400) # 24h TTL
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def find(id)
|
|
65
|
+
raw = @redis.get("#{@prefix}:record:#{id}")
|
|
66
|
+
return nil unless raw
|
|
67
|
+
data = Serialization::Json.decode(raw)
|
|
68
|
+
data.transform_keys(&:to_sym)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def size
|
|
72
|
+
pending_count + scheduled_count
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def pending_count
|
|
76
|
+
count = 0
|
|
77
|
+
@redis.keys("#{@prefix}:queue:*").each do |key|
|
|
78
|
+
count += @redis.llen(key)
|
|
79
|
+
end
|
|
80
|
+
count
|
|
81
|
+
rescue => e
|
|
82
|
+
0
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def scheduled_count
|
|
86
|
+
@redis.zcard("#{@prefix}:scheduled")
|
|
87
|
+
rescue => e
|
|
88
|
+
0
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def shutdown
|
|
92
|
+
@redis.close
|
|
93
|
+
rescue => e
|
|
94
|
+
# Already closed
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def promote_scheduled
|
|
100
|
+
now = Time.now.to_f
|
|
101
|
+
# Get all jobs ready to run
|
|
102
|
+
ready = @redis.zrangebyscore("#{@prefix}:scheduled", "-inf", now.to_s)
|
|
103
|
+
ready.each do |raw|
|
|
104
|
+
# Remove from scheduled set
|
|
105
|
+
removed = @redis.zrem("#{@prefix}:scheduled", raw)
|
|
106
|
+
next unless removed
|
|
107
|
+
|
|
108
|
+
job_data = Serialization::Json.decode(raw)
|
|
109
|
+
queue = job_data["queue"] || "default"
|
|
110
|
+
@redis.lpush("#{@prefix}:queue:#{queue}", raw)
|
|
111
|
+
end
|
|
112
|
+
rescue => e
|
|
113
|
+
# Don't crash on promote errors
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
data/lib/whoosh/jobs/worker.rb
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
# lib/whoosh/jobs/worker.rb
|
|
2
1
|
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
module Whoosh
|
|
5
4
|
module Jobs
|
|
6
5
|
class Worker
|
|
7
|
-
def initialize(backend:, di: nil, max_retries: 3, retry_delay: 5, instrumentation: nil)
|
|
6
|
+
def initialize(backend:, di: nil, max_retries: 3, retry_delay: 5, instrumentation: nil, logger: nil)
|
|
8
7
|
@backend = backend
|
|
9
8
|
@di = di
|
|
10
9
|
@max_retries = max_retries
|
|
11
10
|
@retry_delay = retry_delay
|
|
12
11
|
@instrumentation = instrumentation
|
|
12
|
+
@logger = logger
|
|
13
13
|
@running = true
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -33,13 +33,27 @@ module Whoosh
|
|
|
33
33
|
|
|
34
34
|
def execute(job_data)
|
|
35
35
|
id = job_data[:id]
|
|
36
|
+
class_name = job_data[:class_name]
|
|
37
|
+
|
|
38
|
+
# Skip scheduled jobs that aren't ready yet
|
|
39
|
+
if job_data[:run_at] && job_data[:run_at].to_f > Time.now.to_f
|
|
40
|
+
@backend.push(job_data)
|
|
41
|
+
return
|
|
42
|
+
end
|
|
43
|
+
|
|
36
44
|
record = @backend.find(id) || {}
|
|
37
|
-
record = record.merge(status: :running, started_at: Time.now.to_f)
|
|
45
|
+
record = record.merge(id: id, status: :running, started_at: Time.now.to_f)
|
|
38
46
|
@backend.save(record)
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
@logger&.info("job_started", job_id: id, class: class_name)
|
|
49
|
+
|
|
50
|
+
job_class = Object.const_get(class_name)
|
|
41
51
|
job = job_class.new
|
|
42
52
|
|
|
53
|
+
# Determine retry settings from job class or defaults
|
|
54
|
+
max_retries = job_class.respond_to?(:retry_limit) && job_class.retry_limit ? job_class.retry_limit : @max_retries
|
|
55
|
+
backoff_strategy = job_class.respond_to?(:retry_backoff) ? job_class.retry_backoff : :linear
|
|
56
|
+
|
|
43
57
|
# Inject DI deps
|
|
44
58
|
if @di && job_class.respond_to?(:dependencies)
|
|
45
59
|
job_class.dependencies.each do |dep|
|
|
@@ -49,23 +63,43 @@ module Whoosh
|
|
|
49
63
|
end
|
|
50
64
|
end
|
|
51
65
|
|
|
52
|
-
args = job_data[:args]
|
|
66
|
+
args = job_data[:args]
|
|
67
|
+
args = args.transform_keys(&:to_sym) if args.is_a?(Hash)
|
|
53
68
|
result = job.perform(**args)
|
|
54
69
|
serialized = Serialization::Json.decode(Serialization::Json.encode(result))
|
|
55
70
|
|
|
56
71
|
@backend.save(record.merge(status: :completed, result: serialized, completed_at: Time.now.to_f))
|
|
72
|
+
@logger&.info("job_completed", job_id: id, class: class_name)
|
|
73
|
+
|
|
57
74
|
rescue => e
|
|
58
|
-
record = @backend.find(id) || {}
|
|
75
|
+
record = @backend.find(id) || { id: id }
|
|
59
76
|
retry_count = (record[:retry_count] || 0) + 1
|
|
60
77
|
|
|
61
|
-
if retry_count <=
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
78
|
+
if retry_count <= max_retries
|
|
79
|
+
# Non-blocking retry: re-enqueue with delay timestamp instead of sleeping
|
|
80
|
+
delay = calculate_delay(retry_count, backoff_strategy)
|
|
81
|
+
run_at = Time.now.to_f + delay
|
|
82
|
+
@backend.save(record.merge(retry_count: retry_count, status: :scheduled, run_at: run_at))
|
|
83
|
+
@backend.push(job_data.merge(run_at: run_at))
|
|
84
|
+
@logger&.warn("job_retry", job_id: id, class: class_name, retry_count: retry_count, delay: delay)
|
|
65
85
|
else
|
|
66
86
|
error = { message: e.message, backtrace: e.backtrace&.first(10)&.join("\n") }
|
|
67
|
-
@backend.save(record.merge(
|
|
87
|
+
@backend.save(record.merge(
|
|
88
|
+
status: :failed, error: error, retry_count: retry_count, completed_at: Time.now.to_f
|
|
89
|
+
))
|
|
68
90
|
@instrumentation&.emit(:job_failed, { job_id: id, error: error })
|
|
91
|
+
@logger&.error("job_failed", job_id: id, class: class_name, error: e.message)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def calculate_delay(retry_count, strategy)
|
|
96
|
+
case strategy
|
|
97
|
+
when :exponential
|
|
98
|
+
@retry_delay * (2**(retry_count - 1)) # 5, 10, 20, 40...
|
|
99
|
+
when :linear
|
|
100
|
+
@retry_delay * retry_count # 5, 10, 15, 20...
|
|
101
|
+
else
|
|
102
|
+
@retry_delay
|
|
69
103
|
end
|
|
70
104
|
end
|
|
71
105
|
end
|
data/lib/whoosh/jobs.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# lib/whoosh/jobs.rb
|
|
2
1
|
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
require "securerandom"
|
|
@@ -6,6 +5,7 @@ require "securerandom"
|
|
|
6
5
|
module Whoosh
|
|
7
6
|
module Jobs
|
|
8
7
|
autoload :MemoryBackend, "whoosh/jobs/memory_backend"
|
|
8
|
+
autoload :RedisBackend, "whoosh/jobs/redis_backend"
|
|
9
9
|
autoload :Worker, "whoosh/jobs/worker"
|
|
10
10
|
|
|
11
11
|
@backend = nil
|
|
@@ -23,16 +23,27 @@ module Whoosh
|
|
|
23
23
|
!!@backend
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def enqueue(job_class, **args)
|
|
26
|
+
def enqueue(job_class, run_at: nil, **args)
|
|
27
27
|
raise Errors::DependencyError, "Jobs not configured — boot a Whoosh::App first" unless configured?
|
|
28
|
+
|
|
28
29
|
id = SecureRandom.uuid
|
|
30
|
+
queue_name = job_class.respond_to?(:queue) ? job_class.queue : "default"
|
|
29
31
|
record = {
|
|
30
|
-
id: id,
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
id: id,
|
|
33
|
+
class_name: job_class.name,
|
|
34
|
+
args: args,
|
|
35
|
+
queue: queue_name,
|
|
36
|
+
status: run_at ? :scheduled : :pending,
|
|
37
|
+
run_at: run_at,
|
|
38
|
+
result: nil,
|
|
39
|
+
error: nil,
|
|
40
|
+
retry_count: 0,
|
|
41
|
+
created_at: Time.now.to_f,
|
|
42
|
+
started_at: nil,
|
|
43
|
+
completed_at: nil
|
|
33
44
|
}
|
|
34
45
|
@backend.save(record)
|
|
35
|
-
@backend.push({ id: id, class_name: job_class.name, args: args })
|
|
46
|
+
@backend.push({ id: id, class_name: job_class.name, args: args, queue: queue_name, run_at: run_at })
|
|
36
47
|
id
|
|
37
48
|
end
|
|
38
49
|
|
|
@@ -41,6 +52,18 @@ module Whoosh
|
|
|
41
52
|
@backend.find(id)
|
|
42
53
|
end
|
|
43
54
|
|
|
55
|
+
# Build the right backend from config (auto-detect pattern)
|
|
56
|
+
def build_backend(config_data = {})
|
|
57
|
+
jobs_config = config_data["jobs"] || {}
|
|
58
|
+
redis_url = ENV["REDIS_URL"] || jobs_config["redis_url"]
|
|
59
|
+
|
|
60
|
+
if redis_url && jobs_config["backend"] != "memory"
|
|
61
|
+
RedisBackend.new(url: redis_url)
|
|
62
|
+
else
|
|
63
|
+
MemoryBackend.new
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
44
67
|
def reset!
|
|
45
68
|
@backend = nil
|
|
46
69
|
@di = nil
|
data/lib/whoosh/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: whoosh
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Johannes Dwi Cahyo
|
|
@@ -199,6 +199,7 @@ files:
|
|
|
199
199
|
- lib/whoosh/job.rb
|
|
200
200
|
- lib/whoosh/jobs.rb
|
|
201
201
|
- lib/whoosh/jobs/memory_backend.rb
|
|
202
|
+
- lib/whoosh/jobs/redis_backend.rb
|
|
202
203
|
- lib/whoosh/jobs/worker.rb
|
|
203
204
|
- lib/whoosh/logger.rb
|
|
204
205
|
- lib/whoosh/mcp/client.rb
|