skiplock 1.0.7 → 1.0.8
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/README.md +5 -9
- data/lib/generators/skiplock/templates/migration.rb.erb +1 -1
- data/lib/skiplock/dispatcher.rb +89 -76
- data/lib/skiplock/manager.rb +5 -2
- data/lib/skiplock/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ee6a8ff68af1a1029a26db98867457a798c4fb6aacf0a2a2816efd3ef2b977b
|
4
|
+
data.tar.gz: 2c75fb21c79346f4525b8634b28fb326505acb7ab61795fe7d291c3faa75244f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c34fa2c27f9fc8bcfbe6c54a30de8645418dfe7c13e8db9ea8c30352f75abd9ab9b04d1a294aea7f2a9f33772d9cc5ed96839bd63d1eacbda17749758b4755b2
|
7
|
+
data.tar.gz: 03cdbef1a70a64fb188258dc44049f9323f53cb9c2e64f8a79a9c8a4a17ca8aea2e7ca718ceb3bc78cc08fd61d6dbd5c3f104f653a9645132ef31f73d22883da
|
data/README.md
CHANGED
@@ -72,7 +72,7 @@ The library is quite small compared to other PostgreSQL job queues (eg. *delay_j
|
|
72
72
|
- **workers** (*integer*) sets the maximum number of processes when running in standalone mode using the `skiplock` executable; setting this to **0** will enable **async mode**
|
73
73
|
|
74
74
|
#### Async mode
|
75
|
-
When **workers** is set to **0** then the jobs will be performed in the web server process using separate threads. If using multi-worker cluster web server like Puma, then it should be configured as below:
|
75
|
+
When **workers** is set to **0** then the jobs will be performed in the web server process using separate threads. If using multi-worker cluster mode web server like Puma, then it should be configured as below:
|
76
76
|
```ruby
|
77
77
|
# config/puma.rb
|
78
78
|
before_fork do
|
@@ -80,14 +80,10 @@ The library is quite small compared to other PostgreSQL job queues (eg. *delay_j
|
|
80
80
|
Skiplock::Manager.shutdown
|
81
81
|
end
|
82
82
|
|
83
|
-
|
84
|
-
#
|
85
|
-
Skiplock
|
86
|
-
|
87
|
-
|
88
|
-
on_worker_shutdown do
|
89
|
-
# ...
|
90
|
-
Skiplock::Manager.shutdown
|
83
|
+
after_worker_fork do |worker_index|
|
84
|
+
# restarts skiplock after all Puma workers have been started
|
85
|
+
# Skiplock runs in Puma master's process only
|
86
|
+
Skiplock::Manager.start if (worker_index + 1) == @options[:workers]
|
91
87
|
end
|
92
88
|
```
|
93
89
|
|
@@ -36,7 +36,7 @@ class CreateSkiplockSchema < ActiveRecord::Migration<%= "[#{ActiveRecord::VERSIO
|
|
36
36
|
ELSE
|
37
37
|
record = NEW;
|
38
38
|
END IF;
|
39
|
-
PERFORM pg_notify('skiplock::jobs', CONCAT(TG_OP,',',record.id::TEXT,',',record.worker_id::TEXT,',',record.queue_name,',',record.running::TEXT,',',CAST(EXTRACT(EPOCH FROM record.expired_at) AS FLOAT)::TEXT,',',CAST(EXTRACT(EPOCH FROM record.finished_at) AS FLOAT)::TEXT,',',CAST(EXTRACT(EPOCH FROM record.scheduled_at) AS FLOAT)::TEXT));
|
39
|
+
PERFORM pg_notify('skiplock::jobs', CONCAT(TG_OP,',',record.id::TEXT,',',record.worker_id::TEXT,',',record.queue_name,',',record.running::TEXT,',',CAST(EXTRACT(EPOCH FROM record.expired_at) AS FLOAT)::TEXT,',',CAST(EXTRACT(EPOCH FROM record.finished_at) AS FLOAT)::TEXT,',',CAST(EXTRACT(EPOCH FROM CASE WHEN record.scheduled_at IS NULL THEN record.updated_at ELSE record.scheduled_at END) AS FLOAT)::TEXT));
|
40
40
|
RETURN NULL;
|
41
41
|
END;
|
42
42
|
$$ LANGUAGE plpgsql
|
data/lib/skiplock/dispatcher.rb
CHANGED
@@ -9,100 +9,112 @@ module Skiplock
|
|
9
9
|
else
|
10
10
|
@worker_num = worker_num
|
11
11
|
end
|
12
|
+
@last_dispatch_at = 0
|
12
13
|
@next_schedule_at = Time.now.to_f
|
13
14
|
@running = true
|
14
15
|
end
|
15
16
|
|
16
17
|
def run
|
17
18
|
Thread.new do
|
18
|
-
Rails.application.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
File.delete('tmp/cache/skiplock')
|
19
|
+
sleep(1) while @running && !Rails.application.initialized?
|
20
|
+
Process.setproctitle("skiplock-#{@master ? 'master[0]' : 'worker[' + @worker_num.to_s + ']'}") if Settings['workers'] > 0
|
21
|
+
ActiveRecord::Base.connection_pool.with_connection do |connection|
|
22
|
+
connection.exec_query('LISTEN "skiplock::jobs"')
|
23
|
+
hostname = `hostname -f`.strip
|
24
|
+
@worker = Worker.create!(pid: Process.pid, ppid: (@master ? nil : Process.ppid), capacity: Settings['max_threads'], hostname: hostname)
|
25
|
+
if @master
|
26
|
+
connection.exec_query('LISTEN "skiplock::workers"')
|
27
|
+
if File.exists?('tmp/cache/skiplock')
|
28
|
+
# get performed jobs that could not sync with database
|
29
|
+
job_ids = File.read('tmp/cache/skiplock').split("\n")
|
30
|
+
if Settings['purge_completion']
|
31
|
+
Job.where(id: job_ids).delete_all
|
32
|
+
else
|
33
|
+
Job.where(id: job_ids).update_all(running: false, finished_at: File.mtime('tmp/cache/skiplock'), updated_at: Time.now)
|
35
34
|
end
|
36
|
-
|
37
|
-
|
35
|
+
File.delete('tmp/cache/skiplock')
|
36
|
+
end
|
37
|
+
# get dead worker ids
|
38
|
+
dead_worker_ids = Worker.where(hostname: hostname).where.not(pid: @worker_pids).ids
|
39
|
+
if dead_worker_ids.count > 0
|
38
40
|
# reset orphaned jobs of the dead worker ids for retry
|
39
|
-
Job.where(running: true).where
|
40
|
-
# remove workers
|
41
|
-
Worker.where(
|
42
|
-
# reset retries schedules on startup
|
43
|
-
Job.where('scheduled_at > NOW() AND executions IS NOT NULL AND expired_at IS NULL AND finished_at IS NULL').update_all(scheduled_at: nil, updated_at: Time.now)
|
44
|
-
Cron.setup
|
41
|
+
Job.where(running: true).where(worker_id: dead_worker_ids).update_all(running: false, worker_id: nil)
|
42
|
+
# remove dead workers
|
43
|
+
Worker.where(id: dead_worker_ids).delete_all
|
45
44
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
45
|
+
# reset retries schedules on startup
|
46
|
+
Job.where('scheduled_at > NOW() AND executions IS NOT NULL AND expired_at IS NULL AND finished_at IS NULL').update_all(scheduled_at: nil, updated_at: Time.now)
|
47
|
+
Cron.setup
|
48
|
+
end
|
49
|
+
error = false
|
50
|
+
while @running
|
51
|
+
begin
|
52
|
+
if error
|
53
|
+
unless connection.active?
|
54
|
+
connection.reconnect!
|
55
|
+
sleep(0.5)
|
56
|
+
connection.exec_query('LISTEN "skiplock::jobs"')
|
57
|
+
connection.exec_query('LISTEN "skiplock::workers"') if @master
|
58
|
+
@next_schedule_at = Time.now.to_f
|
57
59
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
Job.where(id: orphaned_ids, running: true).update_all(running: false, worker_id: nil, scheduled_at: (Time.now + 10)) if orphaned_ids.count > 0
|
67
|
-
Job::Errors.clear
|
60
|
+
error = false
|
61
|
+
end
|
62
|
+
if Job::Errors.keys.count > 0
|
63
|
+
completed_ids = Job::Errors.keys.map { |k| k if Job::Errors[k] }.compact
|
64
|
+
if Settings['purge_completion'] && completed_ids.count > 0
|
65
|
+
Job.where(id: completed_ids, running: true).delete_all
|
66
|
+
elsif completed_ids.count > 0
|
67
|
+
Job.where(id: completed_ids, running: true).update_all(running: false, finished_at: Time.now, updated_at: Time.now)
|
68
68
|
end
|
69
|
-
|
70
|
-
|
69
|
+
orphaned_ids = Job::Errors.keys.map { |k| k unless Job::Errors[k] }.compact
|
70
|
+
Job.where(id: orphaned_ids, running: true).update_all(running: false, worker_id: nil, scheduled_at: (Time.now + 10), updated_at: Time.now) if orphaned_ids.count > 0
|
71
|
+
Job::Errors.clear
|
72
|
+
end
|
73
|
+
notifications = { 'skiplock::jobs' => [], 'skiplock::workers' => [] }
|
74
|
+
connection.raw_connection.wait_for_notify(0.1) do |channel, pid, payload|
|
75
|
+
notifications[channel] << payload if payload
|
76
|
+
loop do
|
77
|
+
payload = connection.raw_connection.notifies
|
78
|
+
break unless @running && payload
|
79
|
+
notifications[payload[:relname]] << payload[:extra]
|
71
80
|
end
|
72
|
-
notifications
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
payload = connection.raw_connection.notifies
|
77
|
-
break unless @running && payload
|
78
|
-
notifications << payload[:extra]
|
81
|
+
notifications['skiplock::jobs'].each do |n|
|
82
|
+
op, id, worker_id, queue_name, running, expired_at, finished_at, scheduled_at = n.split(',')
|
83
|
+
if @master
|
84
|
+
# TODO: report job status to action cable
|
79
85
|
end
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
elsif scheduled_at.to_f < @next_schedule_at
|
86
|
-
@next_schedule_at = scheduled_at.to_f
|
87
|
-
end
|
86
|
+
next if op == 'DELETE' || running == 'true' || expired_at.to_f > 0 || finished_at.to_f > 0 || scheduled_at.to_f < @last_dispatch_at
|
87
|
+
if scheduled_at.to_f < Time.now.to_f
|
88
|
+
@next_schedule_at = Time.now.to_f
|
89
|
+
elsif scheduled_at.to_f < @next_schedule_at
|
90
|
+
@next_schedule_at = scheduled_at.to_f
|
88
91
|
end
|
89
92
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
93
|
+
if @master
|
94
|
+
# TODO: report worker status to action cable
|
95
|
+
notifications['skiplock::workers'].each do |n|
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
if Time.now.to_f >= @next_schedule_at && @executor.remaining_capacity > 0
|
100
|
+
@executor.post { do_work }
|
101
|
+
end
|
102
|
+
rescue Exception => ex
|
103
|
+
# most likely error with database connection
|
104
|
+
STDERR.puts ex.message
|
105
|
+
STDERR.puts ex.backtrace
|
106
|
+
Skiplock.on_error.call(ex, @last_exception) if Skiplock.on_error.is_a?(Proc)
|
107
|
+
error = true
|
108
|
+
t = Time.now
|
109
|
+
while @running
|
110
|
+
sleep(0.5)
|
111
|
+
break if Time.now - t > 5
|
101
112
|
end
|
102
|
-
|
113
|
+
@last_exception = ex
|
103
114
|
end
|
104
|
-
|
115
|
+
sleep(0.2)
|
105
116
|
end
|
117
|
+
connection.exec_query('UNLISTEN *')
|
106
118
|
end
|
107
119
|
end
|
108
120
|
end
|
@@ -118,6 +130,7 @@ module Skiplock
|
|
118
130
|
|
119
131
|
def do_work
|
120
132
|
while @running
|
133
|
+
@last_dispatch_at = Time.now.to_f
|
121
134
|
result = Job.dispatch(queues_order_query: @queues_order_query, worker_id: @worker.id)
|
122
135
|
next if result.is_a?(Job)
|
123
136
|
@next_schedule_at = result if result.is_a?(Float)
|
data/lib/skiplock/manager.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Skiplock
|
2
2
|
class Manager
|
3
|
-
def self.start(standalone: false, workers: nil, max_retries: nil, max_threads: nil, min_threads: nil, logging: nil)
|
3
|
+
def self.start(standalone: false, restart: false, workers: nil, max_retries: nil, max_threads: nil, min_threads: nil, logging: nil)
|
4
4
|
unless Settings.frozen?
|
5
5
|
load_settings
|
6
6
|
Settings['logging'] = logging if logging
|
@@ -16,7 +16,7 @@ module Skiplock
|
|
16
16
|
Settings['workers'] = 0 if Settings['workers'] < 0
|
17
17
|
Settings.freeze
|
18
18
|
end
|
19
|
-
return unless standalone || (caller.any?{|l| l =~ %r{/rack/}} && (Settings['workers'] == 0 || Rails.env.development?))
|
19
|
+
return unless standalone || restart || (caller.any?{|l| l =~ %r{/rack/}} && (Settings['workers'] == 0 || Rails.env.development?))
|
20
20
|
if standalone
|
21
21
|
self.standalone
|
22
22
|
else
|
@@ -24,12 +24,15 @@ module Skiplock
|
|
24
24
|
@thread = @dispatcher.run
|
25
25
|
at_exit { self.shutdown }
|
26
26
|
end
|
27
|
+
ActiveJob::Base.logger = nil
|
27
28
|
end
|
28
29
|
|
29
30
|
def self.shutdown(wait: true)
|
30
31
|
if @dispatcher && @thread
|
31
32
|
@dispatcher.shutdown(wait: wait)
|
32
33
|
@thread.join
|
34
|
+
@dispatcher = nil
|
35
|
+
@thread = nil
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
data/lib/skiplock/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: skiplock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tin Vo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|