semian 0.17.0 → 0.18.1

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: 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