Skip to content

Throw FutureCancelledException when api call timed out using S3AsyncClient #2713

@pkgonan

Description

@pkgonan

Describe the bug

FutureCancelledException is thrown when api call timed out. (Using S3AsyncClient)

Expected Behavior

It should be not throw FutureCancelledException when api call timed out.

Current Behavior

It throw FutureCancelledException, and logging messages.

An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
FutureCancelledException
software.amazon.awssdk.core.exception.ApiCallAttemptTimeoutException: HTTP request execution did not complete before the specified timeout configuration: 60000 millis

Sentry Capture

스크린샷 2021-09-15 오전 2 30 40

S3AsyncClient Configuration

@Configuration
@EnableConfigurationProperties(S3ClientConfig.S3ClientConfigurationProperties::class)
open class S3ClientConfig(
    private val properties: S3ClientConfigurationProperties
) {

    companion object {
        private const val HTTP_CLIENT_MAX_CONCURRENCY = 10000
        private const val MAX_PENDING_CONNECTION_ACQUIRE = 10000

        private val LOG = LoggerFactory.getLogger(this::class.java)
        private val CONNECTION_TIMEOUT = Duration.ofSeconds(5)
        private val CONNECTION_ACQUISITION_TIMEOUT = Duration.ofSeconds(5)
        private val WRITE_TIMEOUT = Duration.ofMinutes(3)
        private val READ_TIMEOUT = Duration.ofMinutes(3)
    }

    @Bean
    open fun s3AsyncClient(): S3AsyncClient {
        val clientBuilder = S3AsyncClient.builder()
            .httpClient(getHttpClient())
            .serviceConfiguration(getServiceConfiguration())
            .region(getS3Region())
            .credentialsProvider(getS3CredentialProvider())
            .overrideConfiguration { it
                .retryPolicy(RetryPolicy.none())
                .apiCallAttemptTimeout(Duration.ofMinutes(1L))
                .apiCallTimeout(Duration.ofMinutes(2L))
            }
            .asyncConfiguration { it.advancedOption(SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR, Dispatchers.IO.asExecutor()) }

        if (properties.getEndpoint() != null) {
            clientBuilder.endpointOverride(properties.getEndpoint())
        }

        return clientBuilder.build()
    }

    private fun getHttpClient(): SdkAsyncHttpClient {
        return NettyNioAsyncHttpClient.builder()
            .protocol(Protocol.HTTP1_1)
            .tcpKeepAlive(true)
            .writeTimeout(WRITE_TIMEOUT)
            .readTimeout(READ_TIMEOUT)
            .connectionTimeout(CONNECTION_TIMEOUT)
            .connectionAcquisitionTimeout(CONNECTION_ACQUISITION_TIMEOUT)
            .maxConcurrency(HTTP_CLIENT_MAX_CONCURRENCY)
            .maxPendingConnectionAcquires(MAX_PENDING_CONNECTION_ACQUIRE)
            .build()
    }

    private fun getServiceConfiguration(): S3Configuration {
        return S3Configuration.builder()
            .checksumValidationEnabled(false)
            .chunkedEncodingEnabled(true)
            .accelerateModeEnabled(true)
            .dualstackEnabled(true)
            .build()
    }

    private fun getS3Region(): Region {
        return Region.of(properties.getRegion())
    }

    private fun getS3CredentialProvider(): AwsCredentialsProvider {
        try {
            return DefaultCredentialsProvider.create()
        } catch (ex: Exception) {
            LOG.error("Invalid S3 Credential Configuration Properties")
            throw InvalidAWSCredentialException("Invalid AWS Credential detected")
        }
    }

    @ConstructorBinding
    @ConfigurationProperties("test.storage.aws.s3")
    @Validated
    class S3ClientConfigurationProperties(
        @field:NotBlank private val region: String,
        private val endpoint: URI?
    ) {

        internal fun getRegion() = region
        internal fun getEndpoint() = endpoint

        override fun toString(): String {
            return "S3ClientConfigurationProperties(" +
                    "region=${getRegion()}, " +
                    "endpoint=${getEndpoint()}, " +
                    ")"
        }
    }
}

Environment

  • Spring Boot : 2.5.4
  • Kotlin : 1.5.30
  • org.jetbrains.kotlinx:kotlinx-coroutines-bom : 1.5.1
    • org.jetbrains.kotlinx:kotlinx-coroutines-core
    • org.jetbrains.kotlinx:kotlinx-coroutines-jdk8
    • org.jetbrains.kotlinx:kotlinx-coroutines-reactor
  • AWS Java SDK V2 version : 2.17.36
    • software.amazon.awssdk:bom
    • software.amazon.awssdk:s3
    • software.amazon.awssdk:netty-nio-client
    • software.amazon.awssdk:sts
  • JDK version : java-11-amazon-corretto
  • Operating System and version: Amazon Linux 2

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThis issue is a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions