semian 0.17.0 → 0.18.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: '0461852ea5a5ffbacaf9a20062213609066e3ef8ade9a93a980d2af73a0feb39'
4
- data.tar.gz: 8e21023912cd7c71cc74c0eb6a384771018f5fa3f53c212a62c23446801573e2
3
+ metadata.gz: d24779fb714aedf9f83b9ea263d7a7bd0387bc487dbc2313fed0fa265be33b92
4
+ data.tar.gz: a505d6861420fe42f5f366184b73bdeb6aa8f3b4c5ee2a95ceb224f4a6770286
5
5
  SHA512:
6
- metadata.gz: 0bd910ad2f7891d4dc2660135adea394db252afa7f53f8c707a55fa569cbf481514af485fe9056d5959e98faffd8bd20990ac9f7b6abe0b0f453ca29d3ad3e9f
7
- data.tar.gz: a4149e3b4021fe47be636e28bbd5ece3eb4246d65bbc95b07cc50e1b45a328980e47c32d15e81a52e2516fe01b2622fedf31d5aac8bcedbee7e3bdb73a1a0c8c
6
+ metadata.gz: 0e24a1414fb44190bde478cc02cf4c2f12dde3b73673446dee2225ee0155951be5121dbf077da05ce8d1746485f8ce2914b46a191ba09f05297f70177713d15f
7
+ data.tar.gz: d7e41a294ec3819216b4e5762b0ea98ca545002c528e64c291e606c0e1901b847c3d6ee31a4b927c2402b0249fed38faa5b46ecdbb5a4ae9e2ef767fa34702f7
data/README.md CHANGED
@@ -73,6 +73,7 @@ version is the version of the public gem with the same name:
73
73
  * [`semian/mysql2`][mysql-semian-adapter] (~> 0.3.16)
74
74
  * [`semian/redis`][redis-semian-adapter] (~> 3.2.1)
75
75
  * [`semian/net_http`][nethttp-semian-adapter]
76
+ * [`semian/activerecord_trilogy_adapter`][activerecord-trilogy-semian-adapter]
76
77
  * [`semian-postgres`][postgres-semian-adapter]
77
78
 
78
79
  ### Creating Adapters
@@ -299,6 +300,35 @@ SEMIAN_PARAMETERS = { tickets: 1,
299
300
  open_circuit_server_errors: true }
