davidradl commented on PR #54:
URL: 
https://github.com/apache/flink-connector-http/pull/54#issuecomment-5543715065

   Just put this into my AI and it came back with:
   
   
   1. HttpSinkClientWithRetry does not retry on network exceptions
   In HttpSinkClientWithRetry.send(), the retry loop is wired only via 
retryOnResult — it retries when hasRetryableRequests() returns true. However, 
if the Java HTTP client throws (e.g., IOException for a connection timeout), 
the CompletionStage completes exceptionally. Resilience4j's 
decorateCompletionStage with only retryOnResult will not retry on exceptions 
unless retryOnException is also configured.
   Yet in HttpSinkResponseClassifier.classify(), null response → 
RETRYABLE_FAILURE — the intent is clearly that network failures should be 
retried. But that path is only reached when optResponse.orElse(null) is null 
inside prepareAttemptResult, which only runs if submitRequests succeeds. If the 
future itself fails, the path never reaches the classifier.
   Suggested fix: Add .retryOnException(IOException.class::isInstance) (or 
similar) to the RetryConfig built in HttpSinkClientWithRetry, and add a test 
case for it.
   2. Default SINK_MAX_RETRIES = 3 changes behaviour for existing deployments
   The option has a hard default of 3 in HttpDynamicSinkConnectorOptions. 
Previously retries were not performed at all. Any existing deployment that 
doesn't set this option will silently start retrying on 500/503/504, 
potentially tripling the number of HTTP calls on degraded endpoints. This is a 
silent behaviour change for existing users.
   This should either default to 0 (explicit opt-in), or be clearly called out 
in the PR description / NOTICE / migration guide, and the compatibility note in 
the PR body updated.
   3. SinkRetryConfigProvider will throw at runtime if maxRetries == 0
   return createBuilder().maxAttempts(sinkConfig.getMaxRetries() + 1).build();
   
   When maxRetries = 0, maxAttempts = 1 — that's valid, Resilience4j will make 
exactly one attempt. But createBuilder() also unconditionally reads 
getRetryStrategy() and tries to resolve RetryStrategyType.fromCode(...). If 
strategy config is missing and retries are disabled, this still constructs and 
validates the full retry configuration. Consider short-circuiting when 
maxRetries == 0 to avoid spurious validation errors and unnecessary overhead.
   4. IGNORED responses are added to successfulRequests in the accumulator
   In ResponseAccumulator.add(), IGNORED entries flow into successfulRequests. 
They're excluded from fatalFailedRequests and failedRequests, so the retry 
accumulator never sees them — but then 
SinkHttpClientResponse.successfulRequests conflates "succeeded" with "ignored", 
which may mislead callers who inspect getSuccessfulRequests() for post-callback 
processing. Consider a dedicated ignoredRequests field on 
SinkHttpClientResponse, even if the writer just combines them. This would also 
align the response shape with the four-way HttpSinkResponseStatus enum.
   5. legacyResponseChecker short-circuits before the new ignoredResponseCodes 
check
   In HttpSinkResponseClassifier.classify():
   if (ignoredResponseCodes.contains(response.statusCode())) {
       return HttpSinkResponseStatus.IGNORED;   // ✓ checked first
   }
   if (legacyResponseChecker != null) {         // ← then legacy takes over 
entirely
   
   If a user is on the legacy path and sets sink.ignored-response-codes, the 
ignored check wins — which is the correct precedence. But if a user is on the 
legacy path and does NOT set ignored codes, the new responseChecker is never 
consulted. That's intentional for compatibility, but it means 
sink.success-codes and sink.retry-codes are silently ignored when legacy 
error-code properties are present. This should be documented or at least logged 
as a warning.
   6. DefaultSinkHttpClientContext is not Serializable
   HttpSinkInternal.DefaultSinkHttpClientContext implements 
SinkHttpClientContext but is not serializable. The context is created 
transiently in buildSinkHttpClient(), so this is safe today. But 
SinkHttpClientContext itself is @PublicEvolving and users implementing custom 
SinkHttpClientBuilder will receive it — if they ever try to capture it across a 
serialisation boundary, they'll get a NotSerializableException at runtime. 
Consider either marking SinkHttpClientContext with extends Serializable or 
adding a Javadoc warning.
   7. HttpSinkConfig has a redundant getReadableConfig() method
   HttpSinkConfig stores a Configuration field (named readableConfig) and adds 
a method getReadableConfig() returning ReadableConfig. Lombok's @Getter would 
already generate getReadableConfig() returning Configuration. The custom 
override returning the ReadableConfig supertype while the @Getter-generated 
field is Configuration will cause a compilation conflict. This should be 
verified — it may require suppressing the Lombok getter for that field 
specifically.
   8. HttpSinkBuilder.resolveSinkConfig() will NPE if endpointUrl is null
   HttpSinkConfigFactory.fromDataStream(endpointUrl, ...) is called eagerly in 
build() before the existing null/blank check on endpointUrl that was in 
HttpSinkInternal. That check now operates on sinkConfig.getUrl() inside 
HttpSinkInternal, so the validation still happens — but it happens after 
resolveSinkConfig() already ran. If endpointUrl is null, fromDataStream passes 
it into the config builder without validation. The Preconditions check should 
move to resolveSinkConfig() or HttpSinkBuilder.build().
   9. Deprecated SinkHttpClientResponse constructor uses 
HttpRequest.getRequestEntries()
   The new compatibility constructor flattens List<HttpRequest> via 
request.getRequestEntries(). HttpRequest previously had no requestEntries 
field; this PR adds it as a constructor parameter. However, old custom clients 
that construct HttpRequest using the old three-arg constructor will get a 
NoSuchMethodError at runtime — the deprecated constructor doesn't actually 
provide binary compatibility. This should be noted in the Javadoc or the field 
given a nullable default path.
   10. The SINK_WRITER_THREAD_POOL_SIZE default changed from "4" (string) to 1
   HttpSinkWriter previously defaulted to 4 threads 
(HTTP_SINK_WRITER_THREAD_POOL_SIZE = "4"). 
HttpDynamicSinkConnectorOptions.SINK_WRITER_THREAD_POOL_SIZE now has 
.defaultValue(1). This is a silent performance regression for existing users. 
Intentional?
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to