good_job 0.3.0 → 0.4.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 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