/** Reports success or failure of step, outside synchronization block to avoid deadlocks. */
 void report() {
   if (error != null) {
     context.onFailure(error);
   } else {
     context.onSuccess(result);
   }
 }
 @Override
 public final boolean start(StepContext context) {
   try {
     FilePath ws = context.get(FilePath.class);
     assert ws != null
         : context.getClass() + " failed to provide a FilePath even though one was requested";
     String remote = ws.getRemote();
     String node = null;
     for (Computer c : Jenkins.getInstance().getComputers()) {
       if (c.getChannel() == ws.getChannel()) {
         node = c.getName();
         break;
       }
     }
     if (node == null) {
       throw new IllegalStateException("no known node for " + ws);
     }
     register(
         context,
         task()
             .launch(
                 context.get(EnvVars.class),
                 ws,
                 context.get(Launcher.class),
                 context.get(TaskListener.class)),
         node,
         remote);
   } catch (Exception x) {
     context.onFailure(x);
   }
   return false;
   // TODO implement stop, however it is design (will need to call Controller.stop)
 }
 /** Checks for progress or completion of the external task. */
 CheckResult check() {
   try {
     Computer c = Jenkins.getInstance().getComputer(node);
     if (c == null) {
       LOGGER.log(Level.FINE, "no such computer {0}", node);
       return CheckResult.NO_CHANGE;
     }
     if (c.isOffline()) {
       LOGGER.log(Level.FINE, "{0} is offline", node);
       return CheckResult.NO_CHANGE;
     }
     FilePath ws = new FilePath(c.getChannel(), remote);
     if (!ws.isDirectory()) {
       error = new AbortException("missing workspace " + remote + " on " + node);
       return CheckResult.DONE;
     }
     TaskListener listener = context.get(TaskListener.class);
     boolean wrote = controller.writeLog(ws, listener.getLogger());
     Integer exitCode = controller.exitStatus(ws);
     if (exitCode == null) {
       LOGGER.log(Level.FINE, "still running in {0} on {1}", new Object[] {remote, node});
       return wrote ? CheckResult.UPDATED : CheckResult.NO_CHANGE;
     } else if (exitCode == 0) {
       result = exitCode; // TODO could add an option to have this be text output from command
     } else {
       error = new AbortException("script returned exit code " + exitCode);
     }
     controller.cleanup(ws);
     return CheckResult.DONE;
   } catch (IOException x) {
     error = x;
   } catch (InterruptedException x) {
     error = x;
   }
   return CheckResult.DONE;
 }