stripe 5.3.0 → 5.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 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