noiseless 0.3.0 → 0.5.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: f66be9c1d81e3b887f0c0d4485381c4535f087f8365e2581c4c41d89cdba0934
4
- data.tar.gz: 1565bffbcfb554d2b6afcc78ebb1ded79c5c58a8f157b1e25ccd9a449cd2abe2
3
+ metadata.gz: 466267a77224bbf9915eff8b198002c658e2e9c9d368c6c7f6e4e4ee32939892
4
+ data.tar.gz: 71281cfbbcdd40f83f55eedb0fca494f1e0a75172c4a9523c3000cae81fa752d
5
5
  SHA512:
6
- metadata.gz: 4a7e23f4eb136bc6c0dc457dcb6f267e000269aceddd6450264ac489429dda2e20c7b382b32c2ab2a91271faeccee419a5c72530e3781087a9f630e8bd16a15d
7
- data.tar.gz: 605cf0980142cbe65692b9ef8d2a543969578620fa17b35e069a0344ac1a5ca5947781c6d9cf617203408a73e11cd08626c000d046e4a7a9839508919de8d463
6
+ metadata.gz: 7a0a6c9aa9134b043b4a87c8434735f837bca7cfc63520693e3bf49ff4001617351cf92259b73a781187fd22812d65f781130570f753d27dd68d4d6f3bb16fd0
7
+ data.tar.gz: 14387643b5a8c5b5fc506e00b28b8d5b2664d359bd48797c6180df38f03efa370dd86d46d0a871194328ff80f487458e439733b250dfdad9cf369a253e069c3e
@@ -1,21 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "socket"
4
+ require "timeout"
5
+
3
6
  module Noiseless
4
7
  module Adapters
5
8
  module ExecutionModules
6
9
  # Shared Async::HTTP connection handling for HTTP-based adapters.
7
10
  # Host classes must provide a private +default_port+ method.
8
11
  module HttpTransport
9
- def initialize(hosts: [], **connection_params)
12
+ # Low-level failures the Async::HTTP stack raises when it cannot
13
+ # complete a round-trip with the backend (refused/reset connection,
14
+ # DNS failure, transport timeout). These are wrapped into
15
+ # Noiseless::ConnectionError so callers never have to know which HTTP
16
+ # stack is underneath.
17
+ TRANSPORT_ERRORS = [
18
+ SystemCallError,
19
+ SocketError,
20
+ IOError,
21
+ IO::TimeoutError,
22
+ Timeout::Error
23
+ ].freeze
24
+
25
+ # Default per-operation IO timeout (seconds) for the search backend.
26
+ # This is an *idle* timeout: every socket read/write must make progress
27
+ # within this window. It bounds a stalled/unresponsive backend without
28
+ # capping the total duration of streaming operations (e.g. bulk import),
29
+ # since data keeps flowing during those. Override per-connection with
30
+ # +timeout:+.
31
+ DEFAULT_TIMEOUT = 5
32
+
33
+ def initialize(hosts: [], timeout: DEFAULT_TIMEOUT, **connection_params)
10
34
  # Ensure we always have at least one host
11
35
  hosts_array = Array(hosts)
12
36
  @hosts = hosts_array.empty? ? ["http://localhost:#{default_port}"] : hosts_array
37
+ @timeout = timeout
13
38
  @connection_params = connection_params
14
39
 
15
- # Initialize HTTP clients for each host
40
+ # Initialize HTTP clients for each host. The endpoint timeout makes a
41
+ # stalled backend raise IO::TimeoutError (wrapped below as
42
+ # ConnectionError) instead of blocking the fiber/reactor indefinitely.
16
43
  @clients = {}
17
44
  @hosts.each do |host|
18
- endpoint = Async::HTTP::Endpoint.parse(host)
45
+ endpoint = Async::HTTP::Endpoint.parse(host, timeout: @timeout)
19
46
  @clients[host] = Async::HTTP::Client.new(endpoint)
20
47
  end
21
48
 
@@ -68,7 +95,19 @@ module Noiseless
68
95
  host = @hosts.sample
69
96
  client = @clients[host]
70
97
 
