|
2 | 2 |
|
3 | 3 | module WithAdvisoryLock
|
4 | 4 | class PostgreSQL < Base
|
5 |
| - # See http://www.postgresql.org/docs/9.1/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS |
| 5 | + # See https://www.postgresql.org/docs/16/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS |
| 6 | + |
| 7 | + # MRI returns 't', jruby returns true. YAY! |
| 8 | + LOCK_RESULT_VALUES = ['t', true].freeze |
| 9 | + PG_ADVISORY_UNLOCK = 'pg_advisory_unlock' |
| 10 | + PG_TRY_ADVISORY = 'pg_try_advisory' |
| 11 | + ERROR_MESSAGE_REGEX = / ERROR: +current transaction is aborted,/ |
| 12 | + |
6 | 13 | def try_lock
|
7 |
| - pg_function = "pg_try_advisory#{transaction ? '_xact' : ''}_lock#{shared ? '_shared' : ''}" |
8 |
| - execute_successful?(pg_function) |
| 14 | + execute_successful?(advisory_try_lock_function(transaction)) |
9 | 15 | end
|
10 | 16 |
|
11 | 17 | def release_lock
|
12 | 18 | return if transaction
|
13 | 19 |
|
14 |
| - pg_function = "pg_advisory_unlock#{shared ? '_shared' : ''}" |
15 |
| - execute_successful?(pg_function) |
| 20 | + execute_successful?(advisory_unlock_function) |
16 | 21 | rescue ActiveRecord::StatementInvalid => e
|
17 |
| - raise unless e.message =~ / ERROR: +current transaction is aborted,/ |
| 22 | + raise unless e.message =~ ERROR_MESSAGE_REGEX |
18 | 23 |
|
19 | 24 | begin
|
20 | 25 | connection.rollback_db_transaction
|
21 |
| - execute_successful?(pg_function) |
| 26 | + execute_successful?(advisory_unlock_function) |
22 | 27 | ensure
|
23 | 28 | connection.begin_db_transaction
|
24 | 29 | end
|
25 | 30 | end
|
26 | 31 |
|
| 32 | + def advisory_try_lock_function(transaction_scope) |
| 33 | + [ |
| 34 | + 'pg_try_advisory', |
| 35 | + transaction_scope ? '_xact' : nil, |
| 36 | + '_lock', |
| 37 | + shared ? '_shared' : nil |
| 38 | + ].compact.join |
| 39 | + end |
| 40 | + |
| 41 | + def advisory_unlock_function |
| 42 | + [ |
| 43 | + 'pg_advisory_unlock', |
| 44 | + shared ? '_shared' : nil |
| 45 | + ].compact.join |
| 46 | + end |
| 47 | + |
27 | 48 | def execute_successful?(pg_function)
|
| 49 | + result = connection.select_value(prepare_sql(pg_function)) |
| 50 | + LOCK_RESULT_VALUES.include?(result) |
| 51 | + end |
| 52 | + |
| 53 | + def prepare_sql(pg_function) |
28 | 54 | comment = lock_name.to_s.gsub(%r{(/\*)|(\*/)}, '--')
|
29 |
| - sql = "SELECT #{pg_function}(#{lock_keys.join(',')}) AS #{unique_column_name} /* #{comment} */" |
30 |
| - result = connection.select_value(sql) |
31 |
| - # MRI returns 't', jruby returns true. YAY! |
32 |
| - ['t', true].include?(result) |
| 55 | + "SELECT #{pg_function}(#{lock_keys.join(',')}) AS #{unique_column_name} /* #{comment} */" |
33 | 56 | end
|
34 | 57 |
|
35 | 58 | # PostgreSQL wants 2 32bit integers as the lock key.
|
36 | 59 | def lock_keys
|
37 |
| - @lock_keys ||= [stable_hashcode(lock_name), ENV['WITH_ADVISORY_LOCK_PREFIX']].map do |ea| |
38 |
| - # pg advisory args must be 31 bit ints |
39 |
| - ea.to_i & 0x7fffffff |
40 |
| - end |
| 60 | + @lock_keys ||= [ |
| 61 | + stable_hashcode(lock_name), |
| 62 | + ENV[LOCK_PREFIX_ENV] |
| 63 | + ].map { |ea| ea.to_i & 0x7fffffff } |
41 | 64 | end
|
42 | 65 | end
|
43 | 66 | end
|
0 commit comments