300
301
  ```
301
302
 
303
+ #### Active Record
304
+
305
+ Semian supports Active Record adapter `trilogy`.
306
+ It can be configured in the `database.yml`:
307
+
308
+ ```yml
309
+ semian: &semian
310
+ success_threshold: 2
311
+ error_threshold: 3
312
+ error_timeout: 4
313
+ half_open_resource_timeout: 1
314
+ bulkhead: false # Disable bulkhead for Puma: https://github.com/shopify/semian#thread-safety
315
+ name: semian_identifier_name
316
+
317
+ default: &default
318
+ adapter: trilogy
319
+ username: root
320
+ password:
321
+ host: localhost
322
+ read_timeout: 2
323
+ write_timeout: 1
324
+ connect_timeout: 1
325
+ semian:
326
+ <<: *semian
327
+ ```
328
+
329
+ Example cases for `activerecord-trilogy-adapter` can be run using
330
+ `BUNDLE_GEMFILE=gemfiles/activerecord_trilogy_adapter.gemfile bundle exec rake examples:activerecord_trilogy_adapter`
331
+
302
332
  # Understanding Semian
303
333
 
304
334
  Semian is a library with heuristics for failing fast. This section will explain
@@ -859,6 +889,7 @@ $ bundle install
859
889
  [mysql-semian-adapter]: lib/semian/mysql2.rb
860
890
  [postgres-semian-adapter]: https://github.com/mschoenlaub/semian-postgres
861
891
  [redis-semian-adapter]: lib/semian/redis.rb
892
+ [activerecord-trilogy-semian-adapter]: lib/semian/activerecord_trilogy_adapter.rb
862
893
  [semian-adapter]: lib/semian/adapter.rb
863
894
  [nethttp-semian-adapter]: lib/semian/net_http.rb
864
895
  [nethttp-default-errors]: lib/semian/net_http.rb#L35-L45
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "semian/adapter"
4
+ require "activerecord-trilogy-adapter"
5
+ require "active_record/connection_adapters/trilogy_adapter"
6
+
7
+ module ActiveRecord
8
+ module ConnectionAdapters
9
+ class TrilogyAdapter
10
+ ActiveRecord::ActiveRecordError.include(::Semian::AdapterError)
11
+
12
+ class SemianError < StatementInvalid
13
+ def initialize(semian_identifier, *args)
14
+ super(*args)
15
+ @semian_identifier = semian_identifier
16
+ end
17
+ end
18
+
19
+ ResourceBusyError = Class.new(SemianError)
20
+ CircuitOpenError = Class.new(SemianError)
21
+ end
22
+ end
23
+ end
24
+
25
+ module Semian
26
+ module ActiveRecordTrilogyAdapter
27
+ include Semian::Adapter
28
+
29
+ ResourceBusyError = ::ActiveRecord::ConnectionAdapters::TrilogyAdapter::ResourceBusyError
30
+ CircuitOpenError = ::ActiveRecord::ConnectionAdapters::TrilogyAdapter::CircuitOpenError
31
+
32
+ attr_reader :raw_semian_options, :semian_identifier
33
+
34
+ def initialize(*options)
35
+ *, config = options
36
+ config = config.dup
37
+ @raw_semian_options = config.delete(:semian)
38
+ @semian_identifier = begin
39
+ name = semian_options && semian_options[:name]
40
+ unless name
41
+ host = config[:host] || "localhost"
42
+ port = config[:port] || 3306
43
+ name = "#{host}:#{port}"
44
+ end
45
+ :"mysql_#{name}"
46
+ end
47
+ super
48
+ end
49
+
50
+ def execute(sql, name = nil, async: false, allow_retry: false)
51
+ if query_allowlisted?(sql)
52
+ super(sql, name, async: async, allow_retry: allow_retry)
53
+ else
54
+ acquire_semian_resource(adapter: :trilogy_adapter, scope: :execute) do
55
+ super(sql, name, async: async, allow_retry: allow_retry)
56
+ end
57
+ end
58
+ end
59
+
60
+ def active?
61
+ acquire_semian_resource(adapter: :trilogy_adapter, scope: :ping) do
62
+ super
63
+ end
64
+ rescue ResourceBusyError, CircuitOpenError
65
+ false
66
+ end
67
+
68
+ def with_resource_timeout(temp_timeout)
69
+ if connection.nil?
70
+ prev_read_timeout = @config[:read_timeout] || 0
71
+ @config.merge!(read_timeout: temp_timeout) # Create new client with temp_timeout for read timeout
72
+ else
73
+ prev_read_timeout = connection.read_timeout
74
+ connection.read_timeout = temp_timeout
75
+ end
76
+ yield
77
+ ensure
78
+ @config.merge!(read_timeout: prev_read_timeout)
79
+ connection&.read_timeout = prev_read_timeout
80
+ end
81
+
82
+ private
83
+
84
+ def acquire_semian_resource(**)
85
+ super
86
+ rescue ActiveRecord::StatementInvalid => error
87
+ if error.cause.is_a?(Trilogy::TimeoutError)
88
+ semian_resource.mark_failed(error)
89
+ error.semian_identifier = semian_identifier
90
+ end
91
+ raise
92
+ end
93
+
94
+ def resource_exceptions
95
+ [ActiveRecord::ConnectionNotEstablished]
96
+ end
97
+
98
+ # TODO: share this with Mysql2
99
+ QUERY_ALLOWLIST = Regexp.union(
100
+ %r{\A(?:/\*.*?\*/)?\s*ROLLBACK}i,
101
+ %r{\A(?:/\*.*?\*/)?\s*COMMIT}i,
102
+ %r{\A(?:/\*.*?\*/)?\s*RELEASE\s+SAVEPOINT}i,
103
+ )
104
+
105
+ def query_allowlisted?(sql, *)
106
+ QUERY_ALLOWLIST.match?(sql)
107
+ rescue ArgumentError
108
+ return false unless sql.valid_encoding?
109
+
110
+ raise
111
+ end
112
+
113
+ def connect(*args)
114
+ acquire_semian_resource(adapter: :trilogy_adapter, scope: :connection) do
115
+ super
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ ActiveRecord::ConnectionAdapters::TrilogyAdapter.prepend(Semian::ActiveRecordTrilogyAdapter)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Semian
4
- VERSION = "0.17.0"
4
+ VERSION = "0.18.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: semian
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Francis
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-02-08 00:00:00.000000000 Z
13
+ date: 2023-02-15 00:00:00.000000000 Z
14
14
  dependencies: []
15
15
  description: |2
16
16
  A Ruby C extention that is used to control access to shared resources
@@ -34,6 +34,7 @@ files:
34
34
  - ext/semian/tickets.h
35
35
  - ext/semian/types.h
36
36
  - lib/semian.rb
37
+ - lib/semian/activerecord_trilogy_adapter.rb
37
38
  - lib/semian/adapter.rb
38
39
  - lib/semian/circuit_breaker.rb
39
40
  - lib/semian/grpc.rb