barkibu-kb 1.3.0 → 1.4.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 +4 -4
- data/CHANGELOG.md +7 -1
- data/Gemfile.lock +7 -3
- data/README.md +37 -2
- data/barkibu-kb.gemspec +2 -0
- data/lib/barkibu-kb.rb +7 -0
- data/lib/kb/client.rb +17 -27
- data/lib/kb/connections.rb +70 -0
- data/lib/kb/retry_policy.rb +6 -2
- data/lib/kb/version.rb +1 -1
- metadata +30 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5edd7ddce6b2d27e72f6cafcfb8efba18c35c001efe54ce96eb7a51a83d1d8ae
|
|
4
|
+
data.tar.gz: 7d1be392c9297f09f8420258adf07ad3b69b7f57d7a6a07210ef9f250aeb193e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5427db71ffb7d349463fa04291f24106676b1761ea9e55c5cc0c0440e424dd8c5da57a70dbc17a7528f3e79a37dbe9f0ac57e80f6d735661c984eaf31b3b21a2
|
|
7
|
+
data.tar.gz: 2c92ff67fe128d0d5d9ae10e575e2df5d95d73945dbe10534aba6a6f1e493829fe09f8f0cb0613300f8b7318c73a46828139ea6a9098f6b15aa113b0f963d136
|
data/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
## [unreleased]
|
|
9
|
-
- See diff: https://github.com/barkibu/kb-ruby/compare/v1.
|
|
9
|
+
- See diff: https://github.com/barkibu/kb-ruby/compare/v1.4.0...HEAD
|
|
10
|
+
|
|
11
|
+
## [1.4.0]
|
|
12
|
+
- Reuse connections to KB (keep-alive) with the stock faraday-net_http_persistent adapter. New runtime dependencies: `faraday-net_http_persistent ~> 1.2`, `net-http-persistent ~> 4.0` (4.0.8 accepts `connection_pool` 2.2.4 up to < 4). On by default; `KB.config.request.keep_alive = false` restores one connection per call. New `KB.config.request.idle_timeout` (default 30s, below the Heroku router's ~55s idle close).
|
|
13
|
+
- Every `KB::Client` now shares one Faraday connection (`KB::Connections`), so all model clients use one connection pool per process. Clients send full URLs and their own `x-api-key` header per request; URLs, headers and cache keys are unchanged. Settings are read when that connection is built, on the process's first KB call; `KB::Connections.reset!` applies later changes.
|
|
14
|
+
- A call with its own `read_timeout:` goes through a separate plain net_http connection, so its override never reaches the shared pool's other calls.
|
|
15
|
+
- With keep-alive, a connect timeout is `Faraday::TimeoutError` (was `Faraday::ConnectionFailed`) wrapping `Net::OpenTimeout`, and a refused connection is `Faraday::ConnectionFailed` wrapping `Net::HTTP::Persistent::Error`. Both still become `KB::Error` in model calls and are still retried for every verb: `KB::RetryPolicy.root_cause` unwraps `Net::HTTP::Persistent::Error` to the underlying `Errno`. Datadog `error.type` on connect-timeout spans changes accordingly.
|
|
10
16
|
|
|
11
17
|
## [1.3.0]
|
|
12
18
|
- Retry transport failures once (`faraday-retry`, already in the Faraday 1.10 bundle, now an explicit dependency). `KB::RetryPolicy` decides by the underlying error, not the Faraday class: failures where the request never left (`Net::OpenTimeout`, `ECONNREFUSED`, `EHOSTUNREACH`, `ENETUNREACH`, `EADDRNOTAVAIL`, `SocketError`) retry for every verb; any other transport failure (read/write timeout, reset, EOF, TLS) retries for GET/HEAD only (and not when the call raised its own `read_timeout:`, so a 30s read isn't doubled); HTTP error responses never retry. New settings `KB.config.request.retries` (default 1, 0 disables) and `retry_interval` (default 0.1s, randomized up to 2x). Worst-case latency is now two attempts' worth of phase budgets.
|
data/Gemfile.lock
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
barkibu-kb (1.
|
|
4
|
+
barkibu-kb (1.4.0)
|
|
5
5
|
activemodel (>= 4.0.2)
|
|
6
6
|
activerecord
|
|
7
7
|
activesupport (>= 3.0.0)
|
|
8
8
|
dry-configurable (~> 0.9)
|
|
9
9
|
faraday
|
|
10
10
|
faraday-net_http (~> 1.0)
|
|
11
|
+
faraday-net_http_persistent (~> 1.2)
|
|
11
12
|
faraday-retry (~> 1.0)
|
|
12
13
|
faraday_middleware
|
|
13
14
|
i18n
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
net-http-persistent (~> 4.0)
|
|
16
|
+
barkibu-kb-fake (1.4.0)
|
|
17
|
+
barkibu-kb (= 1.4.0)
|
|
16
18
|
countries
|
|
17
19
|
sinatra
|
|
18
20
|
webmock
|
|
@@ -110,6 +112,8 @@ GEM
|
|
|
110
112
|
multipart-post (2.3.0)
|
|
111
113
|
mustermann (3.0.0)
|
|
112
114
|
ruby2_keywords (~> 0.0.1)
|
|
115
|
+
net-http-persistent (4.0.8)
|
|
116
|
+
connection_pool (>= 2.2.4, < 4)
|
|
113
117
|
parallel (1.24.0)
|
|
114
118
|
parser (3.3.0.5)
|
|
115
119
|
ast (~> 2.4.1)
|
data/README.md
CHANGED
|
@@ -99,13 +99,48 @@ KB.config.request.retries = 1 # default; 0 disables retries
|
|
|
99
99
|
KB.config.request.retry_interval = 0.1 # default, seconds; each wait is 1x-2x this
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
Like the timeouts, these are read when
|
|
103
|
-
its first call, so set them in an initializer
|
|
102
|
+
Like the timeouts, these are read when the process's shared KB connection is
|
|
103
|
+
built, i.e. on its first KB call, so set them in an initializer (or call
|
|
104
|
+
`KB::Connections.reset!` after changing them at runtime).
|
|
104
105
|
|
|
105
106
|
Worst case, a call now takes two attempts' worth of phase budgets plus the
|
|
106
107
|
interval, e.g. a GET that read-times-out twice takes about 2 x (1 + 3 + 5)s with
|
|
107
108
|
the default timeouts.
|
|
108
109
|
|
|
110
|
+
#### Keep-alive connections
|
|
111
|
+
|
|
112
|
+
KB calls reuse TCP/TLS connections instead of opening a new one per call, through
|
|
113
|
+
the stock `net_http_persistent` Faraday adapter (net-http-persistent). Every
|
|
114
|
+
model's client shares one Faraday connection (`KB::Connections`), so they all
|
|
115
|
+
draw on one connection pool per process; each
|
|
116
|
+
thread checks a connection out per call. A pooled connection idle for longer
|
|
117
|
+
than `idle_timeout` is closed and reopened on the next call.
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
# config/initializers/kb_ruby.rb
|
|
121
|
+
KB.config.request.keep_alive = true # default; false opens a connection per call (net_http)
|
|
122
|
+
KB.config.request.idle_timeout = 30 # default, seconds
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Keep `idle_timeout` below the Heroku router's own idle close: it drops an idle
|
|
126
|
+
client connection after about 55 seconds (measured against KB staging,
|
|
127
|
+
2026-09-23). A connection the server already closed is noticed before the next
|
|
128
|
+
request is written, and reopened. A close that races the request in the same
|
|
129
|
+
instant can't be noticed: a GET is retried on a fresh connection (see Retries),
|
|
130
|
+
a POST fails with `Faraday::ConnectionFailed` wrapping `EOFError`.
|
|
131
|
+
|
|
132
|
+
Net::HTTP's own retry of idempotent requests stays off (`max_retries = 0`), so a
|
|
133
|
+
PUT is never resent behind `KB::RetryPolicy`'s back.
|
|
134
|
+
|
|
135
|
+
A call with its own `read_timeout:` goes through a separate plain connection,
|
|
136
|
+
opened for that call: the pooled adapter keeps one timeout setting for the whole
|
|
137
|
+
pool, where one call's override would reach other threads' calls.
|
|
138
|
+
|
|
139
|
+
Differences from `keep_alive = false`: a connect timeout surfaces as
|
|
140
|
+
`Faraday::TimeoutError` wrapping `Net::OpenTimeout` (retries classify it by that
|
|
141
|
+
underlying error, so it is still retried for every verb), and a refused
|
|
142
|
+
connection as `Faraday::ConnectionFailed` wrapping `Net::HTTP::Persistent::Error`.
|
|
143
|
+
|
|
109
144
|
#### Instrumentation
|
|
110
145
|
|
|
111
146
|
Every KB call emits one `request.kb_client` event through
|
data/barkibu-kb.gemspec
CHANGED
|
@@ -53,7 +53,9 @@ Gem::Specification.new do |spec|
|
|
|
53
53
|
spec.add_runtime_dependency 'activesupport', '>= 3.0.0'
|
|
54
54
|
spec.add_runtime_dependency 'faraday'
|
|
55
55
|
spec.add_runtime_dependency 'faraday-net_http', '~> 1.0'
|
|
56
|
+
spec.add_runtime_dependency 'faraday-net_http_persistent', '~> 1.2'
|
|
56
57
|
spec.add_runtime_dependency 'faraday_middleware'
|
|
57
58
|
spec.add_runtime_dependency 'faraday-retry', '~> 1.0'
|
|
58
59
|
spec.add_runtime_dependency 'i18n'
|
|
60
|
+
spec.add_runtime_dependency 'net-http-persistent', '~> 4.0'
|
|
59
61
|
end
|
data/lib/barkibu-kb.rb
CHANGED
|
@@ -24,6 +24,12 @@ module KB
|
|
|
24
24
|
# Retries after a transport failure, per KB::RetryPolicy. 0 disables retries.
|
|
25
25
|
setting :retries, default: 1
|
|
26
26
|
setting :retry_interval, default: 0.1
|
|
27
|
+
# Reuse connections to KB across calls (faraday-net_http_persistent, one pool
|
|
28
|
+
# per process, see KB::Connections). false opens a connection per call.
|
|
29
|
+
setting :keep_alive, default: true
|
|
30
|
+
# Seconds a pooled connection may sit idle before it is closed and reopened.
|
|
31
|
+
# Keep it below the Heroku router's idle close (about 55s, see README).
|
|
32
|
+
setting :idle_timeout, default: 30
|
|
27
33
|
end
|
|
28
34
|
end
|
|
29
35
|
|
|
@@ -33,6 +39,7 @@ require 'kb/cache'
|
|
|
33
39
|
require 'kb/client_resolver'
|
|
34
40
|
require 'kb/errors'
|
|
35
41
|
require 'kb/retry_policy'
|
|
42
|
+
require 'kb/connections'
|
|
36
43
|
require 'kb/client'
|
|
37
44
|
|
|
38
45
|
require 'kb/concerns'
|
data/lib/kb/client.rb
CHANGED
|
@@ -75,10 +75,7 @@ module KB
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def http(event, payload, read_timeout)
|
|
78
|
-
response =
|
|
79
|
-
req.options.read_timeout = read_timeout if read_timeout
|
|
80
|
-
RetryPolicy.track(req, event, read_timeout)
|
|
81
|
-
end
|
|
78
|
+
response = send_request(event, payload, read_timeout)
|
|
82
79
|
event[:status] = response.status
|
|
83
80
|
response.body
|
|
84
81
|
rescue Faraday::ClientError, Faraday::ServerError => e
|
|
@@ -86,11 +83,12 @@ module KB
|
|
|
86
83
|
raise
|
|
87
84
|
end
|
|
88
85
|
|
|
89
|
-
def
|
|
90
|
-
|
|
91
|
-
'
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
def send_request(event, payload, read_timeout)
|
|
87
|
+
connection(read_timeout).public_send(event[:verb], url_for(event[:path]), payload) do |req|
|
|
88
|
+
req.headers[:'x-api-key'] = api_key # a symbol, so Faraday names it X-api-key, as the log filter expects
|
|
89
|
+
req.options.read_timeout = read_timeout if read_timeout
|
|
90
|
+
RetryPolicy.track(req, event, read_timeout)
|
|
91
|
+
end
|
|
94
92
|
end
|
|
95
93
|
|
|
96
94
|
def attributes_case_transform(attributes)
|
|
@@ -103,26 +101,18 @@ module KB
|
|
|
103
101
|
attributes_case_transform(attributes).to_json
|
|
104
102
|
end
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
conn.response :raise_error
|
|
111
|
-
if KB.config.log_level == :debugger
|
|
112
|
-
conn.response :logger do |logger|
|
|
113
|
-
logger.filter(/(X-api-key:\s)("\w+")/, '\1[API_KEY_SCRUBBED]')
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
conn.adapter :net_http
|
|
117
|
-
end
|
|
104
|
+
# The process-wide connection every client shares (see KB::Connections):
|
|
105
|
+
# keep-alive unless disabled, or unless this one call sets its own read_timeout.
|
|
106
|
+
def connection(read_timeout = nil)
|
|
107
|
+
Connections.fetch(keep_alive: KB.config.request.keep_alive && read_timeout.nil?)
|
|
118
108
|
end
|
|
119
109
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
110
|
+
# The URL a Faraday connection on base_url would build for `path`, by Faraday's
|
|
111
|
+
# own joining rules ("" is base_url itself, anything else is relative to
|
|
112
|
+
# base_url + "/"), so sharing one connection across clients changes no URL.
|
|
113
|
+
def url_for(path)
|
|
114
|
+
@url_builder ||= Faraday::Connection.new(url: base_url)
|
|
115
|
+
@url_builder.build_exclusive_url(path).to_s
|
|
126
116
|
end
|
|
127
117
|
end
|
|
128
118
|
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'faraday/net_http_persistent'
|
|
2
|
+
|
|
3
|
+
module KB
|
|
4
|
+
# The Faraday connections behind every KB::Client, shared per process.
|
|
5
|
+
#
|
|
6
|
+
# Each model class has its own KB::Client (PetParent, Pet, Breed...). Sharing
|
|
7
|
+
# one Faraday connection between them means one adapter instance, so with
|
|
8
|
+
# keep-alive (the stock faraday-net_http_persistent adapter) one connection
|
|
9
|
+
# pool serves every client. Clients send full URLs and their own API key per
|
|
10
|
+
# request, so nothing on the connection is specific to one client; the pool
|
|
11
|
+
# keeps its sockets per host and port.
|
|
12
|
+
#
|
|
13
|
+
# Two connections:
|
|
14
|
+
# - keep-alive, for every call on the global timeouts;
|
|
15
|
+
# - plain net_http (a new connection per call), for calls that set their own
|
|
16
|
+
# `read_timeout:`. The persistent adapter writes each call's timeouts onto the
|
|
17
|
+
# shared pool object, where any thread's next checkout reads them, so a
|
|
18
|
+
# per-call override there would leak into other threads' calls.
|
|
19
|
+
#
|
|
20
|
+
# Built on first use from KB.config; `reset!` applies later config changes.
|
|
21
|
+
module Connections
|
|
22
|
+
MUTEX = Mutex.new
|
|
23
|
+
@connections = {}
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
def fetch(keep_alive:)
|
|
27
|
+
@connections[keep_alive] || MUTEX.synchronize { @connections[keep_alive] ||= build(keep_alive) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def reset!
|
|
31
|
+
MUTEX.synchronize { @connections = {} }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def build(keep_alive)
|
|
37
|
+
Faraday.new(headers: { 'Content-Type': 'application/json' }, request: timeouts) do |conn|
|
|
38
|
+
conn.request :retry, RetryPolicy.middleware_options
|
|
39
|
+
conn.response :json
|
|
40
|
+
conn.response :raise_error
|
|
41
|
+
log(conn) if KB.config.log_level == :debugger
|
|
42
|
+
adapter(conn, keep_alive)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The block runs on every request, on the shared Net::HTTP::Persistent.
|
|
47
|
+
# faraday-net_http's configure_request also sets max_retries = 0 there, so
|
|
48
|
+
# Net::HTTP never retries on its own: retries belong to KB::RetryPolicy.
|
|
49
|
+
def adapter(conn, keep_alive)
|
|
50
|
+
return conn.adapter(:net_http) unless keep_alive
|
|
51
|
+
|
|
52
|
+
conn.adapter(:net_http_persistent) { |http| http.idle_timeout = KB.config.request.idle_timeout }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def log(conn)
|
|
56
|
+
conn.response :logger do |logger|
|
|
57
|
+
logger.filter(/(X-api-key:\s)("\w+")/, '\1[API_KEY_SCRUBBED]')
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def timeouts
|
|
62
|
+
{
|
|
63
|
+
open_timeout: KB.config.request.connect_timeout,
|
|
64
|
+
write_timeout: KB.config.request.write_timeout,
|
|
65
|
+
read_timeout: KB.config.request.read_timeout
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/kb/retry_policy.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'socket'
|
|
2
2
|
require 'net/http'
|
|
3
|
+
require 'net/http/persistent'
|
|
3
4
|
require 'faraday/retry'
|
|
4
5
|
|
|
5
6
|
module KB
|
|
@@ -49,9 +50,12 @@ module KB
|
|
|
49
50
|
# The Ruby error behind a Faraday error. `wrapped_exception` is Faraday's own
|
|
50
51
|
# explicit link to it (Ruby's `cause` is only whatever was being rescued at
|
|
51
52
|
# the raise, usually the same object). Faraday's adapters wrap the Ruby error
|
|
52
|
-
# one level deep, so one level is enough
|
|
53
|
+
# one level deep, so one level is enough, except that net-http-persistent
|
|
54
|
+
# re-raises a refused connection as its own Net::HTTP::Persistent::Error with
|
|
55
|
+
# the Errno as its `cause`.
|
|
53
56
|
def root_cause(error)
|
|
54
|
-
(error.respond_to?(:wrapped_exception) && error.wrapped_exception) || error.cause || error
|
|
57
|
+
cause = (error.respond_to?(:wrapped_exception) && error.wrapped_exception) || error.cause || error
|
|
58
|
+
cause.is_a?(Net::HTTP::Persistent::Error) && cause.cause ? cause.cause : cause
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
# Options for faraday-retry's middleware, read from KB.config.request.
|
data/lib/kb/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: barkibu-kb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Léo Figea
|
|
@@ -247,6 +247,20 @@ dependencies:
|
|
|
247
247
|
- - "~>"
|
|
248
248
|
- !ruby/object:Gem::Version
|
|
249
249
|
version: '1.0'
|
|
250
|
+
- !ruby/object:Gem::Dependency
|
|
251
|
+
name: faraday-net_http_persistent
|
|
252
|
+
requirement: !ruby/object:Gem::Requirement
|
|
253
|
+
requirements:
|
|
254
|
+
- - "~>"
|
|
255
|
+
- !ruby/object:Gem::Version
|
|
256
|
+
version: '1.2'
|
|
257
|
+
type: :runtime
|
|
258
|
+
prerelease: false
|
|
259
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
260
|
+
requirements:
|
|
261
|
+
- - "~>"
|
|
262
|
+
- !ruby/object:Gem::Version
|
|
263
|
+
version: '1.2'
|
|
250
264
|
- !ruby/object:Gem::Dependency
|
|
251
265
|
name: faraday_middleware
|
|
252
266
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -289,6 +303,20 @@ dependencies:
|
|
|
289
303
|
- - ">="
|
|
290
304
|
- !ruby/object:Gem::Version
|
|
291
305
|
version: '0'
|
|
306
|
+
- !ruby/object:Gem::Dependency
|
|
307
|
+
name: net-http-persistent
|
|
308
|
+
requirement: !ruby/object:Gem::Requirement
|
|
309
|
+
requirements:
|
|
310
|
+
- - "~>"
|
|
311
|
+
- !ruby/object:Gem::Version
|
|
312
|
+
version: '4.0'
|
|
313
|
+
type: :runtime
|
|
314
|
+
prerelease: false
|
|
315
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
316
|
+
requirements:
|
|
317
|
+
- - "~>"
|
|
318
|
+
- !ruby/object:Gem::Version
|
|
319
|
+
version: '4.0'
|
|
292
320
|
description: A wrapper of Barkibu's Knowledge Base Endpoint to make those entities
|
|
293
321
|
and their respective CRUD operations available to a ruby app
|
|
294
322
|
email:
|
|
@@ -325,6 +353,7 @@ files:
|
|
|
325
353
|
- lib/kb/client_resolver.rb
|
|
326
354
|
- lib/kb/concerns.rb
|
|
327
355
|
- lib/kb/concerns/as_kb_wrapper.rb
|
|
356
|
+
- lib/kb/connections.rb
|
|
328
357
|
- lib/kb/errors.rb
|
|
329
358
|
- lib/kb/errors/client_error.rb
|
|
330
359
|
- lib/kb/errors/conflict_error.rb
|