parse-stack-next 5.7.3 → 5.7.4
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 +4 -4
- data/CHANGELOG.md +26 -0
- data/lib/parse/client.rb +85 -12
- data/lib/parse/stack/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef10cccc964e68942152a64d489ea8044a99eb0b76a3fea00d75c311ddf94d23
|
|
4
|
+
data.tar.gz: 357793ece65c1bb58147e160fb8c95782e3496deac3a1b843a82faf9dcd06737
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 44ed4c55b5388cd068c723f01bfa02c3efa4fafe61adfa328197a76d0bcf4006eeab313d7d3a42a8b2088b43a4a6a25bd9b89868fe183daa744c3f324c0ef0f2
|
|
7
|
+
data.tar.gz: 721eebb0297114c0e439babab1c0c39bb1bb9697788ff4197b6d9cf072d80b1fb7d3783f1c629ea0062314321b0695565bd7e252c4e7c39e1940bcf93adabcb7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
## parse-stack-next Changelog
|
|
2
2
|
|
|
3
|
+
### 5.7.4
|
|
4
|
+
|
|
5
|
+
#### Reset connections are retried instead of surfacing a raw Faraday error
|
|
6
|
+
|
|
7
|
+
A focused fix release for the request retry mechanism. A stale keep-alive
|
|
8
|
+
connection (a pooled persistent connection closed by the server or a load
|
|
9
|
+
balancer after idling) failed the next request with a raw
|
|
10
|
+
`Faraday::ConnectionFailed` / `Errno::ECONNRESET` instead of retrying, even
|
|
11
|
+
though an immediate re-send on a fresh connection succeeds. Reset connections
|
|
12
|
+
now retry under the same idempotency rules as read timeouts.
|
|
13
|
+
|
|
14
|
+
- **FIXED**: `Parse::Client#request` now rescues `Faraday::ConnectionFailed`
|
|
15
|
+
and inspects the wrapped cause. Reset-class causes (`Errno::ECONNRESET`,
|
|
16
|
+
`Errno::EPIPE`, `Errno::ECONNABORTED`, and the `EOFError` raised when the
|
|
17
|
+
remote end closes a keep-alive socket cleanly) are transient, so idempotent
|
|
18
|
+
requests (GET, DELETE, op-free PUT, and any write covered by asserted
|
|
19
|
+
server-side request-id dedup) retry with the standard backoff. Connection
|
|
20
|
+
refused and DNS failures keep the previous fail-fast behavior and propagate
|
|
21
|
+
the raw `Faraday::ConnectionFailed` with no retry latency.
|
|
22
|
+
- **CHANGED**: A reset connection that persists through the whole retry budget
|
|
23
|
+
now raises `Parse::Error::ConnectionError` (consistent with the read-timeout
|
|
24
|
+
path) instead of the raw `Faraday::ConnectionFailed`. Code that rescued
|
|
25
|
+
`Faraday::ConnectionFailed` to catch resets should rescue
|
|
26
|
+
`Parse::Error::ConnectionError` instead; refused and DNS failures still
|
|
27
|
+
raise `Faraday::ConnectionFailed`.
|
|
28
|
+
|
|
3
29
|
### 5.7.3
|
|
4
30
|
|
|
5
31
|
#### Stored values can no longer drive the operator's terminal
|
data/lib/parse/client.rb
CHANGED
|
@@ -1424,6 +1424,30 @@ module Parse
|
|
|
1424
1424
|
retry
|
|
1425
1425
|
end
|
|
1426
1426
|
raise
|
|
1427
|
+
rescue Faraday::ConnectionFailed => e
|
|
1428
|
+
# `Faraday::ConnectionFailed` covers two very different failures under
|
|
1429
|
+
# one class, so it is split on the wrapped cause (see
|
|
1430
|
+
# #connection_reset_error?):
|
|
1431
|
+
#
|
|
1432
|
+
# - RESET mid-flight (`Errno::ECONNRESET` / `Errno::EPIPE` /
|
|
1433
|
+
# `Errno::ECONNABORTED` / `EOFError`): the classic stale
|
|
1434
|
+
# keep-alive failure. A pooled persistent connection idled past
|
|
1435
|
+
# the server's (or an LB's) keep-alive window and was closed
|
|
1436
|
+
# remotely, and the next request on it dies at the socket.
|
|
1437
|
+
# Transient by nature (a fresh connection succeeds immediately),
|
|
1438
|
+
# so it retries under the same idempotency rules as a read
|
|
1439
|
+
# timeout: the outcome is unknown, so only idempotent requests
|
|
1440
|
+
# are re-sent.
|
|
1441
|
+
#
|
|
1442
|
+
# - REFUSED (and DNS failure): the server is down or misconfigured.
|
|
1443
|
+
# Retrying only adds backoff latency and `[Parse:Retry]` noise
|
|
1444
|
+
# before the inevitable error, so it propagates raw and fast.
|
|
1445
|
+
raise unless connection_reset_error?(e)
|
|
1446
|
+
if _retry_count > 0 && idempotent_retry?(method, body, headers)
|
|
1447
|
+
_retry_count = consume_retry_with_backoff(_retry_count, _retry_max, _request)
|
|
1448
|
+
retry
|
|
1449
|
+
end
|
|
1450
|
+
raise Parse::Error::ConnectionError, "#{_request} : #{e.class} - #{e.message}"
|
|
1427
1451
|
rescue Faraday::ClientError, Faraday::TimeoutError, Net::OpenTimeout => e
|
|
1428
1452
|
# Request timed out mid-flight: the outcome is unknown (the server may
|
|
1429
1453
|
# have received and applied the write but never answered), so only
|
|
@@ -1431,25 +1455,74 @@ module Parse
|
|
|
1431
1455
|
#
|
|
1432
1456
|
# Faraday 2.x raises `Faraday::TimeoutError` for a read timeout
|
|
1433
1457
|
# (`Timeout::Error` / `Errno::ETIMEDOUT`); it subclasses `Faraday::Error`,
|
|
1434
|
-
# not `ClientError`, so it must be listed explicitly to be caught.
|
|
1435
|
-
#
|
|
1436
|
-
#
|
|
1437
|
-
# non-transient "server down / misconfigured" failure, and auto-retrying
|
|
1438
|
-
# it only adds backoff latency before the inevitable error. Broadening to
|
|
1439
|
-
# reset connections safely (retry reset, fail fast on refused) is tracked
|
|
1440
|
-
# as a follow-up.
|
|
1458
|
+
# not `ClientError`, so it must be listed explicitly to be caught.
|
|
1459
|
+
# `Faraday::ConnectionFailed` is handled in its own rescue above,
|
|
1460
|
+
# split into retry-reset / fail-fast-refused.
|
|
1441
1461
|
if _retry_count > 0 && idempotent_retry?(method, body, headers)
|
|
1442
|
-
|
|
1443
|
-
_retry_count -= 1
|
|
1444
|
-
backoff_delay = RETRY_DELAY * (_retry_max - _retry_count)
|
|
1445
|
-
_retry_delay = backoff_delay * (0.75 + rand * 0.5)
|
|
1446
|
-
sleep _retry_delay if _retry_delay > 0
|
|
1462
|
+
_retry_count = consume_retry_with_backoff(_retry_count, _retry_max, _request)
|
|
1447
1463
|
retry
|
|
1448
1464
|
end
|
|
1449
1465
|
raise Parse::Error::ConnectionError, "#{_request} : #{e.class} - #{e.message}"
|
|
1450
1466
|
end
|
|
1451
1467
|
end
|
|
1452
1468
|
|
|
1469
|
+
# Consumes one attempt from the retry budget: logs the remaining count,
|
|
1470
|
+
# sleeps the linear backoff (RETRY_DELAY x attempt number, +/-25% jitter,
|
|
1471
|
+
# never zero), and returns the decremented budget. Shared by the
|
|
1472
|
+
# connection-reset and timeout rescue branches in {#request} so their
|
|
1473
|
+
# backoff behavior cannot drift apart; the `retry` keyword itself must
|
|
1474
|
+
# stay lexically inside each rescue clause, so it remains at the call
|
|
1475
|
+
# sites. The 429/503 branch keeps its own inline version because it also
|
|
1476
|
+
# honors a server-supplied Retry-After header.
|
|
1477
|
+
# @param retry_count [Integer] the remaining retry budget (must be > 0).
|
|
1478
|
+
# @param retry_max [Integer] the effective starting budget.
|
|
1479
|
+
# @param request [Parse::Request] the request being retried (for logging).
|
|
1480
|
+
# @return [Integer] the decremented retry budget.
|
|
1481
|
+
def consume_retry_with_backoff(retry_count, retry_max, request)
|
|
1482
|
+
warn "[Parse:Retry] Retries remaining #{retry_count} : #{request}"
|
|
1483
|
+
retry_count -= 1
|
|
1484
|
+
backoff_delay = RETRY_DELAY * (retry_max - retry_count)
|
|
1485
|
+
retry_delay = backoff_delay * (0.75 + rand * 0.5)
|
|
1486
|
+
sleep retry_delay if retry_delay > 0
|
|
1487
|
+
retry_count
|
|
1488
|
+
end
|
|
1489
|
+
|
|
1490
|
+
# The wrapped causes that mark a `Faraday::ConnectionFailed` as a RESET
|
|
1491
|
+
# connection (transient, retry-safe for idempotent requests) rather than a
|
|
1492
|
+
# REFUSED one (server down, fail fast). `EOFError` is what net/http raises
|
|
1493
|
+
# when the remote end closes a keep-alive socket cleanly between requests;
|
|
1494
|
+
# ECONNRESET/EPIPE/ECONNABORTED are the unclean variants.
|
|
1495
|
+
# @!visibility private
|
|
1496
|
+
CONNECTION_RESET_CAUSES = [
|
|
1497
|
+
Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, EOFError,
|
|
1498
|
+
].freeze
|
|
1499
|
+
|
|
1500
|
+
# Message fallback for adapters that raise `Faraday::ConnectionFailed`
|
|
1501
|
+
# with the cause flattened into the message instead of wrapped.
|
|
1502
|
+
# @!visibility private
|
|
1503
|
+
CONNECTION_RESET_MESSAGE = /connection reset|broken pipe|end of file reached/i
|
|
1504
|
+
|
|
1505
|
+
# Whether a `Faraday::ConnectionFailed` was caused by a reset/dropped
|
|
1506
|
+
# connection (retryable) as opposed to connection-refused or a DNS
|
|
1507
|
+
# failure (fail fast). Walks the wrapped exception and the `#cause`
|
|
1508
|
+
# chain looking for a reset-class error.
|
|
1509
|
+
# @param error [Exception] the rescued `Faraday::ConnectionFailed`.
|
|
1510
|
+
# @return [Boolean]
|
|
1511
|
+
def connection_reset_error?(error)
|
|
1512
|
+
inner = error.respond_to?(:wrapped_exception) ? error.wrapped_exception : nil
|
|
1513
|
+
inner ||= error.cause
|
|
1514
|
+
seen = 0
|
|
1515
|
+
while inner && seen < 8
|
|
1516
|
+
return true if CONNECTION_RESET_CAUSES.any? { |klass| inner.is_a?(klass) }
|
|
1517
|
+
inner = inner.cause
|
|
1518
|
+
seen += 1
|
|
1519
|
+
end
|
|
1520
|
+
CONNECTION_RESET_MESSAGE.match?(error.message.to_s)
|
|
1521
|
+
end
|
|
1522
|
+
|
|
1523
|
+
private :consume_retry_with_backoff, :connection_reset_error?
|
|
1524
|
+
private_constant :CONNECTION_RESET_CAUSES, :CONNECTION_RESET_MESSAGE
|
|
1525
|
+
|
|
1453
1526
|
# Whether a request whose outcome is UNKNOWN (a 500/503 or a dropped
|
|
1454
1527
|
# connection) is safe to transparently re-send.
|
|
1455
1528
|
#
|
data/lib/parse/stack/version.rb
CHANGED