docs: public void scanAsync(final ExecutorService executorService, final int numParallelTasks,
              final ScanResultProcessor scanResultProcessor, final FailureHandler failureHandler) {
          if (scanResultProcessor == null) {
              // If scanResultProcessor is null, the scan won't do anything after completion, and the ScanResult will
              // simply be lost.
              throw new IllegalArgumentException("scanResultProcessor cannot be null");
          }
          if (failureHandler == null) {
              // The result of the Future<ScanObject> object returned by launchAsyncScan is discarded below, so we
              // force the addition of a FailureHandler so that exceptions are not silently swallowed.
              throw new IllegalArgumentException("failureHandler cannot be null");
          }
          // Use execute() rather than submit(), since a ScanResultProcessor and FailureHandler are used
          executorService.execute(new Runnable() {
              @Override
              public void run() {
                  try {
                      new Scanner(scanSpec, executorService, numParallelTasks, scanResultProcessor, failureHandler,
                              topLevelLog);
                  } catch (final InterruptedException e) {
                      // Interrupted during the Scanner constructor's execution (specifically, by getModuleOrder(),
                      // which is unlikely to ever actually be interrupted -- but this exception needs to be caught)
                      failureHandler.onFailure(e);
                  }
              }
          });
      }