docs: @Trivial // traced by caller
      public static CompletableFuture<Void> runAsync(Runnable action, Executor executor) {
          // Reject ManagedTask so that we have the flexibility to decide later how to handle ManagedTaskListener and execution properties
          if (action instanceof ManagedTask)
              throw new IllegalArgumentException(ManagedTask.class.getName());
  
          FutureRefExecutor futureExecutor = supportsAsync(executor);
  
          ThreadContextDescriptor contextDescriptor;
          if (action instanceof ContextualSupplier) {
              ContextualRunnable r = (ContextualRunnable) action;
              contextDescriptor = r.getContextDescriptor();
              action = r.getAction();
          } else if (executor instanceof WSManagedExecutorService) {
              WSContextService contextSvc = ((WSManagedExecutorService) executor).getContextService();
              contextDescriptor = contextSvc.captureThreadContext(XPROPS_SUSPEND_TRAN);
          } else {
              contextDescriptor = null;
          }
  
          if (JAVA8) {
              action = new ContextualRunnable(contextDescriptor, action);
              CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(action, futureExecutor == null ? executor : futureExecutor);
              return new ManagedCompletableFuture<Void>(completableFuture, executor, futureExecutor);
          } else {
              ManagedCompletableFuture<Void> completableFuture = new ManagedCompletableFuture<Void>(executor, futureExecutor);
              action = new ContextualRunnable(contextDescriptor, action, completableFuture);
              (futureExecutor == null ? executor : futureExecutor).execute(action);
              return completableFuture;
          }
      }