anway 0.1.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 +7 -0
- data/LICENSE +22 -0
- data/README.md +146 -0
- data/exe/anway +6 -0
- data/lib/active_job/queue_adapters/anway_adapter.rb +65 -0
- data/lib/anway/cli.rb +176 -0
- data/lib/anway/client.rb +151 -0
- data/lib/anway/configuration.rb +32 -0
- data/lib/anway/connection.rb +195 -0
- data/lib/anway/errors.rb +34 -0
- data/lib/anway/executor.rb +29 -0
- data/lib/anway/job.rb +81 -0
- data/lib/anway/pool.rb +85 -0
- data/lib/anway/queues.rb +21 -0
- data/lib/anway/railtie.rb +23 -0
- data/lib/anway/runner.rb +259 -0
- data/lib/anway/testing.rb +48 -0
- data/lib/anway/version.rb +5 -0
- data/lib/anway/worker.rb +127 -0
- data/lib/anway.rb +59 -0
- metadata +71 -0
data/lib/anway/runner.rb
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Anway
|
|
6
|
+
# Runs jobs until stop, or until the server sends terminate.
|
|
7
|
+
#
|
|
8
|
+
# After quiet, the runner stops fetching and stays alive. At a stop, running
|
|
9
|
+
# jobs get `timeout` seconds to finish. Then Anway::Shutdown is raised in them,
|
|
10
|
+
# and they get `cancel_grace` seconds to return. Jobs that still run then fail.
|
|
11
|
+
class Runner
|
|
12
|
+
BEAT_INTERVAL = 15
|
|
13
|
+
ERROR_PAUSE = 1
|
|
14
|
+
EMPTY_FETCH_MINIMUM = 0.2
|
|
15
|
+
BACKTRACE_LINES = 30
|
|
16
|
+
|
|
17
|
+
attr_reader :wid
|
|
18
|
+
|
|
19
|
+
def initialize(queues: ['default'], concurrency: 10, timeout: 25, cancel_grace: 5,
|
|
20
|
+
labels: [], beat_interval: BEAT_INTERVAL, url: nil, logger: Anway.config.logger)
|
|
21
|
+
@queues = Queues.new(queues)
|
|
22
|
+
@concurrency = concurrency
|
|
23
|
+
@timeout = timeout
|
|
24
|
+
@cancel_grace = cancel_grace
|
|
25
|
+
@beat_interval = beat_interval
|
|
26
|
+
@logger = logger
|
|
27
|
+
@wid = SecureRandom.hex(12)
|
|
28
|
+
@fetching = Thread::Queue.new
|
|
29
|
+
@stopped_beat = Thread::Queue.new
|
|
30
|
+
@events_reader, @events_writer = IO.pipe
|
|
31
|
+
@running = {}
|
|
32
|
+
@lock = Mutex.new
|
|
33
|
+
|
|
34
|
+
@client = Client.new(
|
|
35
|
+
url || Anway.config.url,
|
|
36
|
+
pool_size: concurrency + 1,
|
|
37
|
+
timeout: Anway.config.timeout,
|
|
38
|
+
tls: Anway.config.tls,
|
|
39
|
+
wid: @wid,
|
|
40
|
+
labels: ['ruby', *labels]
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Safe in a signal handler.
|
|
45
|
+
def stop
|
|
46
|
+
signal('s')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Safe in a signal handler.
|
|
50
|
+
def quiet
|
|
51
|
+
signal('q')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Safe in a signal handler.
|
|
55
|
+
def dump
|
|
56
|
+
signal('d')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def run
|
|
60
|
+
@client.beat
|
|
61
|
+
|
|
62
|
+
@processors = Array.new(@concurrency) { start_processor }
|
|
63
|
+
|
|
64
|
+
beat_thread = start_beat
|
|
65
|
+
|
|
66
|
+
@logger.info("anway started queues=#{@queues.names.join(',')} concurrency=#{@concurrency}")
|
|
67
|
+
|
|
68
|
+
wait_for_stop
|
|
69
|
+
shutdown
|
|
70
|
+
|
|
71
|
+
@stopped_beat.close
|
|
72
|
+
beat_thread.join
|
|
73
|
+
ensure
|
|
74
|
+
@client.close
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def signal(letter)
|
|
80
|
+
@events_writer.write_nonblock(letter, exception: false)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def wait_for_stop
|
|
84
|
+
loop do
|
|
85
|
+
@events_reader.wait_readable
|
|
86
|
+
|
|
87
|
+
case @events_reader.read_nonblock(1)
|
|
88
|
+
when 's' then return
|
|
89
|
+
when 'q' then stop_fetching
|
|
90
|
+
when 'd' then dump_threads
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def dump_threads
|
|
96
|
+
Thread.list.each { |thread| @logger.info("#{thread.inspect}\n#{thread.backtrace&.first(10)&.join("\n")}") }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def stop_fetching
|
|
100
|
+
@fetching.close
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def fetching?
|
|
104
|
+
!@fetching.closed?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def pause(seconds)
|
|
108
|
+
@fetching.pop(timeout: seconds)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def start_beat
|
|
112
|
+
Thread.new do
|
|
113
|
+
state = nil
|
|
114
|
+
|
|
115
|
+
until @stopped_beat.closed?
|
|
116
|
+
@stopped_beat.pop(timeout: @beat_interval)
|
|
117
|
+
|
|
118
|
+
break if @stopped_beat.closed?
|
|
119
|
+
|
|
120
|
+
state = beat(state)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def beat(state)
|
|
126
|
+
case @client.beat(state)
|
|
127
|
+
when 'quiet'
|
|
128
|
+
quiet
|
|
129
|
+
|
|
130
|
+
'quiet'
|
|
131
|
+
when 'terminate'
|
|
132
|
+
stop
|
|
133
|
+
|
|
134
|
+
state
|
|
135
|
+
else
|
|
136
|
+
state
|
|
137
|
+
end
|
|
138
|
+
rescue Error => e
|
|
139
|
+
@logger.warn("beat failed: #{e.message}")
|
|
140
|
+
|
|
141
|
+
state
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Shutdown stays pending outside perform, so it cannot break a report.
|
|
145
|
+
def start_processor
|
|
146
|
+
Thread.new do
|
|
147
|
+
Thread.handle_interrupt(Shutdown => :never) { process_loop }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# A fetch in progress is not canceled, because its reply can hold a job.
|
|
152
|
+
def process_loop
|
|
153
|
+
while fetching?
|
|
154
|
+
started = monotonic
|
|
155
|
+
job = fetch_next
|
|
156
|
+
|
|
157
|
+
if job
|
|
158
|
+
process(job)
|
|
159
|
+
elsif monotonic - started < EMPTY_FETCH_MINIMUM
|
|
160
|
+
pause(EMPTY_FETCH_MINIMUM)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def fetch_next
|
|
166
|
+
@client.fetch(*@queues.order)
|
|
167
|
+
rescue Error => e
|
|
168
|
+
@logger.warn("fetch failed: #{e.message}")
|
|
169
|
+
pause(ERROR_PAUSE)
|
|
170
|
+
|
|
171
|
+
nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def process(job)
|
|
175
|
+
@lock.synchronize { @running[job.jid] = { job: job, thread: Thread.current } }
|
|
176
|
+
|
|
177
|
+
error = perform(job)
|
|
178
|
+
|
|
179
|
+
return unless claim(job.jid)
|
|
180
|
+
|
|
181
|
+
report(job, error)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def perform(job)
|
|
185
|
+
Thread.handle_interrupt(Shutdown => :immediate) { Executor.call(job) }
|
|
186
|
+
|
|
187
|
+
nil
|
|
188
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
189
|
+
notify_error(e, job) unless e.is_a?(Shutdown)
|
|
190
|
+
|
|
191
|
+
e
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def notify_error(error, job)
|
|
195
|
+
@logger.warn("job failed jid=#{job.jid} jobtype=#{job.type} error=#{error.class}: #{error.message}")
|
|
196
|
+
|
|
197
|
+
Anway.config.error_handlers.each { |handler| handler.call(error, job) }
|
|
198
|
+
rescue StandardError => e
|
|
199
|
+
@logger.error("error handler failed: #{e.message}")
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def claim(jid)
|
|
203
|
+
@lock.synchronize { !@running.delete(jid).nil? }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def report(job, error)
|
|
207
|
+
if error
|
|
208
|
+
@client.fail(
|
|
209
|
+
job.jid,
|
|
210
|
+
errtype: error.class.name,
|
|
211
|
+
message: error.message,
|
|
212
|
+
backtrace: error.backtrace&.first(BACKTRACE_LINES) || []
|
|
213
|
+
)
|
|
214
|
+
else
|
|
215
|
+
@client.ack(job.jid)
|
|
216
|
+
end
|
|
217
|
+
rescue Error => e
|
|
218
|
+
@logger.warn("job report failed, the reservation will expire jid=#{job.jid}: #{e.message}")
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def shutdown
|
|
222
|
+
stop_fetching if fetching?
|
|
223
|
+
|
|
224
|
+
return if join_processors(@timeout)
|
|
225
|
+
|
|
226
|
+
running.each { |entry| entry[:thread].raise(Shutdown, 'the worker is stopping') }
|
|
227
|
+
|
|
228
|
+
return if join_processors(@cancel_grace)
|
|
229
|
+
|
|
230
|
+
running.each { |entry| abandon(entry) }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def running
|
|
234
|
+
@lock.synchronize { @running.values }
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def abandon(entry)
|
|
238
|
+
job = entry[:job]
|
|
239
|
+
|
|
240
|
+
return unless claim(job.jid)
|
|
241
|
+
|
|
242
|
+
@logger.warn("job did not stop jid=#{job.jid} jobtype=#{job.type}")
|
|
243
|
+
|
|
244
|
+
entry[:thread].kill
|
|
245
|
+
|
|
246
|
+
report(job, Shutdown.new('the worker stopped before the job finished'))
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def join_processors(limit)
|
|
250
|
+
deadline = monotonic + limit
|
|
251
|
+
|
|
252
|
+
@processors.all? { |thread| thread.join([deadline - monotonic, 0].max) }
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def monotonic
|
|
256
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'anway'
|
|
4
|
+
|
|
5
|
+
module Anway
|
|
6
|
+
# Test modes for apps. No server is necessary.
|
|
7
|
+
#
|
|
8
|
+
# Anway::Testing.fake! keeps pushed jobs in a list
|
|
9
|
+
# Anway::Testing.inline! runs each job at push
|
|
10
|
+
# Anway::Testing.disable! uses the real client
|
|
11
|
+
module Testing
|
|
12
|
+
@mode = nil
|
|
13
|
+
@jobs = []
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
attr_reader :jobs, :mode
|
|
17
|
+
|
|
18
|
+
def fake!
|
|
19
|
+
@mode = :fake
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def inline!
|
|
23
|
+
@mode = :inline
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def disable!
|
|
27
|
+
@mode = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def clear
|
|
31
|
+
@jobs.clear
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def enqueue(job)
|
|
35
|
+
copy = Job.from_hash(JSON.parse(JSON.generate(job.to_h)))
|
|
36
|
+
|
|
37
|
+
@mode == :inline ? Executor.call(copy) : @jobs << copy
|
|
38
|
+
|
|
39
|
+
job.jid
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Runs and removes all fake jobs. Jobs that they push also run.
|
|
43
|
+
def drain
|
|
44
|
+
Executor.call(@jobs.shift) until @jobs.empty?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/anway/worker.rb
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Anway
|
|
4
|
+
# Makes a class a job:
|
|
5
|
+
#
|
|
6
|
+
# class WelcomeJob
|
|
7
|
+
# include Anway::Worker
|
|
8
|
+
#
|
|
9
|
+
# job_options queue: 'mailers', retry: 5
|
|
10
|
+
#
|
|
11
|
+
# def perform(user_id); end
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# WelcomeJob.perform_async(42)
|
|
15
|
+
module Worker
|
|
16
|
+
DEFAULT_BACKTRACE = 30
|
|
17
|
+
|
|
18
|
+
@registry = {}
|
|
19
|
+
@lock = Mutex.new
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
def included(base)
|
|
23
|
+
base.extend(ClassMethods)
|
|
24
|
+
|
|
25
|
+
register(base) if base.name
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def register(klass, old_type: nil)
|
|
29
|
+
@lock.synchronize do
|
|
30
|
+
@registry.delete(old_type) if old_type
|
|
31
|
+
@registry[klass.job_type] = klass
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def classes
|
|
36
|
+
@lock.synchronize { @registry.values }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Only registered classes run. The job type comes from the network, so it
|
|
40
|
+
# never becomes a constant lookup. The lookup by the class name below
|
|
41
|
+
# uses a registered name, and returns the newest class after a reload.
|
|
42
|
+
def find(type)
|
|
43
|
+
klass = @lock.synchronize { @registry[type] }
|
|
44
|
+
|
|
45
|
+
raise UnknownJob, "no handler for job type #{type.inspect}" unless klass
|
|
46
|
+
|
|
47
|
+
klass.name ? Object.const_get(klass.name) : klass
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
attr_accessor :job
|
|
52
|
+
|
|
53
|
+
def jid
|
|
54
|
+
job&.jid
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Updates the progress of a tracked job.
|
|
58
|
+
def track(percent: nil, desc: nil, reserve_until: nil)
|
|
59
|
+
Anway.client.track_set(jid, percent: percent, desc: desc, reserve_until: reserve_until)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module ClassMethods
|
|
63
|
+
def inherited(subclass)
|
|
64
|
+
super
|
|
65
|
+
|
|
66
|
+
Worker.register(subclass) if subclass.name
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def job_type(name = nil)
|
|
70
|
+
return @job_type || self.name unless name
|
|
71
|
+
|
|
72
|
+
old_type = job_type
|
|
73
|
+
@job_type = name
|
|
74
|
+
|
|
75
|
+
Worker.register(self, old_type: old_type)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def job_options(options = nil)
|
|
79
|
+
inherited_options = superclass.respond_to?(:job_options) ? superclass.job_options : {}
|
|
80
|
+
|
|
81
|
+
@job_options = (@job_options || {}).merge(options) if options
|
|
82
|
+
|
|
83
|
+
inherited_options.merge(@job_options || {})
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def perform_async(*args)
|
|
87
|
+
enqueue_with(args, {})
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def perform_at(time, *args)
|
|
91
|
+
enqueue_with(args, { at: time })
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def perform_in(seconds, *args)
|
|
95
|
+
enqueue_with(args, { at: Time.now + seconds.to_f })
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Changes the options for one push: WelcomeJob.set(queue: 'low').perform_async(1)
|
|
99
|
+
def set(**options)
|
|
100
|
+
Setter.new(self, options)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def enqueue_with(args, options)
|
|
104
|
+
Anway.enqueue(Job.new(job_type, args, backtrace: DEFAULT_BACKTRACE, **job_options, **options))
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class Setter
|
|
109
|
+
def initialize(klass, options)
|
|
110
|
+
@klass = klass
|
|
111
|
+
@options = options
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def perform_async(*args)
|
|
115
|
+
@klass.enqueue_with(args, @options)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def perform_at(time, *args)
|
|
119
|
+
@klass.enqueue_with(args, @options.merge(at: time))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def perform_in(seconds, *args)
|
|
123
|
+
@klass.enqueue_with(args, @options.merge(at: Time.now + seconds.to_f))
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
data/lib/anway.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'anway/version'
|
|
4
|
+
require_relative 'anway/errors'
|
|
5
|
+
require_relative 'anway/configuration'
|
|
6
|
+
require_relative 'anway/job'
|
|
7
|
+
require_relative 'anway/connection'
|
|
8
|
+
require_relative 'anway/pool'
|
|
9
|
+
require_relative 'anway/client'
|
|
10
|
+
require_relative 'anway/worker'
|
|
11
|
+
require_relative 'anway/executor'
|
|
12
|
+
require_relative 'anway/queues'
|
|
13
|
+
require_relative 'anway/runner'
|
|
14
|
+
require_relative 'anway/railtie' if defined?(Rails::Railtie)
|
|
15
|
+
|
|
16
|
+
module Anway
|
|
17
|
+
@lock = Mutex.new
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
def config
|
|
21
|
+
@config ||= Configuration.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def configure
|
|
25
|
+
yield config
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def client
|
|
29
|
+
@lock.synchronize do
|
|
30
|
+
@client ||= Client.new(
|
|
31
|
+
config.url, pool_size: config.pool_size, timeout: config.timeout, tls: config.tls
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Sends a job with no local class, for a worker in any language.
|
|
37
|
+
def push(type, args = [], **options)
|
|
38
|
+
enqueue(Job.new(type, args, **options))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Inside a database transaction, the push waits for the commit.
|
|
42
|
+
def enqueue(job)
|
|
43
|
+
testing = const_defined?(:Testing) ? Testing : nil
|
|
44
|
+
|
|
45
|
+
return testing.enqueue(job) if testing&.mode
|
|
46
|
+
|
|
47
|
+
config.push_wrapper.call { client.push(job) }
|
|
48
|
+
|
|
49
|
+
job.jid
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def reset!
|
|
53
|
+
@lock.synchronize do
|
|
54
|
+
@client&.close
|
|
55
|
+
@client = nil
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: anway
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Thadeu
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: logger
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.6'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.6'
|
|
26
|
+
executables:
|
|
27
|
+
- anway
|
|
28
|
+
extensions: []
|
|
29
|
+
extra_rdoc_files: []
|
|
30
|
+
files:
|
|
31
|
+
- LICENSE
|
|
32
|
+
- README.md
|
|
33
|
+
- exe/anway
|
|
34
|
+
- lib/active_job/queue_adapters/anway_adapter.rb
|
|
35
|
+
- lib/anway.rb
|
|
36
|
+
- lib/anway/cli.rb
|
|
37
|
+
- lib/anway/client.rb
|
|
38
|
+
- lib/anway/configuration.rb
|
|
39
|
+
- lib/anway/connection.rb
|
|
40
|
+
- lib/anway/errors.rb
|
|
41
|
+
- lib/anway/executor.rb
|
|
42
|
+
- lib/anway/job.rb
|
|
43
|
+
- lib/anway/pool.rb
|
|
44
|
+
- lib/anway/queues.rb
|
|
45
|
+
- lib/anway/railtie.rb
|
|
46
|
+
- lib/anway/runner.rb
|
|
47
|
+
- lib/anway/testing.rb
|
|
48
|
+
- lib/anway/version.rb
|
|
49
|
+
- lib/anway/worker.rb
|
|
50
|
+
homepage: https://github.com/thadeu/anway-ruby
|
|
51
|
+
licenses:
|
|
52
|
+
- MIT
|
|
53
|
+
metadata: {}
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.4'
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 4.0.20
|
|
69
|
+
specification_version: 4
|
|
70
|
+
summary: Ruby and Rails client and worker for the Anway job server
|
|
71
|
+
test_files: []
|