@SuppressWarnings({"unchecked", "deprecation"})
  protected void startWithChefSoloAsync() {
    String baseDir =
        MachineLifecycleEffectorTasks.resolveOnBoxDir(
            entity(),
            Machines.findUniqueMachineLocation(entity().getLocations(), SshMachineLocation.class)
                .get());
    String installDir = Urls.mergePaths(baseDir, "installs/chef");

    @SuppressWarnings("rawtypes")
    Map<String, String> cookbooks =
        (Map)
            ConfigBag.newInstance(entity().getConfig(CHEF_COOKBOOK_URLS))
                .putIfAbsent(entity().getConfig(CHEF_COOKBOOKS))
                .getAllConfig();
    if (cookbooks.isEmpty())
      log.warn("No cookbook_urls set for " + entity() + "; launch will likely fail subsequently");
    DynamicTasks.queue(
        ChefSoloTasks.installChef(installDir, false),
        ChefSoloTasks.installCookbooks(installDir, cookbooks, false));

    // TODO chef for and run a prestart recipe if necessary
    // TODO open ports

    String primary = getPrimaryCookbook();

    // put all config under brooklyn/cookbook/config
    Navigator<MutableMap<Object, Object>> attrs = Jsonya.newInstancePrimitive().at("brooklyn");
    if (Strings.isNonBlank(primary)) attrs.at(primary);
    attrs.at("config");
    attrs.put(entity().getAllConfigBag().getAllConfig());
    // and put launch attrs at root
    try {
      attrs
          .root()
          .put(
              (Map<?, ?>)
                  Tasks.resolveDeepValue(
                      entity().getConfig(CHEF_LAUNCH_ATTRIBUTES),
                      Object.class,
                      entity().getExecutionContext()));
    } catch (Exception e) {
      Exceptions.propagate(e);
    }

    Collection<? extends String> runList = entity().getConfig(CHEF_LAUNCH_RUN_LIST);
    if (runList == null) runList = entity().getConfig(CHEF_RUN_LIST);
    if (runList == null) {
      if (Strings.isNonBlank(primary)) runList = ImmutableList.of(primary + "::" + "start");
      else
        throw new IllegalStateException(
            "Require a primary cookbook or a run_list to effect " + "start" + " on " + entity());
    }

    String runDir =
        Urls.mergePaths(
            baseDir,
            "apps/"
                + entity().getApplicationId()
                + "/chef/entities/"
                + entity().getEntityType().getSimpleName()
                + "_"
                + entity().getId());

    DynamicTasks.queue(
        ChefSoloTasks.buildChefFile(
            runDir, installDir, "launch", runList, (Map<String, Object>) attrs.root().get()));

    DynamicTasks.queue(
        ChefSoloTasks.runChef(runDir, "launch", entity().getConfig(CHEF_RUN_CONVERGE_TWICE)));
  }
  protected void doStop(ConfigBag parameters, Callable<StopMachineDetails<Integer>> stopTask) {
    preStopConfirmCustom();

    log.info("Stopping {} in {}", entity(), entity().getLocations());

    StopMode stopMachineMode = getStopMachineMode(parameters);
    StopMode stopProcessMode = parameters.get(StopSoftwareParameters.STOP_PROCESS_MODE);

    DynamicTasks.queue("pre-stop", new PreStopCustomTask());

    // BROOKLYN-263:
    // With this change the stop effector will wait for Location to provision so it can terminate
    // the machine, if a provisioning request is in-progress.
    //
    // The ProvisionMachineTask stores transient internal state in PROVISIONING_TASK_STATE and
    // PROVISIONED_MACHINE: it records when the provisioning is running and when done; and it
    // records the final machine. We record the machine in the internal sensor (rather than
    // just relying on getLocations) because the latter is set much later in the start()
    // process.
    //
    // This code is a big improvement (previously there was a several-minute window in some
    // clouds where a call to stop() would leave the machine running).
    //
    // However, there are still races. If the start() code has not yet reached the call to
    // location.obtain() then we won't wait, and the start() call won't know to abort. It's
    // fiddly to get that right, because we need to cope with restart() - so we mustn't leave
    // any state behind that will interfere with subsequent sequential calls to start().
    // There is some attempt to handle it by ProvisionMachineTask checking if the expectedState
    // is stopping/stopped.
    Maybe<MachineLocation> machine = Machines.findUniqueMachineLocation(entity().getLocations());
    ProvisioningTaskState provisioningState =
        entity().sensors().get(AttributesInternal.INTERNAL_PROVISIONING_TASK_STATE);

    if (machine.isAbsent() && provisioningState == ProvisioningTaskState.RUNNING) {
      Duration maxWait = entity().config().get(STOP_WAIT_PROVISIONING_TIMEOUT);
      log.info(
          "When stopping {}, waiting for up to {} for the machine to finish provisioning, before terminating it",
          entity(),
          maxWait);
      boolean success =
          Repeater.create("Wait for a machine to appear")
              .until(
                  new Callable<Boolean>() {
                    @Override
                    public Boolean call() throws Exception {
                      ProvisioningTaskState state =
                          entity()
                              .sensors()
                              .get(AttributesInternal.INTERNAL_PROVISIONING_TASK_STATE);
                      return (state != ProvisioningTaskState.RUNNING);
                    }
                  })
              .backoffTo(Duration.FIVE_SECONDS)
              .limitTimeTo(maxWait)
              .run();
      if (!success) {
        log.warn(
            "When stopping {}, timed out after {} waiting for the machine to finish provisioning - machine may we left running",
            entity(),
            maxWait);
      }
      machine = Maybe.ofDisallowingNull(entity().sensors().get(INTERNAL_PROVISIONED_MACHINE));
    }
    entity().sensors().remove(AttributesInternal.INTERNAL_PROVISIONING_TASK_STATE);
    entity().sensors().remove(INTERNAL_PROVISIONED_MACHINE);

    Task<List<?>> stoppingProcess = null;
    if (canStop(stopProcessMode, entity())) {
      stoppingProcess =
          Tasks.parallel(
              "stopping",
              Tasks.create("stopping (process)", new StopProcessesAtMachineTask()),
              Tasks.create("stopping (feeds)", new StopFeedsAtMachineTask()));
      DynamicTasks.queue(stoppingProcess);
    }

    Task<StopMachineDetails<Integer>> stoppingMachine = null;
    if (canStop(stopMachineMode, machine.isAbsent())) {
      // Release this machine (even if error trying to stop process - we rethrow that after)
      Map<String, Object> stopMachineFlags = MutableMap.of();
      if (Entitlements.getEntitlementContext() != null) {
        stopMachineFlags.put(
            "tags",
            MutableSet.of(
                BrooklynTaskTags.tagForEntitlement(Entitlements.getEntitlementContext())));
      }
      Task<StopMachineDetails<Integer>> stopMachineTask =
          Tasks.<StopMachineDetails<Integer>>builder()
              .displayName("stopping (machine)")
              .body(stopTask)
              .flags(stopMachineFlags)
              .build();
      stoppingMachine = DynamicTasks.queue(stopMachineTask);

      DynamicTasks.drain(entity().getConfig(STOP_PROCESS_TIMEOUT), false);

      // shutdown the machine if stopping process fails or takes too long
      synchronized (stoppingMachine) {
        // task also used as mutex by DST when it submits it; ensure it only submits once!
        if (!stoppingMachine.isSubmitted()) {
          // force the stoppingMachine task to run by submitting it here
          StringBuilder msg =
              new StringBuilder("Submitting machine stop early in background for ")
                  .append(entity());
          if (stoppingProcess == null) {
            msg.append(". Process stop skipped, pre-stop not finished?");
          } else {
            msg.append(" because process stop has ")
                .append((stoppingProcess.isDone() ? "finished abnormally" : "not finished"));
          }
          log.warn(msg.toString());
          Entities.submit(entity(), stoppingMachine);
        }
      }
    }

    try {
      // This maintains previous behaviour of silently squashing any errors on the stoppingProcess
      // task if the
      // stoppingMachine exits with a nonzero value
      boolean checkStopProcesses =
          (stoppingProcess != null
              && (stoppingMachine == null || stoppingMachine.get().value == 0));

      if (checkStopProcesses) {
        // TODO we should test for destruction above, not merely successful "stop", as things like
        // localhost and ssh won't be destroyed
        DynamicTasks.waitForLast();
        if (machine.isPresent()) {
          // throw early errors *only if* there is a machine and we have not destroyed it
          stoppingProcess.get();
        }
      }
    } catch (Throwable e) {
      ServiceStateLogic.setExpectedState(entity(), Lifecycle.ON_FIRE);
      Exceptions.propagate(e);
    }
    entity().sensors().set(SoftwareProcess.SERVICE_UP, false);
    ServiceStateLogic.setExpectedState(entity(), Lifecycle.STOPPED);

    DynamicTasks.queue("post-stop", new PostStopCustomTask());

    if (log.isDebugEnabled()) log.debug("Stopped software process entity " + entity());
  }