71
- yield(client)
98
+ wrap_transport_errors(host: host) { yield(client) }
99
+ end
100
+
101
+ def parse_json_response!(response, error_class: Noiseless::RequestError, context: nil)
102
+ wrap_transport_errors { super }
103
+ end
104
+
105
+ def wrap_transport_errors(host: nil)
106
+ yield
107
+ rescue *TRANSPORT_ERRORS => e
108
+ location = host ? " at #{host}" : ""
109
+ raise Noiseless::ConnectionError,
110
+ "search backend unreachable#{location} (#{e.class}: #{e.message})"
72
111
  end
73
112
 
74
113
  def default_headers
@@ -54,7 +54,7 @@ module Noiseless
54
54
  return unless should_update_search_index?
55
55
 
56
56
  update_search_index_async if noiseless_new_record? || (respond_to?(:changed?) && changed?)
57
- rescue Net::ProtocolError, JSON::ParserError, Timeout::Error => e
57
+ rescue Noiseless::Error => e
58
58
  handle_search_index_error(e, :update)
59
59
  end
60
60
 
@@ -62,7 +62,7 @@ module Noiseless
62
62
  return unless should_update_search_index?
63
63
 
64
64
  update_search_index_async
65
- rescue Net::ProtocolError, JSON::ParserError, Timeout::Error => e
65
+ rescue Noiseless::Error => e
66
66
  handle_search_index_error(e, :update)
67
67
  end
68
68
 
@@ -70,7 +70,7 @@ module Noiseless
70
70
  return unless should_update_search_index?
71
71
 
72
72
  remove_from_search_index_async
73
- rescue Net::ProtocolError, JSON::ParserError, Timeout::Error => e
73
+ rescue Noiseless::Error => e
74
74
  handle_search_index_error(e, :delete)
75
75
  end
76
76
 
@@ -78,7 +78,7 @@ module Noiseless
78
78
  return unless should_update_search_index?
79
79
 
80
80
  remove_from_search_index_async
81
- rescue Net::ProtocolError, JSON::ParserError, Timeout::Error => e
81
+ rescue Noiseless::Error => e
82
82
  handle_search_index_error(e, :delete)
83
83
  end
84
84
 
@@ -8,8 +8,8 @@ module Noiseless
8
8
  end
9
9
 
10
10
  # Register a named client statically from YAML (boot-time only)
11
- def register(name, adapter:, hosts:)
12
- @configs[name.to_sym] = { adapter: adapter, hosts: hosts }
11
+ def register(name, adapter:, hosts:, timeout: nil)
12
+ @configs[name.to_sym] = { adapter: adapter, hosts: hosts, timeout: timeout }
13
13
  end
14
14
 
15
15
  # Retrieve a client; defaults to :primary
@@ -19,7 +19,9 @@ module Noiseless
19
19
  # Lazy-load the adapter only when actually used
20
20
  @clients[name] ||= begin
21
21
  config = @configs.fetch(name) { raise "Unknown connection: #{name}" }
22
- Adapters.lookup(config[:adapter], hosts: config[:hosts])
22
+ params = { hosts: config[:hosts] }
23
+ params[:timeout] = config[:timeout] unless config[:timeout].nil?
24
+ Adapters.lookup(config[:adapter], **params)
23
25
  end
24
26
  end
25
27
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Noiseless
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/noiseless.rb CHANGED
@@ -32,6 +32,14 @@ module Noiseless
32
32
  # (malformed query, missing index, shard failures).
33
33
  class SearchError < RequestError; end
34
34
 
35
+ # Raised when the search backend cannot be reached at all: connection
36
+ # refused, DNS failure, reset socket, or transport timeout. Distinct from
37
+ # RequestError, which means the backend *was* reached and replied with an
38
+ # HTTP error. Wrapping happens at the transport boundary (HttpTransport),
39
+ # so callers rescue Noiseless::Error instead of enumerating socket/system
40
+ # exception classes from whatever HTTP stack the transport happens to use.
41
+ class ConnectionError < Error; end
42
+
35
43
  class Configuration
36
44
  attr_accessor :connections_config, :default_connection, :default_adapter, :config_path
37
45
 
@@ -85,7 +93,7 @@ module Noiseless
85
93
  config.connections_config.each do |name, params|
86
94
  adapter_name = params[:adapter]
87
95
  hosts = params[:hosts] || []
88
- connections.register(name, adapter: adapter_name, hosts: hosts)
96
+ connections.register(name, adapter: adapter_name, hosts: hosts, timeout: params[:timeout])
89
97
  end
90
98
  end
91
99
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: noiseless
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih