semian 0.17.0 → 0.18.1

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: 8c0cecc8c8e42f223f2cde75e3818ad4bc33897107b6a99186beb48c413c6d30
4
+ data.tar.gz: 9202b9e1cf15ff54d68f8d3fe11800ace1624828d1aa840840b88180f65e5355
5
5
  SHA512:
6
- metadata.gz: 0bd910ad2f7891d4dc2660135adea394db252afa7f53f8c707a55fa569cbf481514af485fe9056d5959e98faffd8bd20990ac9f7b6abe0b0f453ca29d3ad3e9f
7
- data.tar.gz: a4149e3b4021fe47be636e28bbd5ece3eb4246d65bbc95b07cc50e1b45a328980e47c32d15e81a52e2516fe01b2622fedf31d5aac8bcedbee7e3bdb73a1a0c8c
6
+ metadata.gz: 1bd71610d96984ada73fe625966f66ecc13cc921ac659a78c9010eec952f61e1ea56aa4c9f3e6de0572a2b2059e0cd919e1063907c4d39c9d630dd536bf4a89e
7
+ data.tar.gz: 20513585134a89dc3a33bcc2895728a64fa5e35e978dbcf83ac44fccc00ae9428e0f8bd8988fe3941e4236d4d18daed380014843b0ad071f945164e68eda68af
data/README.md CHANGED
@@ -1,5 +1,4 @@
1
- ## Semian ![Build Status](https://github.com/Shopify/semian/actions/workflows/main.yml/badge.svg)
2
-
1
+ ## Semian ![Build Status](https://github.com/Shopify/semian/actions/workflows/test.yml/badge.svg)
3
2
 
4
3
  ![](http://i.imgur.com/7Vn2ibF.png)
5
4
 
@@ -73,6 +72,7 @@ version is the version of the public gem with the same name:
73
72
  * [`semian/mysql2`][mysql-semian-adapter] (~> 0.3.16)
74
73
  * [`semian/redis`][redis-semian-adapter] (~> 3.2.1)
75
74
  * [`semian/net_http`][nethttp-semian-adapter]
75
+ * [`semian/activerecord_trilogy_adapter`][activerecord-trilogy-semian-adapter]
76
76
  * [`semian-postgres`][postgres-semian-adapter]
77
77
 
78
78
  ### Creating Adapters
@@ -299,6 +299,35 @@ SEMIAN_PARAMETERS = { tickets: 1,
299
299
  open_circuit_server_errors: true }
300
300
  ```
301
301
 
302
+ #### Active Record
303
+
304
+ Semian supports Active Record adapter `trilogy`.
305
+ It can be configured in the `database.yml`:
306
+
307
+ ```yml
308
+ semian: &semian
309
+ success_threshold: 2
310
+ error_threshold: 3
311
+ error_timeout: 4
312
+ half_open_resource_timeout: 1
313
+ bulkhead: false # Disable bulkhead for Puma: https://github.com/shopify/semian#thread-safety
314
+ name: semian_identifier_name
315
+
316
+ default: &default
317
+ adapter: trilogy
318
+ username: root
319
+ password:
320
+ host: localhost
321
+ read_timeout: 2
322
+ write_timeout: 1
323
+ connect_timeout: 1
324
+ semian:
325
+ <<: *semian
326
+ ```
327
+
328
+ Example cases for `activerecord-trilogy-adapter` can be run using
329
+ `BUNDLE_GEMFILE=gemfiles/activerecord_trilogy_adapter.gemfile bundle exec rake examples:activerecord_trilogy_adapter`
330
+
302
331
  # Understanding Semian
303
332
 
304
333
  Semian is a library with heuristics for failing fast. This section will explain
@@ -859,6 +888,7 @@ $ bundle install
859
888
  [mysql-semian-adapter]: lib/semian/mysql2.rb
860
889
  [postgres-semian-adapter]: https://github.com/mschoenlaub/semian-postgres
861
890
  [redis-semian-adapter]: lib/semian/redis.rb
891
+ [activerecord-trilogy-semian-adapter]: lib/semian/activerecord_trilogy_adapter.rb
862
892
  [semian-adapter]: lib/semian/adapter.rb
863
893
  [nethttp-semian-adapter]: lib/semian/net_http.rb
864
894
  [nethttp-default-errors]: lib/semian/net_http.rb#L35-L45
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "semian/adapter"
4
+ require "active_record"
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, *)
51
+ if query_allowlisted?(sql)
52
+ super
53
+ else
54
+ acquire_semian_resource(adapter: :trilogy_adapter, scope: :execute) do
55
+ super
56
+ end
57
+ end
58
+ end
59
+ ruby2_keywords :execute
60
+
61
+ def active?
62
+ acquire_semian_resource(adapter: :trilogy_adapter, scope: :ping) do
63
+ super
64
+ end
65
+ rescue ResourceBusyError, CircuitOpenError
66
+ false
67
+ end
68
+
69
+ def with_resource_timeout(temp_timeout)
70
+ if connection.nil?
71
+ prev_read_timeout = @config[:read_timeout] || 0
72
+ @config.merge!(read_timeout: temp_timeout) # Create new client with temp_timeout for read timeout
73
+ else
74
+ prev_read_timeout = connection.read_timeout
75
+ connection.read_timeout = temp_timeout
76
+ end
77
+ yield
78
+ ensure
79
+ @config.merge!(read_timeout: prev_read_timeout)
80
+ connection&.read_timeout = prev_read_timeout
81
+ end
82
+
83
+ private
84
+
85
+ def acquire_semian_resource(**)
86
+ super
87
+ rescue ActiveRecord::StatementInvalid => error
88
+ if error.cause.is_a?(Trilogy::TimeoutError)
89
+ semian_resource.mark_failed(error)
90
+ error.semian_identifier = semian_identifier
91
+ end
92
+ raise
93
+ end
94
+
95
+ def resource_exceptions
96
+ [ActiveRecord::ConnectionNotEstablished]
97
+ end
98
+
99
+ # TODO: share this with Mysql2
100
+ QUERY_ALLOWLIST = Regexp.union(
101
+ %r{\A(?:/\*.*?\*/)?\s*ROLLBACK}i,
102
+ %r{\A(?:/\*.*?\*/)?\s*COMMIT}i,
103
+ %r{\A(?:/\*.*?\*/)?\s*RELEASE\s+SAVEPOINT}i,
104
+ )
105
+
106
+ def query_allowlisted?(sql, *)
107
+ QUERY_ALLOWLIST.match?(sql)
108
+ rescue ArgumentError
109
+ return false unless sql.valid_encoding?
110
+
111
+ raise
112
+ end
113
+
114
+ def connect(*args)
115
+ acquire_semian_resource(adapter: :trilogy_adapter, scope: :connection) do
116
+ super
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ 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.1"
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.1
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-05-02 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
@@ -78,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
79
  - !ruby/object:Gem::Version
79
80
  version: '0'
80
81
  requirements: []
81
- rubygems_version: 3.3.3
82
+ rubygems_version: 3.4.12
82
83
  signing_key:
83
84
  specification_version: 4
84
85
  summary: Bulkheading for Ruby with SysV semaphores