good_job 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3fb26ee806945cc3f179365b97150d570d9054c7907a94044f28a2afdd46d389
4
- data.tar.gz: ab7d3247d78ad394e729c4b4787157ea3b60b0eeb01d0a31a2c93694a3320a62
3
+ metadata.gz: 5a3b5327b3b95b67901f3be13858f0bbf9cf1d0818793ed653f084a77b8e6ff9
4
+ data.tar.gz: 056fd886d26d7ee60fd63d7f45714c7b843f7f4e6661b58a00dc4c6ec17d9b9c
5
5
  SHA512:
6
- metadata.gz: 8065515ccdf0533d6069688a2afba7ffdb71a2ba9ad45660fda62b3c6c8347eff8e7d6a0a735bfcd974176a1e1e2e1168efd5dc74aa6fbe155326cb33f7700c3
7
- data.tar.gz: 6e8877daa20867a5748b62a3e40130ee8a270c9c2db3ba1a8a460c76d33b9a48e6d8f127e779fe0808d245cc75276d36492adf7e70850640e729adbe2863e715
6
+ metadata.gz: 12221043ad332a684e062aae176d529bbd39da7998e9ae6d355bdb1f68f2f7ace21c4f4196ebe0306841a761161e564125f35c7522f30f3b2f718830bd15c788
7
+ data.tar.gz: 7d389ac1aafb614b813fdb1eb75ce7dceba40238111966d8031578ac88553d989cee08dcef03d72fd8e53ce3215692779e2e03c538ea90cb3a477361ef2442d8
@@ -1,14 +1,30 @@
1
1
  # Changelog
2
2
 
3
- ## [0.3.0](https://github.com/bensheldon/good_job/tree/0.3.0) (2020-03-22)
3
+ ## [0.4.0](https://github.com/bensheldon/good_job/tree/0.4.0) (2020-03-30)
4
4
 
