semian 0.17.0 → 0.18.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
  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