[External] : Re: Changes to JEP 453
forax at univ-mlv.fr
forax at univ-mlv.fr
Tue May 23 21:00:00 UTC 2023
----- Original Message -----
> From: "Ron Pressler" <ron.pressler at oracle.com>
> To: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "loom-dev" <loom-dev at openjdk.org>
> Sent: Tuesday, May 23, 2023 8:27:25 PM
> Subject: Re: [External] : Re: Changes to JEP 453
>> On 23 May 2023, at 17:47, forax at univ-mlv.fr wrote:
>>
>> Returning the default value instead of an exception inside the callable is a
>> useful simplification.
>> But it does not work in all cases, by example, if the default value of a
>> callable is computed from the result of another async call belonging to the
>> same scope.
>>
>> There is quite a lot of business cases based on the idea that if some data are
>> missing, they will be replaced by less accurate data available that are good
>> enough.
>
> Sure, but “give me the best successful result among these forks” doesn’t require
> treating their exceptions individually. It’s a somewhat more elaborate policy
> than ShutdownOnSuccess, and I don’t see how exception transparency for the
> individual forks would be useful.
>
>>
>> When you implement a REST API, you have the problem of mapping the error to the
>> right HTTP error code, so in Java, an exception subclass to the right HTTP
>> code.
>> Most frameworks/libraries centralize this mapping per service (which is a good
>> idea), so it's usual to have a code inside the callable to wrap the exception
>> to a specific business exception (DeliveryServiceException,
>> UserMappingServiceException, etc) that will be used to map to the correct HTTP
>> error code.
>
> Great, then do that transformation in the fork by wrapping the subtask. The code
> that wraps the fork is also the responsibility of the scope.
>
>> In that setup, it's more useful to get the exception thrown by the callable than
>> a wrapping exception. And if you want to log the exception without loosing the
>> context, you can wrap it.
>
> STS itself doesn’t wrap any exceptions. The two policies throw their *own*
> exception when they fail and those exceptions may, indeed, wrap some fork
> exception, but it’s a failure of the scope, not of the forks.
>
> While computing its own outcome, the scope examines the Subtasks, where it has
> access to the fork’s actual exceptions. There you can pattern match on
> exception types if you like. If you need to consider the actual heterogeneity
> of exception types among forks, do that processing inside the fork and throw
> some uniform exceptions.
>
> But perhaps I just don’t understand what you’re saying. Can you show a code
> example (as short as possible) of what you think would be a common situation
> where exception transparency would be useful?
Let say I've two services, one that give me a Customer from a customer id and another that give me the product description from a product id,
and i want to send a mail containing the customer name and a description.
Alternative scenario, if the product service reports an error, a default description is provided depending on the language of the customer.
record Customer(long id, String name, Locale locale) {}
record ProductDescription(long id, String description) {}
static sealed class ApplicationException extends Exception { ... }
static final class ServiceErrorException extends ApplicationException { ... }
static final class CustomerNotFoundException extends ApplicationException { ... }
record CustomerService() {
Customer findCustomerFromId(long id) throws ApplicationException {
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
throw new ServiceErrorException(e);
}
if (id == 0) {
throw new CustomerNotFoundException();
}
return new Customer(id, "Bob", Locale.of("fr")); // fake data
}
}
record ProductService() {
ProductDescription findProductDescription(long id) throws ApplicationException {
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
throw new ServiceErrorException(e);
}
return new ProductDescription(id, "description"); // fake data
}
}
private static String getDefaultDescription(Locale locale) {
return "fake data";
}
record MailData(String customerName, String productDescription) {}
static MailData getMailData(long customerId, long productId) throws ApplicationException {
var customerService = new CustomerService();
var productService = new ProductService();
try(var sts = new StructuredTaskScope<Object, ApplicationException>()) {
// both calls can be done in parallel
var customerTask = sts.fork(() -> customerService.findCustomerFromId(customerId));
var productDescriptionTask = sts.fork(() -> productService.findProductDescription(productId));
sts.join();
// the business logic is implemented here
var customer = customerTask.result(); // should propagate the exception transparently
String productDescription;
try {
productDescription = productDescriptionTask.result().description();
} catch(ApplicationException e) { // the exception type is not erased
productDescription = getDefaultDescription(customer.locale());
}
return new MailData(customer.name(), productDescription);
} catch (InterruptedException e) {
throw new ServiceErrorException(e);
}
}
findCustomerfromId, findProductDescription and getMailData() all throw an ApplicationException.
getMailData() can itself be called in another STS, composition is easy !
Exceptions naturally flows with no wrapping by default.
[...]
>
> — Ron
Rémi
More information about the loom-dev
mailing list