sequel-advisory-locking 0.0.1 → 0.1.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
  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