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

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