|
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