[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