Uploaded image for project: 'Weld'
  1. Weld
  2. WELD-2711

Event#fireAsync behaves inconsistently on exceptions

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Not a Bug
    • Icon: Major Major
    • None
    • 3.1.9.Final, 4.0.3.Final, 5.0.0.CR2
    • Events
    • None
    • Hide
      @EnableAutoWeld
      class WeldTest {
      
          @Inject
          Observer1 observer1;
      
          @Inject
          Observer2 observer2;
      
          @Inject
          Event<Integer> event;
      
          @Test
          void testSingleFailure() throws InterruptedException, TimeoutException {
              final CompletionStage<Integer> stage = event.fireAsync(42);
      
              try {
                  stage.toCompletableFuture().get(30, TimeUnit.SECONDS);
                  failBecauseExceptionWasNotThrown(ExecutionException.class);
              } catch (ExecutionException ex) {
                  assertThat(ex.getCause()).isInstanceOf(CompletionException.class)
                          .extracting(Throwable::getSuppressed, as(ARRAY))
                          .hasSize(1)
                          .hasAtLeastOneElementOfType(IllegalArgumentException.class);
              }
          }
      
          @Test
          void testTwoFailures() throws InterruptedException, TimeoutException {
              final CompletionStage<Integer> stage = event.fireAsync(47);
      
              try {
                  stage.toCompletableFuture().get(30, TimeUnit.SECONDS);
                  failBecauseExceptionWasNotThrown(ExecutionException.class);
              } catch (ExecutionException ex) {
                  assertThat(ex.getCause()).isInstanceOf(CompletionException.class)
                          .extracting(Throwable::getSuppressed, as(ARRAY))
                          .hasSize(2)
                          .hasAtLeastOneElementOfType(IllegalArgumentException.class)
                          .hasAtLeastOneElementOfType(IllegalStateException.class);
              }
          }
      
          @ApplicationScoped
          static class Observer1 {
      
              void failOn42And47(@ObservesAsync int value) {
                  if (value == 42 || value == 47) {
                      throw new IllegalArgumentException();
                  }
              }
          }
      
          @ApplicationScoped
          static class Observer2 {
      
              void failOn47(@ObservesAsync int value) {
                  if (value == 47) {
                      throw new IllegalStateException();
                  }
              }
          }
      }
      
      Show
      @EnableAutoWeld class WeldTest { @Inject Observer1 observer1; @Inject Observer2 observer2; @Inject Event< Integer > event; @Test void testSingleFailure() throws InterruptedException, TimeoutException { final CompletionStage< Integer > stage = event.fireAsync(42); try { stage.toCompletableFuture().get(30, TimeUnit.SECONDS); failBecauseExceptionWasNotThrown(ExecutionException.class); } catch (ExecutionException ex) { assertThat(ex.getCause()).isInstanceOf(CompletionException.class) .extracting(Throwable::getSuppressed, as(ARRAY)) .hasSize(1) .hasAtLeastOneElementOfType(IllegalArgumentException.class); } } @Test void testTwoFailures() throws InterruptedException, TimeoutException { final CompletionStage< Integer > stage = event.fireAsync(47); try { stage.toCompletableFuture().get(30, TimeUnit.SECONDS); failBecauseExceptionWasNotThrown(ExecutionException.class); } catch (ExecutionException ex) { assertThat(ex.getCause()).isInstanceOf(CompletionException.class) .extracting(Throwable::getSuppressed, as(ARRAY)) .hasSize(2) .hasAtLeastOneElementOfType(IllegalArgumentException.class) .hasAtLeastOneElementOfType(IllegalStateException.class); } } @ApplicationScoped static class Observer1 { void failOn42And47(@ObservesAsync int value) { if (value == 42 || value == 47) { throw new IllegalArgumentException(); } } } @ApplicationScoped static class Observer2 { void failOn47(@ObservesAsync int value) { if (value == 47) { throw new IllegalStateException(); } } } }

      If an event is fired (and observed) asynchronously, the returned CompletionStage behaves differently in the case of exceptions depending on the number of observers that threw exceptions: If only one observer throws, the exception is directly put into the CompletionStage, if more than one observer throws, the exceptions are first composed into a CompletionException and that is put into the CompletionStage.

       

      This violates the CDI Spec, 10.5.1 or at least my reading of the spec. Since the programmer cannot know how many observer methods have thrown exceptions (in general, one does not even know how many there are), the exceptions should be accessed in a uniform way, no matter how many there are, i.e. they should always be treated as if there are more than one.

              Unassigned Unassigned
              johannes-hahn Johannes Hahn (Inactive)
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

                Created:
                Updated:
                Resolved: