amplitude-experiment 1.0.0.beta.2 → 1.0.0.beta.3

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: c5f72aeab5a4f6fb15f3c320e90c1cd075c23f8a02401ce6e0e105ee5ea21bc5
4
- data.tar.gz: 9e4c3b349d2d9b9e68e5dcec7c790a0646b03872c985d6d1bc628240603d231d
3
+ metadata.gz: 70f2c8f1fb01b7e1de84104c7d1b875868ea11da376725b172ad062c14789746
4
+ data.tar.gz: 94849ff0ec78e5ceb05169594203d84d793cbd9d1455e6f60be1383a26788237
5
5
  SHA512:
6
- metadata.gz: cdedf6dd11e06202be466b83640eb4dd0f6c50a9560bbc18d0c89f71d2e59e95efa5b947e802dcd735c56e6b1ac32b12e4d754fc5740f40c10c3932e77fc0d59
7
- data.tar.gz: 2c9ae0b3b18970a1eba2c64a4a6f4e629b26c62bba5eb78ca0a4a25f4816aea2074379a3397fe0b6498d43b9aae56db9da84b937887864fb449c2eafff49b17b
6
+ metadata.gz: 31a6213acb520c87654f4a6120b930ba6cfe17111eca31e986d7eb544aa53164e5caf2723e8f0850bf674411df76c0a5d8c5e4823c34e01af8d61f7244532c36
7
+ data.tar.gz: 949cffd01dc77def83fd1c64f9d323ffc34f8b2081a850f72530c6d0f19f23fcd3d73edb61b8c8b27d7f135e7bb534db8d3c3bda0e9b2418119021d22c0bd5e4
@@ -19,6 +19,8 @@ module Experiment
19
19
  else
20
20
  Logger::INFO
21
21
  end
22
+ endpoint = "#{@config.server_url}/sdk/vardata"
23
+ @uri = URI(endpoint)
22
24
  raise ArgumentError, 'Experiment API key is empty' if @api_key.nil? || @api_key.empty?
23
25
  end
24
26
 
@@ -91,16 +93,13 @@ module Experiment
91
93
  def do_fetch(user, timeout_millis)
92
94
  start_time = Time.now
93
95
  user_context = add_context(user)
94
- endpoint = "#{@config.server_url}/sdk/vardata"
95
96
  headers = {
96
97
  'Authorization' => "Api-Key #{@api_key}",
97
98
  'Content-Type' => 'application/json;charset=utf-8'
98
99
  }
99
- uri = URI(endpoint)
100
- http = Net::HTTP.new(uri.host, uri.port)
101
- http.use_ssl = true
102
- http.read_timeout = timeout_millis / 1000 if (timeout_millis / 1000) > 0
103
- request = Net::HTTP::Post.new(uri, headers)
100
+ read_timeout = timeout_millis / 1000 if (timeout_millis / 1000) > 0
101
+ http = PersistentHttpClient.get(@uri, { read_timeout: read_timeout })
102
+ request = Net::HTTP::Post.new(@uri, headers)
104
103
  request.body = user_context.to_json
105
104
  if request.body.length > 8000
106
105
  @logger.warn("[Experiment] encoded user object length #{request.body.length} cannot be cached by CDN; must be < 8KB")
