sequel-advisory-locking 0.0.1 → 0.1.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
  SHA1:
3
- metadata.gz: 2b0557baa78d17f9720b23a06e2816fc1e3b004d
4
- data.tar.gz: 461541ba776f14f621082f2b21b4b76046da4522
3
+ metadata.gz: 57fd6955d93c47e0e7c2be787616466d8e87e351
4
+ data.tar.gz: d35d690106eb1d21f4dea2624abad06600c4b9d8
5
5
  SHA512:
6
- metadata.gz: c6752ad6945be5933dd6c5e4168253217049abacc37ca10268e32cdc8066515665fbaf75c8dac1a54a72220bfafd30bf66fe1353c227c7870271588618475c29
7
- data.tar.gz: 17ecaec857690d795ca1a611b9d7995923dbe8bf847086fd1421d357f831ac481ed3b623b59610e319dd6dd62204cd577b84c7aa96b1733e8bbdaac747af1795
6
+ metadata.gz: cce1a093220e70c2758d78d59920c60627de1d040742c2a4e69641f78d6a115ce40385768da9e7e9773c0fd62e335e4dd4d0f865d1286930dd5e09105936b94e
7
+ data.tar.gz: 1321d4964ebca516d48f8ab06999a13b8db69bba80d3f82119110d402bd01fb5cc6b4e00a31eb24c54fc4210f76e17c8600c914d1c016034ac9027297240548d
@@ -8,45 +8,77 @@ module Sequel
8
8
  module AdvisoryLocking
9
9
  class Error < StandardError; end
10
10
 
11
- HEX_STRING_SLICE_RANGE = 0..15
11
+ HEX_STRING_SLICE_RANGE = (0..15).freeze
12
12
  POSTGRES_SIGNED_BIGINT_BOUND = 2**63
13
- POSTGRES_UNSIGNED_BIGINT_RANGE = 2**64
14
-
15
- LOCK_SQL = "SELECT pg_advisory_lock(?) -- ?".freeze
16
- TRY_LOCK_SQL = "SELECT pg_try_advisory_lock(?) -- ?".freeze
17
- UNLOCK_SQL = "SELECT pg_advisory_unlock(?) -- ?".freeze
18
-
19
- def advisory_lock(key, try: false)
20
- int = case key
21
- when Integer then key
22
- when String, Symbol
23
- # For an arbitrary string, pseudorandomly return an integer in
24
- # the PG bigint range.
25
- hex = Digest::MD5.hexdigest(key.to_s)[HEX_STRING_SLICE_RANGE].hex
26
- # Mimic PG's bigint rollover behavior.
27
- hex -= POSTGRES_UNSIGNED_BIGINT_RANGE if hex >= POSTGRES_SIGNED_BIGINT_BOUND
28
- hex
29
- else
30
- raise Error, "passed an invalid key type (#{key.class})"
31
- end
13
+ POSTGRES_UNSIGNED_BIGINT_BOUND = 2**64
14
+
15
+ POSTGRES_SIGNED_BIGINT_MAXIMUM = POSTGRES_SIGNED_BIGINT_BOUND - 1
16
+ POSTGRES_SIGNED_BIGINT_MINIMUM = -POSTGRES_SIGNED_BIGINT_BOUND
17
+ POSTGRES_SIGNED_BIGINT_RANGE = (POSTGRES_SIGNED_BIGINT_MINIMUM..POSTGRES_SIGNED_BIGINT_MAXIMUM).freeze
18
+
19
+ def advisory_lock(key, try: false, shared: false, &block)
20
+ int = advisory_lock_key(key)
32
21
 
33
22
  synchronize do
34
23
  begin
35
24
  # Add key to the end so that logs read easier.
36
- sql = try ? TRY_LOCK_SQL : LOCK_SQL
25
+ sql = "SELECT pg#{'_try' if try}_advisory_lock#{'_shared' if shared}(?) -- ?"
37
26
  locked = !!self[sql, int, key].get
38
27
 
39
- if locked && block_given?
40
- yield
28
+ if locked && block
29
+ if in_transaction?
30
+ # If we're in a transaction and an error occurs at the DB level,
31
+ # the advisory lock won't be released for us and we won't be
32
+ # able to run the unlock function below. So, wrap the block in a
33
+ # savepoint that will hopefully be transparent to the caller.
34
+ transaction(savepoint: true, rollback: :reraise, &block)
35
+ else
36
+ # If we're not in a transaction, of course, we don't have that
37
+ # worry, and we don't want to force the caller to enter a
38
+ # transaction that they maybe don't want to incur the overhead
39
+ # of, so just yield.
40
+ yield
41
+ end
41
42
  else
42
43
  locked
43
44
  end
44
45
  ensure
45
- self[UNLOCK_SQL, int, key].get if locked
46
+ sql = "SELECT pg_advisory_unlock#{'_shared' if shared}(?) -- ?"
47
+ self[sql, int, key].get if locked
46
48
  end
47
49
  end
48
50
  end
51
+
52
+ def advisory_lock_key(key)
53
+ case key
54
+ when Integer
55
+ advisory_lock_key_range_check(key)
56
+ when String, Symbol
57
+ # For an arbitrary string, pseudorandomly return an integer in
58
+ # the PG bigint range.
59
+ hex = Digest::MD5.hexdigest(key.to_s)[HEX_STRING_SLICE_RANGE].hex
60
+
61
+ # Mimic PG's bigint rollover behavior.
62
+ hex -= POSTGRES_UNSIGNED_BIGINT_BOUND if hex >= POSTGRES_SIGNED_BIGINT_BOUND
63
+
64
+ # The keys we derive from strings shouldn't ever fall outside the
65
+ # bigint range, but assert that just to be safe.
66
+ advisory_lock_key_range_check(hex)
67
+ else
68
+ raise Error, "passed an invalid key type (#{key.class})"
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def advisory_lock_key_range_check(integer)
75
+ if POSTGRES_SIGNED_BIGINT_RANGE.cover?(integer)
76
+ integer
77
+ else
78
+ raise Error, "given advisory lock integer (#{integer}) falls outside Postgres' bigint range"
79
+ end
80
+ end
49
81
  end
50
82
 
51
- Database.register_extension(:advisory_locking){|db| db.extend(Sequel::AdvisoryLocking)}
83
+ Database.register_extension(:advisory_locking) { |db| db.extend(Sequel::AdvisoryLocking) }
52
84
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sequel
4
4
  module AdvisoryLocking
5
- VERSION = '0.0.1'
5
+ VERSION = '0.1.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel-advisory-locking
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Hanks
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-02-29 00:00:00.000000000 Z
11
+ date: 2016-06-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler