stripe 5.3.0 → 5.4.0

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: d03cea94ef3cacc081d711f3f1fe3bb779791c77e35f4c087ee8c69ffecabb93
4
- data.tar.gz: a3546067f4fe07f56ab18458691d5261f0a533760da07995dedbd3758259ec18
3
+ metadata.gz: a3a4f568f2aa887474af9b072e52b0b97de8ec04f72ee52595682192f3c75998
4
+ data.tar.gz: 7791a3d16f6b79d8e100821d241981532326527bbd805be585f20f2bb70338d8
5
5
  SHA512:
6
- metadata.gz: d821b782237ca17e82a770e6a8ec11d97732290af7f06aaae680d0830cc63b523b7e9d8831d96294f4b80a5facb42993bbddb4a27017542fafd3399b3a513aaf
7
- data.tar.gz: 665f013eec87c9c1b9deac66cec3125a3d6e65920c1d2f45bb509c4b5371222817d6bbe53f7f05406d8f477da061c3d1c3a5e5d94b6047d0d56d3736cf3f710a
6
+ metadata.gz: 31a50d23fc166cf97222b0e51b307d5356693d92596d56ab1bdefb885f8c9de941567d2c38102dca0ce1ae8f92a38a6ccb36f52b98293ed16b1c944c5d298a45
7
+ data.tar.gz: bf4c8207a3f9c6a4ec955309fd54f235002edb9e82a7a313d3c5dff0f7ff83f0b57bfb683c77ac693a3132977dd6c7ddf48f58176c985c1a34b6b9bfc072f806
@@ -1,5 +1,8 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.4.0 - 2019-10-01
4
+ * [#857](https://github.com/stripe/stripe-ruby/pull/857) Move to monotonic time for duration calculations
5
+
3
6
  ## 5.3.0 - 2019-10-01
4
7
  * [#853](https://github.com/stripe/stripe-ruby/pull/853) Support `Stripe-Should-Retry` header
5
8
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 5.3.0
1
+ 5.4.0
@@ -10,14 +10,15 @@ module Stripe
10
10
  # Note that this class in itself is *not* thread safe. We expect it to be
11
11
  # instantiated once per thread.
12
12
  class ConnectionManager
13
- # Timestamp indicating when the connection manager last made a request.
14
- # This is used by `StripeClient` to determine whether a connection manager
15
- # should be garbage collected or not.
13
+ # Timestamp (in seconds procured from the system's monotonic clock)
14
+ # indicating when the connection manager last made a request. This is used
15
+ # by `StripeClient` to determine whether a connection manager should be
16
+ # garbage collected or not.
16
17
  attr_reader :last_used
17
18
 
18
19
  def initialize
19
20
  @active_connections = {}
20
- @last_used = Time.now
21
+ @last_used = Util.monotonic_time
21
22
 
22
23
  # A connection manager may be accessed across threads as one thread makes
23
24
  # requests on it while another is trying to clear it (either because it's
@@ -78,7 +79,7 @@ module Stripe
78
79
  raise ArgumentError, "query should be a string" \
79
80
  if query && !query.is_a?(String)
80
81
 
81
- @last_used = Time.now
82
+ @last_used = Util.monotonic_time
82
83
 
83
84
  connection = connection_for(uri)
84
85
 
@@ -9,7 +9,7 @@ module Stripe
9
9
  # synchronize global access to them.
10
10
  @thread_contexts_with_connection_managers = []
11
11
  @thread_contexts_with_connection_managers_mutex = Mutex.new
12
- @last_connection_manager_gc = Time.now
12
+ @last_connection_manager_gc = Util.monotonic_time
13
13
 
14
14
  # Initializes a new `StripeClient`.
15
15
  #
@@ -368,11 +368,11 @@ module Stripe
368
368
  # For internal use only. Does not provide a stable API and may be broken
369
369
  # with future non-major changes.
370
370
  def self.maybe_gc_connection_managers
371
- if @last_connection_manager_gc + CONNECTION_MANAGER_GC_PERIOD > Time.now
372
- return nil
373
- end
371
+ next_gc_time = @last_connection_manager_gc + CONNECTION_MANAGER_GC_PERIOD
372
+ return nil if next_gc_time > Util.monotonic_time
374
373
 
375
- last_used_threshold = Time.now - CONNECTION_MANAGER_GC_LAST_USED_EXPIRY
374
+ last_used_threshold =
375
+ Util.monotonic_time - CONNECTION_MANAGER_GC_LAST_USED_EXPIRY
376
376
 
377
377
  pruned_thread_contexts = []
378
378
  @thread_contexts_with_connection_managers.each do |thread_context|
@@ -385,7 +385,7 @@ module Stripe
385
385
  end
386
386
 
387
387
  @thread_contexts_with_connection_managers -= pruned_thread_contexts
388
- @last_connection_manager_gc = Time.now
388
+ @last_connection_manager_gc = Util.monotonic_time
389
389
 
390
390
  pruned_thread_contexts.count
391
391
  end
@@ -445,7 +445,7 @@ module Stripe
445
445
  private def execute_request_with_rescues(method, api_base, context)
446
446
  num_retries = 0
447
447
  begin
448
- request_start = Time.now
448
+ request_start = Util.monotonic_time
449
449
  log_request(context, num_retries)
450
450
  resp = yield
451
451
  context = context.dup_from_response_headers(resp)
@@ -455,7 +455,8 @@ module Stripe
455
455
  log_response(context, request_start, resp.code.to_i, resp.body)
456
456
 
457
457
  if Stripe.enable_telemetry? && context.request_id
458
- request_duration_ms = ((Time.now - request_start) * 1000).to_int
458
+ request_duration_ms =
459
+ ((Util.monotonic_time - request_start) * 1000).to_int
459
460
  @last_request_metrics =
460
461
  StripeRequestMetrics.new(context.request_id, request_duration_ms)
461
462
  end
@@ -726,7 +727,7 @@ module Stripe
726
727
  Util.log_info("Response from Stripe API",
727
728
  account: context.account,
728
729
  api_version: context.api_version,
729
- elapsed: Time.now - request_start,
730
+ elapsed: Util.monotonic_time - request_start,
730
731
  idempotency_key: context.idempotency_key,
731
732
  method: context.method,
732
733
  path: context.path,
@@ -748,7 +749,7 @@ module Stripe
748
749
 
749
750
  private def log_response_error(context, request_start, error)
750
751
  Util.log_error("Request error",
751
- elapsed: Time.now - request_start,
752
+ elapsed: Util.monotonic_time - request_start,
752
753
  error_message: error.message,
753
754
  idempotency_key: context.idempotency_key,
754
755
  method: context.method,
@@ -172,6 +172,19 @@ module Stripe
172
172
  result
173
173
  end
174
174
 
175
+ # `Time.now` can be unstable in cases like an administrator manually
176
+ # updating its value or a reconcilation via NTP. For this reason, prefer
177
+ # the use of the system's monotonic clock especially where comparing times
178
+ # to calculate an elapsed duration.
179
+ #
180
+ # Shortcut for getting monotonic time, mostly for purposes of line length
181
+ # and stubbing (Timecop doesn't freeze the monotonic clock). Returns time
182
+ # in seconds since the event used for monotonic reference purposes by the
183
+ # platform (e.g. system boot time).
184
+ def self.monotonic_time
185
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
186
+ end
187
+
175
188
  def self.normalize_id(id)
176
189
  if id.is_a?(Hash) # overloaded id
177
190
  params_hash = id.dup
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stripe
4
- VERSION = "5.3.0"
4
+ VERSION = "5.4.0"
5
5
  end
@@ -10,10 +10,9 @@ module Stripe
10
10
 
11
11
  context "#initialize" do
12
12
  should "set #last_used to current time" do
13
- t = Time.new(2019)
14
- Timecop.freeze(t) do
15
- assert_equal t, Stripe::ConnectionManager.new.last_used
16
- end
13
+ t = 123.0
14
+ Util.stubs(:monotonic_time).returns(t)
15
+ assert_equal t, Stripe::ConnectionManager.new.last_used
17
16
  end
18
17
  end
19
18
 
@@ -147,19 +146,17 @@ module Stripe
147
146
  stub_request(:post, "#{Stripe.api_base}/path")
148
147
  .to_return(body: JSON.generate(object: "account"))
149
148
 
150
- t = Time.new(2019)
149
+ t = 123.0
150
+ Util.stubs(:monotonic_time).returns(t)
151
151
 
152
- # Make sure the connection manager is initialized at a different time
153
- # than the one we're going to measure at because `#last_used` is also
154
- # set by the constructor.
155
- manager = Timecop.freeze(t) do
156
- Stripe::ConnectionManager.new
157
- end
152
+ manager = Stripe::ConnectionManager.new
158
153
 
159
- Timecop.freeze(t + 1) do
160
- manager.execute_request(:post, "#{Stripe.api_base}/path")
161
- assert_equal t + 1, manager.last_used
162
- end
154
+ # `#last_used` is also set by the constructor, so make sure we get a
155
+ # new value for it.
156
+ Util.stubs(:monotonic_time).returns(t + 1.0)
157
+
158
+ manager.execute_request(:post, "#{Stripe.api_base}/path")
159
+ assert_equal t + 1.0, manager.last_used
163
160
  end
164
161
  end
165
162
  end
@@ -27,47 +27,63 @@ module Stripe
27
27
  StripeClient.clear_all_connection_managers
28
28
 
29
29
  # Establish a base time.
30
- t = Time.new(2019)
30
+ t = 0.0
31
31
 
32
32
  # And pretend that `StripeClient` was just initialized for the first
33
33
  # time. (Don't access instance variables like this, but it's tricky to
34
34
  # test properly otherwise.)
35
35
  StripeClient.instance_variable_set(:@last_connection_manager_gc, t)
36
36
 
37
- Timecop.freeze(t) do
38
- # Execute an initial request to ensure that a connection manager was
39
- # created.
40
- client = StripeClient.new
41
- client.execute_request(:post, "/v1/path")
37
+ #
38
+ # t
39
+ #
40
+ Util.stubs(:monotonic_time).returns(t)
42
41
 
43
- # The GC shouldn't run yet (a `nil` return indicates that GC didn't run).
44
- assert_equal nil, StripeClient.maybe_gc_connection_managers
45
- end
42
+ # Execute an initial request to ensure that a connection manager was
43
+ # created.
44
+ client = StripeClient.new
45
+ client.execute_request(:post, "/v1/path")
46
46
 
47
+ # The GC shouldn't run yet (a `nil` return indicates that GC didn't run).
48
+ assert_equal nil, StripeClient.maybe_gc_connection_managers
49
+
50
+ #
51
+ # t + StripeClient::CONNECTION_MANAGER_GC_PERIOD - 1
52
+ #
47
53
  # Move time to just *before* garbage collection is eligible to run.
48
54
  # Nothing should happen.
49
- Timecop.freeze(t + StripeClient::CONNECTION_MANAGER_GC_PERIOD - 1) do
50
- assert_equal nil, StripeClient.maybe_gc_connection_managers
51
- end
55
+ #
56
+ Util.stubs(:monotonic_time).returns(t + StripeClient::CONNECTION_MANAGER_GC_PERIOD - 1)
57
+
58
+ assert_equal nil, StripeClient.maybe_gc_connection_managers
52
59
 
60
+ #
61
+ # t + StripeClient::CONNECTION_MANAGER_GC_PERIOD + 1
62
+ #
53
63
  # Move time to just *after* garbage collection is eligible to run.
54
64
  # Garbage collection will run, but because the connection manager is
55
65
  # not passed its expiry age, it will not be collected. Zero is returned
56
66
  # to indicate so.
57
- Timecop.freeze(t + StripeClient::CONNECTION_MANAGER_GC_PERIOD + 1) do
58
- assert_equal 0, StripeClient.maybe_gc_connection_managers
59
- end
67
+ #
68
+ Util.stubs(:monotonic_time).returns(t + StripeClient::CONNECTION_MANAGER_GC_PERIOD + 1)
69
+
70
+ assert_equal 0, StripeClient.maybe_gc_connection_managers
60
71
 
72
+ #
73
+ # t + StripeClient::CONNECTION_MANAGER_GC_LAST_USED_EXPIRY + 1
74
+ #
61
75
  # Move us far enough into the future that we're passed the horizons for
62
76
  # both a GC run as well as well as the expiry age of a connection
63
77
  # manager. That means the GC will run and collect the connection
64
78
  # manager that we created above.
65
- Timecop.freeze(t + StripeClient::CONNECTION_MANAGER_GC_LAST_USED_EXPIRY + 1) do
66
- assert_equal 1, StripeClient.maybe_gc_connection_managers
79
+ #
80
+ Util.stubs(:monotonic_time).returns(t + StripeClient::CONNECTION_MANAGER_GC_LAST_USED_EXPIRY + 1)
67
81
 
68
- # And as an additional check, the connection manager of the current thread context should have been set to `nil` as it was GCed.
69
- assert_nil StripeClient.current_thread_context.default_connection_manager
70
- end
82
+ assert_equal 1, StripeClient.maybe_gc_connection_managers
83
+
84
+ # And as an additional check, the connection manager of the current
85
+ # thread context should have been set to `nil` as it was GCed.
86
+ assert_nil StripeClient.current_thread_context.default_connection_manager
71
87
  end
72
88
  end
73
89
 
@@ -300,17 +316,12 @@ module Stripe
300
316
 
301
317
  context "logging" do
302
318
  setup do
303
- # Freeze time for the purposes of the `elapsed` parameter that we
304
- # emit for responses. I didn't want to bring in a new dependency for
305
- # this, but Mocha's `anything` parameter can't match inside of a hash
306
- # and is therefore not useful for this purpose. If we switch over to
307
- # rspec-mocks at some point, we can probably remove Timecop from the
308
- # project.
309
- Timecop.freeze(Time.local(1990))
310
- end
311
-
312
- teardown do
313
- Timecop.return
319
+ # Freeze time for the purposes of the `elapsed` parameter that we emit
320
+ # for responses. Mocha's `anything` parameter can't match inside of a
321
+ # hash and is therefore not useful for this purpose, and Timecop
322
+ # doesn't freeze monotonic time. If we switch over to rspec-mocks at
323
+ # some point, we can probably remove Timecop from the project.
324
+ Util.stubs(:monotonic_time).returns(0.0)
314
325
  end
315
326
 
316
327
  should "produce appropriate logging" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stripe
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.3.0
4
+ version: 5.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stripe