shinq 0.8.0 → 1.0.2
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 +5 -5
- data/.travis.yml +6 -6
- data/README.md +2 -0
- data/lib/generators/shinq/worker/templates/create_table_migration.erb +1 -0
- data/lib/shinq.rb +10 -2
- data/lib/shinq/active_job/queue_adapters/shinq_adapter.rb +17 -0
- data/lib/shinq/cli.rb +12 -5
- data/lib/shinq/client.rb +33 -5
- data/lib/shinq/configuration.rb +14 -4
- data/lib/shinq/launcher.rb +31 -11
- data/shinq.gemspec +6 -5
- data/spec/db/structure.sql.erb +15 -0
- data/spec/helpers/my_worker.rb +4 -0
- data/spec/integration_spec.rb +82 -10
- data/spec/shinq/cli_spec.rb +38 -0
- data/spec/shinq/client_spec.rb +46 -0
- data/spec/shinq/configuration_spec.rb +15 -0
- data/spec/shinq_spec.rb +52 -34
- data/spec/spec_helper.rb +18 -1
- metadata +47 -16
- data/spec/db/structure.sql +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c0cdad6cb88d95c3f54ef38ac64b7db0314850912693d9f87eae2d88c59c11be
|
4
|
+
data.tar.gz: aed02806856d77fda982ae5d2039be2bf4558f32e7732a6d3681299fc25aac26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f8e92ee4d147158524f66aa2d1bfb596abe66c0dddf49924d1e642a379f8ae30075127cee711dc535da8c97305025a983753802ead7b5f11eebccda399522f7
|
7
|
+
data.tar.gz: 45ee3dcf8f20c696c078cb2db67475da5aa66160f1699b0db03108540a42cf641595f05ebd529e5110a9972e61710b7e9644e9881d74e2ffd799618a5cef3207
|
data/.travis.yml
CHANGED
@@ -3,14 +3,14 @@ sudo: false
|
|
3
3
|
cache: bundler
|
4
4
|
rvm:
|
5
5
|
- 2.1
|
6
|
-
- 2.2
|
7
|
-
- 2.
|
8
|
-
- 2.
|
9
|
-
- 2.
|
10
|
-
- 2.
|
11
|
-
- 2.4.1
|
6
|
+
- 2.2
|
7
|
+
- 2.3
|
8
|
+
- 2.4
|
9
|
+
- 2.6
|
10
|
+
- 2.7
|
12
11
|
gemfile:
|
13
12
|
- Gemfile
|
14
13
|
before_install:
|
14
|
+
- gem update bundler
|
15
15
|
- mysql -e "create database IF NOT EXISTS shinq_test;" -uroot
|
16
16
|
script: bundle exec rspec
|
data/README.md
CHANGED
@@ -58,6 +58,7 @@ class CreateWorkerNames < ActiveRecord::Migration
|
|
58
58
|
create_table :worker_names, id: false, options: "ENGINE=QUEUE" do |t|
|
59
59
|
t.string :job_id, null: false
|
60
60
|
t.string :title
|
61
|
+
t.integer :scheduled_at, limit: 8, default: 0, null: false
|
61
62
|
t.datetime :enqueued_at, null: false
|
62
63
|
end
|
63
64
|
end
|
@@ -78,6 +79,7 @@ mysql> show create table worker_names\G
|
|
78
79
|
Create Table: CREATE TABLE `worker_names` (
|
79
80
|
`job_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
|
80
81
|
`title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
|
82
|
+
`scheduled_at` bigint(20) NOT NULL DEFAULT '0',
|
81
83
|
`enqueued_at` datetime NOT NULL
|
82
84
|
) ENGINE=QUEUE DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
|
83
85
|
```
|
@@ -5,6 +5,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
|
|
5
5
|
<% attributes.each do |attribute| -%>
|
6
6
|
t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
|
7
7
|
<% end -%>
|
8
|
+
t.integer :scheduled_at, limit: 8, default: 0, null: false
|
8
9
|
t.datetime :enqueued_at, null: false
|
9
10
|
end
|
10
11
|
end
|
data/lib/shinq.rb
CHANGED
@@ -24,12 +24,20 @@ module Shinq
|
|
24
24
|
|
25
25
|
def self.setup_db_connection(db_name)
|
26
26
|
raise Shinq::ConfigurationError unless self.configuration.db_defined?(db_name)
|
27
|
-
@connections[db_name] = Mysql2::Client.new(self.configuration.db_config[db_name])
|
27
|
+
@connections[db_name.to_sym] = Mysql2::Client.new(self.configuration.db_config[db_name])
|
28
28
|
end
|
29
29
|
|
30
30
|
def self.connection(db_name: self.default_db)
|
31
31
|
@connections ||= {}
|
32
|
-
@connections[db_name] ||= setup_db_connection(db_name)
|
32
|
+
@connections[db_name.to_sym] ||= setup_db_connection(db_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.clear_all_connections!
|
36
|
+
return unless @connections
|
37
|
+
@connections.each do |_db_name, connection|
|
38
|
+
connection && connection.close
|
39
|
+
end
|
40
|
+
@connections = {}
|
33
41
|
end
|
34
42
|
|
35
43
|
def self.logger
|
@@ -1,6 +1,14 @@
|
|
1
1
|
module ActiveJob
|
2
2
|
module QueueAdapters
|
3
3
|
class ShinqAdapter
|
4
|
+
def enqueue(job)
|
5
|
+
self.class.enqueue job
|
6
|
+
end
|
7
|
+
|
8
|
+
def enqueue_at(job, timestamp)
|
9
|
+
self.class.enqueue_at job, timestamp
|
10
|
+
end
|
11
|
+
|
4
12
|
class << self
|
5
13
|
def enqueue(job)
|
6
14
|
Shinq::Client.enqueue(
|
@@ -9,6 +17,15 @@ module ActiveJob
|
|
9
17
|
args: job.arguments.first
|
10
18
|
)
|
11
19
|
end
|
20
|
+
|
21
|
+
def enqueue_at(job, timestamp)
|
22
|
+
Shinq::Client.enqueue(
|
23
|
+
table_name: job.queue_name,
|
24
|
+
job_id: job.job_id,
|
25
|
+
args: job.arguments.first,
|
26
|
+
scheduled_at: timestamp,
|
27
|
+
)
|
28
|
+
end
|
12
29
|
end
|
13
30
|
end
|
14
31
|
end
|
data/lib/shinq/cli.rb
CHANGED
@@ -15,6 +15,7 @@ module Shinq
|
|
15
15
|
def initialize(args=ARGV)
|
16
16
|
setup_option(args)
|
17
17
|
bootstrap
|
18
|
+
initialize_shinq
|
18
19
|
end
|
19
20
|
|
20
21
|
def setup_option(args)
|
@@ -31,10 +32,7 @@ module Shinq
|
|
31
32
|
end
|
32
33
|
|
33
34
|
opt.on('-w', '--worker VALUE', 'Name of worker class') do |v|
|
34
|
-
worker_class = v.camelize.safe_constantize
|
35
|
-
raise OptionParseError, "worker class #{v.camelize} corresponding to #{v} does not exist" unless worker_class
|
36
35
|
opts[:worker_name] = v
|
37
|
-
opts[:worker_class] = worker_class
|
38
36
|
end
|
39
37
|
|
40
38
|
opt.on('-p', '--process VALUE', 'Number of workers') do |v|
|
@@ -75,6 +73,10 @@ module Shinq
|
|
75
73
|
opts[:abort_on_error] = v
|
76
74
|
end
|
77
75
|
|
76
|
+
opt.on('--sleep-sec-on-error N', Integer, 'Allow worker to sleep(sec) after exception') do |v|
|
77
|
+
opts[:sleep_sec_on_error] = v
|
78
|
+
end
|
79
|
+
|
78
80
|
opt.on('-v', '--version', 'Print version') do |v|
|
79
81
|
puts "Shinq #{Shinq::VERSION}"
|
80
82
|
exit(0)
|
@@ -104,6 +106,11 @@ module Shinq
|
|
104
106
|
end
|
105
107
|
end
|
106
108
|
|
109
|
+
def initialize_shinq
|
110
|
+
Shinq::Client.fetch_column_names(table_name: Shinq.configuration.worker_name.pluralize)
|
111
|
+
Shinq.configuration.worker_class # check if worker_class is constantizable before running ServerEngine
|
112
|
+
end
|
113
|
+
|
107
114
|
def run
|
108
115
|
klass = !options.statistics.nil? && options.statistics ? Shinq::Statistics : Shinq::Launcher
|
109
116
|
|
@@ -112,8 +119,8 @@ module Shinq
|
|
112
119
|
worker_type: 'process',
|
113
120
|
pid_file: 'shinq.pid',
|
114
121
|
workers: options.process,
|
115
|
-
worker_graceful_kill_timeout: options.graceful_kill_timeout
|
116
|
-
logger: options.daemonize ? Shinq.logger : nil
|
122
|
+
worker_graceful_kill_timeout: options.graceful_kill_timeout,
|
123
|
+
logger: options.daemonize ? Shinq.logger : nil,
|
117
124
|
})
|
118
125
|
|
119
126
|
se.run
|
data/lib/shinq/client.rb
CHANGED
@@ -8,13 +8,19 @@ module Shinq
|
|
8
8
|
@builder ||= SQL::Maker.new(driver: 'mysql', auto_bind: true)
|
9
9
|
end
|
10
10
|
|
11
|
-
def self.enqueue(table_name: , job_id: , args:)
|
11
|
+
def self.enqueue(table_name: , job_id: , args:, scheduled_at: nil)
|
12
|
+
if scheduled_at && !schedulable?(table_name: table_name)
|
13
|
+
raise ArgumentError, "table #{table_name} is not schedulable. You need column `scheduled_at`"
|
14
|
+
end
|
15
|
+
|
12
16
|
case args
|
13
17
|
when Hash
|
14
|
-
|
18
|
+
attributes = args.merge(
|
15
19
|
job_id: job_id,
|
16
|
-
|
17
|
-
|
20
|
+
scheduled_at: scheduled_at ? scheduled_at.to_i : nil,
|
21
|
+
enqueued_at: Time.now,
|
22
|
+
).compact
|
23
|
+
sql = builder.insert(table_name, attributes)
|
18
24
|
Shinq.connection.query(sql)
|
19
25
|
else
|
20
26
|
raise ArgumentError, "`args` should be a Hash"
|
@@ -22,7 +28,9 @@ module Shinq
|
|
22
28
|
end
|
23
29
|
|
24
30
|
def self.dequeue(table_name:)
|
25
|
-
|
31
|
+
condition = schedulable?(table_name: table_name) ? ":scheduled_at<=#{Time.now.to_i}" : ''
|
32
|
+
quoted = SQL::Maker::Quoting.quote("#{table_name}#{condition}")
|
33
|
+
|
26
34
|
queue_timeout_quoted = SQL::Maker::Quoting.quote(Shinq.configuration.queue_timeout)
|
27
35
|
|
28
36
|
wait_query = "queue_wait(#{quoted}, #{queue_timeout_quoted})"
|
@@ -52,6 +60,26 @@ module Shinq
|
|
52
60
|
)
|
53
61
|
end
|
54
62
|
|
63
|
+
def self.schedulable?(table_name:)
|
64
|
+
self.column_names(table_name: table_name).include?('scheduled_at')
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.column_names(table_name:)
|
68
|
+
@column_names_by_table_name ||= {}
|
69
|
+
@column_names_by_table_name[table_name.to_sym] ||= begin
|
70
|
+
quoted = SQL::Maker::Quoting.quote(table_name)
|
71
|
+
column = Shinq.connection.query(<<-EOS).map { |record| record['column_name'] }
|
72
|
+
select column_name from information_schema.columns where table_schema = database() and table_name = #{quoted}
|
73
|
+
EOS
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.fetch_column_names(table_name:)
|
78
|
+
@column_names_by_table_name ||= {}
|
79
|
+
@column_names_by_table_name.delete(table_name.to_sym)
|
80
|
+
column_names(table_name: table_name)
|
81
|
+
end
|
82
|
+
|
55
83
|
def self.done
|
56
84
|
Shinq.connection.query('select queue_end()')
|
57
85
|
end
|
data/lib/shinq/configuration.rb
CHANGED
@@ -10,7 +10,7 @@ module Shinq
|
|
10
10
|
# You may need to set it +false+ for jobs which take very long time to proceed.
|
11
11
|
# You may also need to handle performing error manually then.
|
12
12
|
class Configuration
|
13
|
-
attr_accessor :require, :worker_name, :
|
13
|
+
attr_accessor :require, :worker_name, :db_config, :queue_db, :default_db, :process, :graceful_kill_timeout, :queue_timeout, :daemonize, :statistics, :lifecycle, :abort_on_error, :sleep_sec_on_error
|
14
14
|
|
15
15
|
DEFAULT = {
|
16
16
|
require: '.',
|
@@ -18,15 +18,25 @@ module Shinq
|
|
18
18
|
graceful_kill_timeout: 600,
|
19
19
|
queue_timeout: 1,
|
20
20
|
daemonize: false,
|
21
|
-
abort_on_error: true
|
21
|
+
abort_on_error: true,
|
22
|
+
sleep_sec_on_error: 1,
|
22
23
|
}
|
23
24
|
|
24
25
|
def initialize(opts)
|
25
|
-
%i(require worker_name db_config queue_db default_db process queue_timeout daemonize statistics lifecycle abort_on_error).each do |k|
|
26
|
-
|
26
|
+
%i(require worker_name db_config queue_db default_db process queue_timeout daemonize statistics lifecycle abort_on_error sleep_sec_on_error).each do |k|
|
27
|
+
value = opts.key?(k) ? opts[k] : DEFAULT[k]
|
28
|
+
send(:"#{k}=", value)
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
32
|
+
def worker_class
|
33
|
+
worker_class = worker_name.camelize.safe_constantize
|
34
|
+
unless worker_class
|
35
|
+
raise ConfigurationError, "worker class #{worker_name.camelize} corresponding to #{worker_name} does not exist"
|
36
|
+
end
|
37
|
+
worker_class
|
38
|
+
end
|
39
|
+
|
30
40
|
def default_db_config
|
31
41
|
raise ConfigurationError if !(default_db && db_defined?(default_db))
|
32
42
|
db_config[default_db]
|
data/lib/shinq/launcher.rb
CHANGED
@@ -3,6 +3,10 @@ require 'active_support/inflector'
|
|
3
3
|
|
4
4
|
module Shinq
|
5
5
|
module Launcher
|
6
|
+
def initialize
|
7
|
+
Shinq.clear_all_connections!
|
8
|
+
end
|
9
|
+
|
6
10
|
# Wait configured queue and proceed each of them until stop.
|
7
11
|
# @see Shinq::Configuration#abort_on_error
|
8
12
|
def run
|
@@ -12,21 +16,26 @@ module Shinq
|
|
12
16
|
@loop_count = 0
|
13
17
|
|
14
18
|
until @stop
|
15
|
-
|
16
|
-
|
19
|
+
begin
|
20
|
+
queue = Shinq::Client.dequeue(table_name: worker_name.pluralize)
|
21
|
+
next Shinq.logger.info("Queue is empty (#{Time.now})") unless queue
|
17
22
|
|
18
|
-
|
19
|
-
|
23
|
+
if Shinq.configuration.abort_on_error
|
24
|
+
worker_class.new.perform(queue)
|
25
|
+
Shinq::Client.done
|
26
|
+
else
|
27
|
+
Shinq::Client.done
|
20
28
|
worker_class.new.perform(queue)
|
21
|
-
rescue => e
|
22
|
-
Shinq::Client.abort
|
23
|
-
raise e
|
24
29
|
end
|
25
30
|
|
26
|
-
Shinq
|
27
|
-
|
28
|
-
Shinq
|
29
|
-
|
31
|
+
Shinq.clear_all_connections!
|
32
|
+
rescue => e
|
33
|
+
Shinq.logger.error(format_error_message(e))
|
34
|
+
sleep Shinq.configuration.sleep_sec_on_error
|
35
|
+
|
36
|
+
Shinq::Client.abort if Shinq.configuration.abort_on_error && queue
|
37
|
+
Shinq.clear_all_connections!
|
38
|
+
break
|
30
39
|
end
|
31
40
|
|
32
41
|
@loop_count += 1
|
@@ -46,5 +55,16 @@ module Shinq
|
|
46
55
|
return false unless Shinq.configuration.lifecycle
|
47
56
|
return (Shinq.configuration.lifecycle < @loop_count)
|
48
57
|
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def format_error_message(error)
|
62
|
+
if defined?(::Rails) && ::Rails.backtrace_cleaner
|
63
|
+
backtrace = ::Rails.backtrace_cleaner.clean(error.backtrace || []).presence || error.backtrace
|
64
|
+
else
|
65
|
+
backtrace = error.backtrace
|
66
|
+
end
|
67
|
+
"#{error.message} at #{backtrace.join(' <<< ')}"
|
68
|
+
end
|
49
69
|
end
|
50
70
|
end
|
data/shinq.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "shinq"
|
7
|
-
spec.version = '0.
|
7
|
+
spec.version = '1.0.2'
|
8
8
|
spec.authors = ["Ryoichi SEKIGUCHI"]
|
9
9
|
spec.email = ["ryopeko@gmail.com"]
|
10
10
|
spec.summary = %q{Worker and enqueuer for Q4M using the interface of ActiveJob.}
|
@@ -16,17 +16,18 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
|
-
spec.add_development_dependency "bundler"
|
19
|
+
spec.add_development_dependency "bundler"
|
20
20
|
spec.add_development_dependency "rake", "~> 10.0"
|
21
21
|
spec.add_development_dependency "pry"
|
22
22
|
spec.add_development_dependency "tapp"
|
23
23
|
spec.add_development_dependency "rspec"
|
24
24
|
spec.add_development_dependency "rspec-mocks"
|
25
25
|
spec.add_development_dependency "simplecov"
|
26
|
+
spec.add_development_dependency "timecop"
|
26
27
|
|
27
|
-
spec.add_dependency "mysql2", ">= 0.3.16", "< 0.
|
28
|
+
spec.add_dependency "mysql2", ">= 0.3.16", "< 0.6"
|
28
29
|
spec.add_dependency "sql-maker", "~> 0.0.4"
|
29
|
-
spec.add_dependency "activesupport", "
|
30
|
-
spec.add_dependency "activejob", "
|
30
|
+
spec.add_dependency "activesupport", ">= 4.2.0", "< 6"
|
31
|
+
spec.add_dependency "activejob", ">= 4.2.0", "< 6"
|
31
32
|
spec.add_dependency 'serverengine', '~> 1.5.9'
|
32
33
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
DROP TABLE IF EXISTS `queue_test`;
|
2
|
+
CREATE TABLE `queue_test` (
|
3
|
+
`job_id` varchar(255) NOT NULL,
|
4
|
+
`title` varchar(255),
|
5
|
+
`scheduled_at` bigint(20) NOT NULL DEFAULT '0',
|
6
|
+
`enqueued_at` datetime NOT NULL
|
7
|
+
) ENGINE=<%= engine %>;
|
8
|
+
|
9
|
+
DROP TABLE IF EXISTS `queue_test_without_scheduled_at`;
|
10
|
+
|
11
|
+
CREATE TABLE `queue_test_without_scheduled_at` (
|
12
|
+
`job_id` varchar(255) NOT NULL,
|
13
|
+
`title` varchar(255),
|
14
|
+
`enqueued_at` datetime NOT NULL
|
15
|
+
) ENGINE=<%= engine %>;
|
data/spec/integration_spec.rb
CHANGED
@@ -3,7 +3,6 @@ require 'shinq'
|
|
3
3
|
require 'shinq/client'
|
4
4
|
|
5
5
|
describe "Integration", skip: ENV['TRAVIS'] do
|
6
|
-
let(:queue_table) { 'queue_test' }
|
7
6
|
|
8
7
|
before do
|
9
8
|
Shinq.configuration = {
|
@@ -13,28 +12,97 @@ describe "Integration", skip: ENV['TRAVIS'] do
|
|
13
12
|
end
|
14
13
|
|
15
14
|
after do
|
16
|
-
Shinq.
|
15
|
+
Shinq.clear_all_connections! # Return from owner mode
|
16
|
+
tables = Shinq.connection.query('show tables').flat_map(&:values)
|
17
|
+
tables.each do |table|
|
18
|
+
Shinq.connection.query("delete from #{table}")
|
19
|
+
end
|
20
|
+
Shinq.clear_all_connections!
|
17
21
|
end
|
18
22
|
|
19
23
|
describe "Shinq::Client.enqueue,dequeue" do
|
20
|
-
|
21
|
-
|
24
|
+
before do
|
25
|
+
Timecop.freeze
|
26
|
+
end
|
27
|
+
after do
|
28
|
+
Timecop.return
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'with scheduled_at on table' do
|
32
|
+
let(:queue_table) { 'queue_test' }
|
33
|
+
let(:job_id) { SecureRandom.uuid }
|
22
34
|
|
23
35
|
before do
|
24
36
|
Shinq::Client.enqueue(
|
25
37
|
table_name: queue_table,
|
26
|
-
job_id:
|
27
|
-
args:
|
38
|
+
job_id: job_id,
|
39
|
+
args: { title: :foo },
|
40
|
+
scheduled_at: scheduled_at
|
28
41
|
)
|
42
|
+
end
|
29
43
|
|
30
|
-
|
31
|
-
|
44
|
+
context 'when scheduled_at is not specified' do
|
45
|
+
let(:scheduled_at) { nil }
|
46
|
+
|
47
|
+
it 'can dequeue immediately' do
|
48
|
+
expect(Shinq::Client.dequeue(table_name: queue_table)[:job_id]).to eq job_id
|
49
|
+
end
|
32
50
|
end
|
33
51
|
|
34
|
-
|
52
|
+
context 'when scheduled_at is specified' do
|
53
|
+
let(:scheduled_at) { 1.minute.since }
|
54
|
+
|
55
|
+
it 'can not dequeue job immediately' do
|
56
|
+
expect(Shinq::Client.dequeue(table_name: queue_table)).to be nil
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when specified time elapsed' do
|
60
|
+
before do
|
61
|
+
Timecop.travel(scheduled_at)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'can dequeue job' do
|
65
|
+
expect(Shinq::Client.dequeue(table_name: queue_table)[:job_id]).to eq job_id
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
35
69
|
end
|
36
70
|
|
37
|
-
context
|
71
|
+
context 'without scheduled_at on table' do
|
72
|
+
let(:job_id) { SecureRandom.uuid }
|
73
|
+
let(:unschedulable_queue_table) { 'queue_test_without_scheduled_at' }
|
74
|
+
|
75
|
+
context 'when scheduled_at is not specified' do
|
76
|
+
before do
|
77
|
+
Shinq::Client.enqueue(
|
78
|
+
table_name: unschedulable_queue_table,
|
79
|
+
job_id: job_id,
|
80
|
+
args: { title: :foo },
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'can dequeue job' do
|
85
|
+
expect(Shinq::Client.dequeue(table_name: unschedulable_queue_table)[:job_id]).to eq job_id
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'when scheduled_at is specified' do
|
90
|
+
it 'cannot enqueue job' do
|
91
|
+
expect {
|
92
|
+
Shinq::Client.enqueue(
|
93
|
+
table_name: unschedulable_queue_table,
|
94
|
+
job_id: job_id,
|
95
|
+
args: { title: :foo },
|
96
|
+
scheduled_at: 1.minute.since,
|
97
|
+
)
|
98
|
+
}.to raise_error ArgumentError
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
context "with invalid args" do
|
105
|
+
let(:queue_table) { 'queue_test' }
|
38
106
|
it {
|
39
107
|
expect {
|
40
108
|
Shinq::Client.enqueue(
|
@@ -48,6 +116,8 @@ describe "Integration", skip: ENV['TRAVIS'] do
|
|
48
116
|
end
|
49
117
|
|
50
118
|
describe "Shinq::Client.abort" do
|
119
|
+
let(:queue_table) { 'queue_test' }
|
120
|
+
|
51
121
|
context "When client has a queue" do
|
52
122
|
before do
|
53
123
|
Shinq::Client.enqueue(
|
@@ -77,6 +147,8 @@ describe "Integration", skip: ENV['TRAVIS'] do
|
|
77
147
|
end
|
78
148
|
|
79
149
|
describe "Shinq::Client.queue_stats" do
|
150
|
+
let(:queue_table) { 'queue_test' }
|
151
|
+
|
80
152
|
subject(:stats) {
|
81
153
|
Shinq::Client.queue_stats(table_name: queue_table)
|
82
154
|
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'shinq/cli'
|
3
|
+
|
4
|
+
describe Shinq::CLI do
|
5
|
+
after do
|
6
|
+
Shinq.clear_all_connections!
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '.new' do
|
10
|
+
context 'when there are require statement' do
|
11
|
+
it 'requires and run the code' do
|
12
|
+
# NOTE: As CLI alters global process irreversibly, we only check the result
|
13
|
+
Shinq::CLI.new(%W[
|
14
|
+
--require my_worker
|
15
|
+
--db-config #{File.expand_path('../config/database.yml', __dir__)}
|
16
|
+
--worker my_worker
|
17
|
+
])
|
18
|
+
expect(defined? MyWorker).to eq 'constant'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#run' do
|
24
|
+
before do
|
25
|
+
allow_any_instance_of(ServerEngine::Daemon).to receive(:run).and_return(nil)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'launches Shinq::Launcher backended by ServerEngine' do
|
29
|
+
expect {
|
30
|
+
Shinq::CLI.new(%W[
|
31
|
+
--require my_worker
|
32
|
+
--db-config #{File.expand_path('../config/database.yml', __dir__)}
|
33
|
+
--worker my_worker
|
34
|
+
]).run
|
35
|
+
}.not_to raise_error
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'shinq'
|
3
|
+
require 'shinq/client'
|
4
|
+
|
5
|
+
describe Shinq::Client do
|
6
|
+
subject(:shinq_client) do
|
7
|
+
Shinq::Client.dup.tap do |client|
|
8
|
+
client.instance_variables.each do |variable|
|
9
|
+
client.remove_instance_variable(variable)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
before do
|
15
|
+
Shinq.configuration = {
|
16
|
+
default_db: :test,
|
17
|
+
db_config: load_database_config
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
after do
|
22
|
+
Shinq.clear_all_connections!
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '.schedulable?' do
|
26
|
+
context 'when target table have scheduled_at' do
|
27
|
+
it { expect(shinq_client.schedulable?(table_name: :queue_test)).to be true }
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when target table does NOT have scheduled_at' do
|
31
|
+
it { expect(shinq_client.schedulable?(table_name: :queue_test_without_scheduled_at)).to be false }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '.column_names' do
|
36
|
+
it 'fetches column_names' do
|
37
|
+
expect(shinq_client.column_names(table_name: :queue_test)).to eq(['job_id', 'title', 'scheduled_at', 'enqueued_at'])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'fetch_column_names' do
|
42
|
+
it 'fetches column_names' do
|
43
|
+
expect(shinq_client.fetch_column_names(table_name: :queue_test)).to eq(['job_id', 'title', 'scheduled_at', 'enqueued_at'])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -33,6 +33,21 @@ describe Shinq::Configuration do
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
describe '#worker_class' do
|
37
|
+
context 'when worker_name is valid' do
|
38
|
+
let(:configuration) { Shinq::Configuration.new(worker_name: 'shinq') }
|
39
|
+
it 'constantizes worker_name to corresponding constant' do
|
40
|
+
expect(configuration.worker_class).to eq Shinq
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when worker_name is invalid' do
|
45
|
+
let(:configuration) { Shinq::Configuration.new(worker_name: 'invalid_shinq') }
|
46
|
+
|
47
|
+
it {expect { configuration.worker_class }.to raise_error(Shinq::ConfigurationError)}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
36
51
|
describe "#default_db_config" do
|
37
52
|
context "when default_db is present" do
|
38
53
|
let(:configuration) { Shinq::Configuration.new({}) }
|
data/spec/shinq_spec.rb
CHANGED
@@ -3,29 +3,32 @@ require 'shinq'
|
|
3
3
|
require 'shinq/configuration'
|
4
4
|
require 'logger'
|
5
5
|
|
6
|
-
def shinq_class
|
7
|
-
Shinq.dup
|
8
|
-
end
|
9
|
-
|
10
6
|
describe Shinq do
|
11
|
-
|
7
|
+
# remove instance variable deliberately or indeliberately defined by other specs
|
8
|
+
subject(:shinq) do
|
9
|
+
Shinq.dup.tap do |shinq|
|
10
|
+
shinq.instance_variables.each do |variable|
|
11
|
+
shinq.remove_instance_variable(variable)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
shinq.clear_all_connections!
|
18
|
+
end
|
12
19
|
|
13
20
|
it { is_expected.to respond_to(:configuration) }
|
14
21
|
it { is_expected.to respond_to(:configuration=) }
|
15
22
|
|
16
23
|
describe ".configuration" do
|
17
24
|
context "when configuration is not present" do
|
18
|
-
let(:shinq) { shinq_class }
|
19
|
-
|
20
25
|
it { expect(shinq.configuration).to be_a_kind_of(Shinq::Configuration) }
|
21
26
|
end
|
22
27
|
|
23
28
|
context "when configuration is present" do
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
}
|
28
|
-
}
|
29
|
+
before do
|
30
|
+
shinq.configuration = Hash.new
|
31
|
+
end
|
29
32
|
|
30
33
|
it { expect(shinq.configuration).to be_a_kind_of(Shinq::Configuration) }
|
31
34
|
end
|
@@ -33,7 +36,6 @@ describe Shinq do
|
|
33
36
|
|
34
37
|
describe ".configuration=" do
|
35
38
|
context "when specify args is Hash" do
|
36
|
-
let(:shinq) { shinq_class }
|
37
39
|
let(:args) { Hash.new }
|
38
40
|
|
39
41
|
it 'is expect to return specified args' do
|
@@ -42,7 +44,6 @@ describe Shinq do
|
|
42
44
|
end
|
43
45
|
|
44
46
|
context "when specify args is Shinq::Configuration" do
|
45
|
-
let(:shinq) { shinq_class }
|
46
47
|
let(:args) { Shinq::Configuration.new({}) }
|
47
48
|
|
48
49
|
it 'is expect to return specified args' do
|
@@ -52,39 +53,56 @@ describe Shinq do
|
|
52
53
|
end
|
53
54
|
|
54
55
|
describe ".connection" do
|
56
|
+
context "when db_config is not present" do
|
57
|
+
it { expect{ shinq.connection(db_name: :unknown) }.to raise_error(Shinq::ConfigurationError) }
|
58
|
+
end
|
59
|
+
|
55
60
|
context "when db_config is present" do
|
56
|
-
|
61
|
+
before do
|
62
|
+
shinq.configuration = { db_config: load_database_config, default_db: :test }
|
63
|
+
end
|
57
64
|
|
58
|
-
it
|
65
|
+
it do
|
66
|
+
shinq.connection(db_name: :test)
|
67
|
+
expect(shinq.connection(db_name: :test)).to be_a_kind_of(Mysql2::Client)
|
68
|
+
end
|
59
69
|
end
|
70
|
+
end
|
60
71
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
+
describe '.clear_all_connections!' do
|
73
|
+
before do
|
74
|
+
shinq.configuration = { db_config: load_database_config, default_db: :test }
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'when there are no connections' do
|
78
|
+
it { expect { shinq.clear_all_connections! }.not_to raise_error }
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'when there is a connection' do
|
82
|
+
let!(:connection) { shinq.connection(db_name: :test) }
|
83
|
+
|
84
|
+
it 'closes connection' do
|
85
|
+
shinq.clear_all_connections!
|
86
|
+
expect(connection.ping).to be false
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'clears connection cache' do
|
90
|
+
shinq.clear_all_connections!
|
91
|
+
expect(shinq.connection(db_name: :test)).not_to eq connection
|
92
|
+
end
|
72
93
|
end
|
73
94
|
end
|
74
95
|
|
75
96
|
describe ".logger" do
|
76
|
-
context "when logger is present" do
|
77
|
-
let(:shinq) { shinq_class }
|
97
|
+
context "when logger is not present" do
|
78
98
|
it { expect(shinq.logger).to be nil }
|
79
99
|
end
|
80
100
|
|
81
101
|
context "when logger is present" do
|
82
102
|
let(:logger) { Logger.new(STDOUT) }
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
}
|
87
|
-
}
|
103
|
+
before do
|
104
|
+
shinq.logger = logger
|
105
|
+
end
|
88
106
|
|
89
107
|
it { expect(shinq.logger).to be logger }
|
90
108
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
+
ENV['RAILS_ENV'] ||= 'test'
|
2
|
+
$LOAD_PATH << File.expand_path('../helpers', __FILE__)
|
3
|
+
|
1
4
|
require 'rspec/mocks/standalone'
|
2
5
|
require 'simplecov'
|
6
|
+
require 'erb'
|
3
7
|
require 'yaml'
|
4
8
|
require 'active_support/core_ext/hash'
|
9
|
+
require 'mysql2'
|
10
|
+
require 'timecop'
|
5
11
|
|
6
12
|
def load_database_config
|
7
13
|
db_config = YAML.load_file(File.expand_path('./config/database.yml', __dir__)).symbolize_keys
|
@@ -25,8 +31,19 @@ RSpec.configure do |config|
|
|
25
31
|
end
|
26
32
|
|
27
33
|
config.before(:suite) do
|
34
|
+
# MySQL on Travis does not have Q4M plugins.
|
35
|
+
# We use QUEUE engine and run Q4M specific spec (integration_spec) only when ENV['TRAVIS'] is nil.
|
36
|
+
engine = ENV['TRAVIS'] ? 'InnoDB' : 'QUEUE' # Travis MySQL does not have Q4M plugins.
|
37
|
+
sql = ERB.new(File.read(File.expand_path('./db/structure.sql.erb', __dir__))).result(binding)
|
38
|
+
|
28
39
|
connection = Mysql2::Client.new(load_database_config[:test].merge(flags: Mysql2::Client::MULTI_STATEMENTS))
|
29
|
-
connection.query(
|
40
|
+
result = connection.query(sql)
|
41
|
+
|
42
|
+
while connection.next_result
|
43
|
+
connection.store_result
|
44
|
+
end
|
45
|
+
|
46
|
+
connection.close
|
30
47
|
end
|
31
48
|
|
32
49
|
config.order = :random
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shinq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryoichi SEKIGUCHI
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-10-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: timecop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: mysql2
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -117,7 +131,7 @@ dependencies:
|
|
117
131
|
version: 0.3.16
|
118
132
|
- - "<"
|
119
133
|
- !ruby/object:Gem::Version
|
120
|
-
version: '0.
|
134
|
+
version: '0.6'
|
121
135
|
type: :runtime
|
122
136
|
prerelease: false
|
123
137
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -127,7 +141,7 @@ dependencies:
|
|
127
141
|
version: 0.3.16
|
128
142
|
- - "<"
|
129
143
|
- !ruby/object:Gem::Version
|
130
|
-
version: '0.
|
144
|
+
version: '0.6'
|
131
145
|
- !ruby/object:Gem::Dependency
|
132
146
|
name: sql-maker
|
133
147
|
requirement: !ruby/object:Gem::Requirement
|
@@ -146,30 +160,42 @@ dependencies:
|
|
146
160
|
name: activesupport
|
147
161
|
requirement: !ruby/object:Gem::Requirement
|
148
162
|
requirements:
|
149
|
-
- - "
|
163
|
+
- - ">="
|
150
164
|
- !ruby/object:Gem::Version
|
151
165
|
version: 4.2.0
|
166
|
+
- - "<"
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '6'
|
152
169
|
type: :runtime
|
153
170
|
prerelease: false
|
154
171
|
version_requirements: !ruby/object:Gem::Requirement
|
155
172
|
requirements:
|
156
|
-
- - "
|
173
|
+
- - ">="
|
157
174
|
- !ruby/object:Gem::Version
|
158
175
|
version: 4.2.0
|
176
|
+
- - "<"
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '6'
|
159
179
|
- !ruby/object:Gem::Dependency
|
160
180
|
name: activejob
|
161
181
|
requirement: !ruby/object:Gem::Requirement
|
162
182
|
requirements:
|
163
|
-
- - "
|
183
|
+
- - ">="
|
164
184
|
- !ruby/object:Gem::Version
|
165
185
|
version: 4.2.0
|
186
|
+
- - "<"
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: '6'
|
166
189
|
type: :runtime
|
167
190
|
prerelease: false
|
168
191
|
version_requirements: !ruby/object:Gem::Requirement
|
169
192
|
requirements:
|
170
|
-
- - "
|
193
|
+
- - ">="
|
171
194
|
- !ruby/object:Gem::Version
|
172
195
|
version: 4.2.0
|
196
|
+
- - "<"
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: '6'
|
173
199
|
- !ruby/object:Gem::Dependency
|
174
200
|
name: serverengine
|
175
201
|
requirement: !ruby/object:Gem::Requirement
|
@@ -214,8 +240,11 @@ files:
|
|
214
240
|
- lib/shinq/statistics.rb
|
215
241
|
- shinq.gemspec
|
216
242
|
- spec/config/database.yml
|
217
|
-
- spec/db/structure.sql
|
243
|
+
- spec/db/structure.sql.erb
|
244
|
+
- spec/helpers/my_worker.rb
|
218
245
|
- spec/integration_spec.rb
|
246
|
+
- spec/shinq/cli_spec.rb
|
247
|
+
- spec/shinq/client_spec.rb
|
219
248
|
- spec/shinq/configuration_spec.rb
|
220
249
|
- spec/shinq_spec.rb
|
221
250
|
- spec/spec_helper.rb
|
@@ -238,15 +267,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
238
267
|
- !ruby/object:Gem::Version
|
239
268
|
version: '0'
|
240
269
|
requirements: []
|
241
|
-
|
242
|
-
rubygems_version: 2.6.8
|
270
|
+
rubygems_version: 3.0.3
|
243
271
|
signing_key:
|
244
272
|
specification_version: 4
|
245
273
|
summary: Worker and enqueuer for Q4M using the interface of ActiveJob.
|
246
274
|
test_files:
|
247
275
|
- spec/config/database.yml
|
248
|
-
- spec/db/structure.sql
|
276
|
+
- spec/db/structure.sql.erb
|
277
|
+
- spec/helpers/my_worker.rb
|
249
278
|
- spec/integration_spec.rb
|
279
|
+
- spec/shinq/cli_spec.rb
|
280
|
+
- spec/shinq/client_spec.rb
|
250
281
|
- spec/shinq/configuration_spec.rb
|
251
282
|
- spec/shinq_spec.rb
|
252
283
|
- spec/spec_helper.rb
|