5
- [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.2.1...0.3.0)
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.3.0...0.4.0)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - Improve ActiveRecord usage for advisory locking [\#24](https://github.com/bensheldon/good_job/pull/24) ([bensheldon](https://github.com/bensheldon))
10
+ - Remove support for Rails 5.1 [\#23](https://github.com/bensheldon/good_job/pull/23) ([bensheldon](https://github.com/bensheldon))
11
+
12
+ ## [v0.3.0](https://github.com/bensheldon/good_job/tree/v0.3.0) (2020-03-22)
13
+
14
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.2.2...v0.3.0)
6
15
 
7
16
  **Merged pull requests:**
8
17
 
9
18
  - Update development Ruby to 2.6.5 [\#22](https://github.com/bensheldon/good_job/pull/22) ([bensheldon](https://github.com/bensheldon))
10
19
  - Simplify the internal API, removing JobWrapper and InlineScheduler [\#21](https://github.com/bensheldon/good_job/pull/21) ([bensheldon](https://github.com/bensheldon))
11
20
  - Generate a new future for every executed job [\#20](https://github.com/bensheldon/good_job/pull/20) ([bensheldon](https://github.com/bensheldon))
21
+
22
+ ## [v0.2.2](https://github.com/bensheldon/good_job/tree/v0.2.2) (2020-03-08)
23
+
24
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.2.1...v0.2.2)
25
+
26
+ **Merged pull requests:**
27
+
12
28
  - Configuration for maximum number of job execution threads [\#18](https://github.com/bensheldon/good_job/pull/18) ([bensheldon](https://github.com/bensheldon))
13
29
  - Gracefully shutdown Scheduler when executable receives TERM or INT [\#17](https://github.com/bensheldon/good_job/pull/17) ([bensheldon](https://github.com/bensheldon))
14
30
  - Update Appraisals [\#16](https://github.com/bensheldon/good_job/pull/16) ([bensheldon](https://github.com/bensheldon))
data/README.md CHANGED
@@ -114,17 +114,10 @@ Package maintainers can release this gem with the following [gem-release](https:
114
114
  # Sign into rubygems
115
115
  $ gem signin
116
116
 
117
- # Increase the version number
118
- $ gem bump -v minor --no-commit
119
-
120
- # Update the changelog
121
- $ bundle exec rake changelog
122
-
123
- # Commit the version and changelog to git
117
+ # Update version number, changelog, and create git commit:
124
118
  $ bundle exec rake commit_version
125
119
 
126
- # Push to rubygems.org
127
- $ gem release
120
+ # ..and follow subsequent directions.
128
121
  ```
129
122
 
130
123
  ## Contributing
@@ -5,36 +5,66 @@ module GoodJob
5
5
  RecordAlreadyAdvisoryLockedError = Class.new(StandardError)
6
6
 
7
7
  included do
8
+ scope :advisory_lock, (lambda do
9
+ original_query = self
10
+
11
+ cte_table = Arel::Table.new(:rows)
12
+ composed_cte = Arel::Nodes::As.new(cte_table, original_query.select(primary_key).except(:limit).arel)
13
+
14
+ query = cte_table.project(cte_table[:id])
15
+ .with(composed_cte)
16
+ .where(Arel.sql(sanitize_sql_for_conditions(["pg_try_advisory_lock(('x'||substr(md5(:table_name || \"#{cte_table.name}\".\"#{primary_key}\"::text), 1, 16))::bit(64)::bigint)", { table_name: table_name }])))
17
+
18
+ limit = original_query.arel.ast.limit
19
+ query.limit = limit.value if limit.present?
20
+
21
+ unscoped.where(arel_table[:id].in(query)).merge(original_query.only(:order))
22
+ end)
23
+
8
24
  scope :joins_advisory_locks, (lambda do
9
- joins(<<~SQL)
25
+ join_sql = <<~SQL
10
26
  LEFT JOIN pg_locks ON pg_locks.locktype = 'advisory'
11
27
  AND pg_locks.objsubid = 1
12
- AND pg_locks.classid = ('x'||substr(md5(good_jobs.id::text), 1, 16))::bit(32)::int
13
- AND pg_locks.objid = (('x'||substr(md5(good_jobs.id::text), 1, 16))::bit(64) << 32)::bit(32)::int
28
+ AND pg_locks.classid = ('x'||substr(md5(:table_name || "#{table_name}"."#{primary_key}"::text), 1, 16))::bit(32)::int
29
+ AND pg_locks.objid = (('x'||substr(md5(:table_name || "#{table_name}"."#{primary_key}"::text), 1, 16))::bit(64) << 32)::bit(32)::int
14
30
  SQL
31
+
32
+ joins(sanitize_sql_for_conditions([join_sql, { table_name: table_name }]))
15
33
  end)
16
34
 
17
35
  scope :advisory_unlocked, -> { joins_advisory_locks.where(pg_locks: { locktype: nil }) }
36
+ scope :advisory_locked, -> { joins_advisory_locks.where.not(pg_locks: { locktype: nil }) }
37
+ scope :owns_advisory_locked, -> { joins_advisory_locks.where('"pg_locks"."pid" = pg_backend_pid()') }
18
38
 
19
39
  attr_accessor :create_with_advisory_lock
20
-
21
40
  after_create -> { advisory_lock }, if: :create_with_advisory_lock
22
41
  end
23
42
 
24
43
  class_methods do
25
- def first_advisory_locked_row(query)
26
- find_by_sql(<<~SQL).first
27
- WITH rows AS (#{query.to_sql})
28
- SELECT rows.*
29
- FROM rows
30
- WHERE pg_try_advisory_lock(('x'||substr(md5(id::text), 1, 16))::bit(64)::bigint)
31
- LIMIT 1
32
- SQL
44
+ def with_advisory_lock(&block)
45
+ records = advisory_lock.to_a
46
+ begin
47
+ block.call(records)
48
+ ensure
49
+ records.each(&:advisory_unlock)
50
+ end
33
51
  end
34
52
  end
35
53
 
36
54
  def advisory_lock
37
- self.class.connection.execute(sanitize_sql_for_conditions(["SELECT 1 as one WHERE pg_try_advisory_lock(('x'||substr(md5(?), 1, 16))::bit(64)::bigint)", id])).ntuples.positive?
55
+ query = <<~SQL
56
+ SELECT 1 AS one
57
+ WHERE pg_try_advisory_lock(('x'||substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
58
+ SQL
59
+ self.class.connection.execute(sanitize_sql_for_conditions([query, { table_name: self.class.table_name, id: send(self.class.primary_key) }])).ntuples.positive?
60
+ end
61
+
62
+ def advisory_unlock
63
+ query = <<~SQL
64
+ SELECT 1 AS one
65
+ WHERE pg_advisory_unlock(('x'||substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
66
+ SQL
67
+ self.class.connection.execute(sanitize_sql_for_conditions([query, { table_name: self.class.table_name, id: send(self.class.primary_key) }])).ntuples.positive?
38
68
  end
39
69
 
40
70
  def advisory_lock!
@@ -45,38 +75,16 @@ module GoodJob
45
75
  def with_advisory_lock
46
76
  advisory_lock!
47
77
  yield
48
- rescue StandardError => e
49
- advisory_unlock unless e.is_a? RecordAlreadyAdvisoryLockedError
50
- raise
78
+ ensure
79
+ advisory_unlock unless $ERROR_INFO.is_a? RecordAlreadyAdvisoryLockedError
51
80
  end
52
81
 
53
82
  def advisory_locked?
54
- self.class.connection.execute(<<~SQL).ntuples.positive?
55
- SELECT 1 as one
56
- FROM pg_locks
57
- WHERE
58
- locktype = 'advisory'
59
- AND objsubid = 1
60
- AND classid = ('x'||substr(md5('#{id}'), 1, 16))::bit(32)::int
61
- AND objid = (('x'||substr(md5('#{id}'), 1, 16))::bit(64) << 32)::bit(32)::int
62
- SQL
83
+ self.class.advisory_locked.where(id: send(self.class.primary_key)).any?
63
84
  end
64
85
 
65
86
  def owns_advisory_lock?
66
- self.class.connection.execute(<<~SQL).ntuples.positive?
67
- SELECT 1 as one
68
- FROM pg_locks
69
- WHERE
70
- locktype = 'advisory'
71
- AND objsubid = 1
72
- AND classid = ('x'||substr(md5('#{id}'), 1, 16))::bit(32)::int
73
- AND objid = (('x'||substr(md5('#{id}'), 1, 16))::bit(64) << 32)::bit(32)::int
74
- AND pid = pg_backend_pid()
75
- SQL
76
- end
77
-
78
- def advisory_unlock
79
- self.class.connection.execute("SELECT pg_advisory_unlock(('x'||substr(md5('#{id}'), 1, 16))::bit(64)::bigint)").first["pg_advisory_unlock"]
87
+ self.class.owns_advisory_locked.where(id: send(self.class.primary_key)).any?
80
88
  end
81
89
 
82
90
  def advisory_unlock!
@@ -62,18 +62,18 @@ module GoodJob
62
62
 
63
63
  def create_thread
64
64
  future = Concurrent::Future.new(args: [ordered_query], executor: @pool) do |query|
65
- executed_job = false
65
+ good_job = nil
66
66
 
67
67
  Rails.application.executor.wrap do
68
- good_job = GoodJob::Job.first_advisory_locked_row(query)
69
- break unless good_job
68
+ query.limit(1).with_advisory_lock do |good_jobs|
69
+ good_job = good_jobs.first
70
+ break unless good_job
70
71
 
71
- executed_job = true
72
- good_job.perform
73
- good_job.advisory_unlock
72
+ good_job.perform
73
+ end
74
74
  end
75
75
 
76
- executed_job
76
+ good_job
77
77
  end
78
78
  future.add_observer(self, :task_observer)
79
79
  future.execute
@@ -83,9 +83,9 @@ module GoodJob
83
83
  ActiveSupport::Notifications.instrument("finished_timer_task.good_job", { result: executed_task, error: error, time: time })
84
84
  end
85
85
 
86
- def task_observer(time, executed_task, error)
87
- ActiveSupport::Notifications.instrument("finished_job_task.good_job", { result: executed_task, error: error, time: time })
88
- create_thread if executed_task
86
+ def task_observer(time, performed_job, error)
87
+ ActiveSupport::Notifications.instrument("finished_job_task.good_job", { good_job: performed_job, error: error, time: time })
88
+ create_thread if performed_job
89
89
  end
90
90
  end
91
91
  end
@@ -1,3 +1,3 @@
1
1
  module GoodJob
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '0.4.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_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
  - Ben Sheldon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-22 00:00:00.000000000 Z
11
+ date: 2020-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: foreman
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: gem-release
99
113
  requirement: !ruby/object:Gem::Requirement