@@ -0,0 +1,127 @@
1
+ module Experiment
2
+ # Persist Http Client to reuse connection and reduce IO
3
+ class PersistentHttpClient
4
+ DEFAULT_OPTIONS = { read_timeout: 80 }.freeze
5
+
6
+ class << self
7
+ # url: URI / String
8
+ # options: any options that Net::HTTP.new accepts
9
+ def get(url, options = {})
10
+ uri = url.is_a?(URI) ? url : URI(url)
11
+ connection_manager.get_client(uri, options)
12
+ end
13
+
14
+ private
15
+
16
+ # each thread gets its own connection manager
17
+ def connection_manager
18
+ # before getting a connection manager
19
+ # we first clear all old ones
20
+ remove_old_managers
21
+ Thread.current[:http_connection_manager] ||= new_manager
22
+ end
23
+
24
+ def new_manager
25
+ # create a new connection manager in a thread safe way
26
+ mutex.synchronize do
27
+ manager = ConnectionManager.new
28
+ connection_managers << manager
29
+ manager
30
+ end
31
+ end
32
+
33
+ def remove_old_managers
34
+ mutex.synchronize do
35
+ removed = connection_managers.reject!(&:stale?)
36
+ (removed || []).each(&:close_connections!)
37
+ end
38
+ end
39
+
40
+ # mutex isn't needed for CRuby, but might be needed
41
+ # for other Ruby implementations
42
+ def mutex
43
+ @mutex ||= Mutex.new
44
+ end
45
+
46
+ def connection_managers
47
+ @connection_managers ||= []
48
+ end
49
+ end
50
+
51
+ # connection manager represents
52
+ # a cache of all keep-alive connections
53
+ # in a current thread
54
+ class ConnectionManager
55
+ # if a client wasn't used within this time range
56
+ # it gets removed from the cache and the connection closed.
57
+ # This helps to make sure there are no memory leaks.
58
+ STALE_AFTER = 300 # 5 minutes
59
+
60
+ # Seconds to reuse the connection of the previous request. If the idle time is less than this Keep-Alive Timeout,
61
+ # Net::HTTP reuses the TCP/IP socket used by the previous communication. Source: Ruby docs
62
+ KEEP_ALIVE_TIMEOUT = 30 # seconds
63
+
64
+ # KEEP_ALIVE_TIMEOUT vs STALE_AFTER
65
+ # STALE_AFTER - how long an Net::HTTP client object is cached in ruby
66
+ # KEEP_ALIVE_TIMEOUT - how long that client keeps TCP/IP socket open.
67
+
68
+ attr_accessor :clients_store, :last_used
69
+
70
+ def initialize
71
+ self.clients_store = {}
72
+ self.last_used = Time.now
73
+ end
74
+
75
+ def get_client(uri, options)
76
+ mutex.synchronize do
77
+ # refresh the last time a client was used,
78
+ # this prevents the client from becoming stale
79
+ self.last_used = Time.now
80
+
81
+ # we use params as a cache key for clients.
82
+ # 2 connections to the same host but with different
83
+ # options are going to use different HTTP clients
84
+ params = [uri.host, uri.port, options]
85
+ client = clients_store[params]
86
+
87
+ return client if client
88
+
89
+ client = Net::HTTP.new(uri.host, uri.port)
90
+ client.keep_alive_timeout = KEEP_ALIVE_TIMEOUT
91
+
92
+ # set SSL to true if a scheme is https
93
+ client.use_ssl = uri.scheme == 'https'
94
+
95
+ # dynamically set Net::HTTP options
96
+ DEFAULT_OPTIONS.merge(options).each_pair do |key, value|
97
+ client.public_send("#{key}=", value)
98
+ end
99
+
100
+ # open connection
101
+ client.start
102
+
103
+ # cache the client
104
+ clients_store[params] = client
105
+
106
+ client
107
+ end
108
+ end
109
+
110
+ # close connections for each client
111
+ def close_connections!
112
+ mutex.synchronize do
113
+ clients_store.values.each(&:finish)
114
+ self.clients_store = {}
115
+ end
116
+ end
117
+
118
+ def stale?
119
+ Time.now - last_used > STALE_AFTER
120
+ end
121
+
122
+ def mutex
123
+ @mutex ||= Mutex.new
124
+ end
125
+ end
126
+ end
127
+ end
@@ -1,3 +1,3 @@
1
1
  module Experiment
2
- VERSION = '1.0.0.beta.2'.freeze
2
+ VERSION = '1.0.0.beta.3'.freeze
3
3
  end
data/lib/experiment.rb CHANGED
@@ -4,6 +4,7 @@ require 'experiment/cookie'
4
4
  require 'experiment/user'
5
5
  require 'experiment/variant'
6
6
  require 'experiment/factory'
7
+ require 'experiment/persistent_http_client'
7
8
  require 'experiment/client'
8
9
 
9
10
  # Amplitude Experiment Module
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amplitude-experiment
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta.2
4
+ version: 1.0.0.beta.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amplitude
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-28 00:00:00.000000000 Z
11
+ date: 2022-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -124,6 +124,7 @@ files:
124
124
  - lib/experiment/config.rb
125
125
  - lib/experiment/cookie.rb
126
126
  - lib/experiment/factory.rb
127
+ - lib/experiment/persistent_http_client.rb
127
128
  - lib/experiment/user.rb
128
129
  - lib/experiment/variant.rb
129
130
  - lib/experiment/version.rb