Skip to content

Branched Exception Handling In Mutiny reactive #45469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
prakashelango opened this issue Jan 9, 2025 · 5 comments
Open

Branched Exception Handling In Mutiny reactive #45469

prakashelango opened this issue Jan 9, 2025 · 5 comments
Labels

Comments

@prakashelango
Copy link

prakashelango commented Jan 9, 2025

Describe the bug

import io.quarkus.logging.Log;
import io.smallrye.mutiny.Uni;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbAsyncTable;
import software.amazon.awssdk.enhanced.dynamodb.Expression;
import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest;
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException;
import software.amazon.awssdk.services.dynamodb.model.InternalServerErrorException;
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughputExceededException;
import software.amazon.awssdk.services.dynamodb.model.RequestLimitExceededException;

public class GenericRepositoryHelper<T> {

    private final DynamoDbAsyncTable<T> table;

    public GenericRepositoryHelper(DynamoDbAsyncTable<T> table) {
        this.table = table;
    }

    public Uni<T> save(final T entity) {
        return Uni.createFrom().completionStage(table.putItemWithResponse(toPutItemRequest(entity)))
            .map(resp -> entity)
            .onFailure(ProvisionedThroughputExceededException.class).recoverWithUni(fail -> handleRetryableException(fail, "ProvisionedThroughputExceededException occurred during put item", entity))
            .onFailure(RequestLimitExceededException.class).recoverWithUni(fail -> handleRetryableException(fail, "RequestLimitExceededException occurred during put item operation", entity))
            .onFailure(InternalServerErrorException.class).recoverWithUni(fail -> handleRetryableException(fail, "InternalServerErrorException occurred during put item operation", entity))
            .onFailure(ConditionalCheckFailedException.class).recoverWithUni(fail -> {
                Log.errorf("ConditionalCheckFailedException occurred during put item operation: %s", fail.getMessage());
                return Uni.createFrom().failure(new NonRetryableException(fail.getMessage(), fail));
            })
            .onFailure().recoverWithUni(fail -> handleRetryableException(fail, "GenericException occurred during put item operation", entity));
    }

    private PutItemEnhancedRequest<T> toPutItemRequest(final T entity) {
        return PutItemEnhancedRequest.builder(entity.getClass())
            .item(entity)
            .conditionExpression(Expression.builder().expression("attribute_not_exists(pk) and attribute_not_exists(sk)").build())
            .build();
    }

    private Uni<T> handleRetryableException(final Throwable failure, final String message, final T entity) {
        Log.errorf("%s %s", message, failure);
        if (failure instanceof NonRetryableException) {
            return Uni.createFrom().failure(failure);
        }
        return Uni.createFrom().failure(new RetryableException(failure.getMessage(), new UnprocessedItemsException(entity)));
    }
}
 .onFailure(ConditionalCheckFailedException.class).recoverWithUni(fail -> { even with specified exception generic exception handler gets invoked which leads to add another condition in retryableException expectation is similar to try catch block will be preferred
                Log.errorf("ConditionalCheckFailedException occurred during put item operation: %s", fail.getMessage());
                return Uni.createFrom().failure(new NonRetryableException(fail.getMessage(), fail));
            })
            .onFailure().recoverWithUni(fail -> handleRetryableException(fail, "GenericException occurred during put item operation", entity));

Note: Changing order of exception handling pipes doesn't take any effect

Expected behavior

No response

Actual behavior

No response

How to Reproduce?

No response

Output of uname -a or ver

No response

Output of java -version

21

Quarkus version or git rev

No response

Build tool (ie. output of mvnw --version or gradlew --version)

gradle

Additional information

No response

@prakashelango prakashelango added the kind/bug Something isn't working label Jan 9, 2025
Copy link

quarkus-bot bot commented Jan 9, 2025

/cc @cescoffier (mutiny), @jponge (mutiny)

@jponge
Copy link
Member

jponge commented Jan 9, 2025

Can you please provide an explanation of what you think is wrong?

@geoand geoand added the triage/needs-feedback We are waiting for feedback. label Jan 9, 2025
@prakashelango
Copy link
Author

.onFailure(ProvisionedThroughputExceededException.class).recoverWithUni(fail -> handleRetryableException(fail, "ProvisionedThroughputExceededException occurred during put item", entity))
            .onFailure(RequestLimitExceededException.class).recoverWithUni(fail -> handleRetryableException(fail, "RequestLimitExceededException occurred during put item operation", entity))
            .onFailure(InternalServerErrorException.class).recoverWithUni(fail -> handleRetryableException(fail, "InternalServerErrorException occurred during put item operation", entity))
            .onFailure(ConditionalCheckFailedException.class).recoverWithUni(fail -> {
                Log.errorf("ConditionalCheckFailedException occurred during put item operation: %s", fail.getMessage());
                return Uni.createFrom().failure(new NonRetryableException(fail.getMessage(), fail));
            })
            .onFailure().recoverWithUni(fail -> handleRetryableException(fail, "GenericException occurred during put item operation", entity));

Given that failure of Ex: ConditionalCheckFailedException reactive pipeline should return Uni of (NonRetryableException) method execution should be passed back to caller. whereas Uni(NonRetryableException) being carried over to generic exception handler .onFailure().recoverWithUni( leading to invocation of handleRetryableException one more which adds additional ```
if (failure instanceof NonRetryableException) {
return Uni.createFrom().failure(failure);
}

@geoand geoand removed the triage/needs-feedback We are waiting for feedback. label Feb 11, 2025
@prakashelango
Copy link
Author

@jponge can help on this.

@cescoffier
Copy link
Member

There is an open PR on Mutiny to add that feature. However, it's a breaking change, and it will have to wait a Mutiny 3.0.
See smallrye/smallrye-mutiny#1848

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants