code: public static <T> List<T> gatherIO(List<? extends CompletableFuture<? extends T>> futures,
                                       CompletionStage<Void> timeout)
        throws IOException {
      // Apply timeout
      timeout.thenRun(() -> futures.forEach(f -> f.completeExceptionally(new TimeoutException())));
      // Collect results
      var values = ImmutableList.<T>builder();
      for (var future : futures) {
        try {
          values.add(future.get());
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
          throw new IOException(e);
        } catch (ExecutionException e) {
          final Throwable cause = e.getCause();
          Throwables.propagateIfPossible(cause, IOException.class);
          if (cause instanceof TimeoutException) {
            throw new IOException(cause);
          } else {
            throw new RuntimeException(cause);
          }
        }
      }
      return values.build();
    }
docs: Gathers results from futures that may fail with IOExceptions.
  @param timeout A global timeout. All futures must have completed before this future completes. Any future
  not yet completed will be cancelled.
  @return The values of all futures, in the same order.
  @throws IOException if interrupted or any of the futures timed out or failed with an {@link IOException}.