public static String puInstancesToString(Collection<ProcessingUnitInstance> instances) {
   StringBuilder builder = new StringBuilder();
   for (ProcessingUnitInstance instance : instances) {
     builder.append(RebalancingUtils.puInstanceToString(instance));
     builder.append(File.separator);
   }
   return builder.toString();
 }
  static FutureStatefulProcessingUnitInstance relocateProcessingUnitInstanceAsync(
      final GridServiceContainer targetContainer,
      final ProcessingUnitInstance puInstance,
      final Log logger,
      final long duration,
      final TimeUnit timeUnit) {

    final ProcessingUnit pu = puInstance.getProcessingUnit();
    final GridServiceContainer[] replicationSourceContainers =
        getReplicationSourceContainers(puInstance);
    final int instanceId = puInstance.getInstanceId();

    final AtomicReference<Throwable> relocateThrowable = new AtomicReference<Throwable>();

    final Admin admin = puInstance.getAdmin();
    final int runningNumber = puInstance.getClusterInfo().getRunningNumber();
    final String puName = puInstance.getName();

    final GridServiceContainer sourceContainer = puInstance.getGridServiceContainer();
    final Set<ProcessingUnitInstance> puInstancesFromSamePartition =
        getOtherInstancesFromSamePartition(puInstance);
    if (logger.isDebugEnabled()) {
      logger.debug(
          "Found instances from the same partition as "
              + RebalancingUtils.puInstanceToString(puInstance)
              + " : "
              + RebalancingUtils.puInstancesToString(puInstancesFromSamePartition));
    }

    if (puInstancesFromSamePartition.size() != pu.getNumberOfBackups()) {
      // total number of instances per partition = numberOfBackups + 1
      throw new IllegalStateException(
          "puInstancesFromSamePartition has "
              + puInstancesFromSamePartition.size()
              + " instances instead of "
              + pu.getNumberOfBackups());
    }

    final long start = System.currentTimeMillis();
    final long end = start + timeUnit.toMillis(duration);

    ((InternalAdmin) admin)
        .scheduleAdminOperation(
            new Runnable() {
              public void run() {
                try {
                  logger.debug(
                      "Relocation of "
                          + RebalancingUtils.puInstanceToString(puInstance)
                          + " to "
                          + ContainersSlaUtils.gscToString(targetContainer)
                          + " has started.");
                  puInstance.relocate(targetContainer);
                } catch (AdminException e) {
                  logger.error("Admin exception " + e.getMessage(), e);
                  relocateThrowable.set(e);
                } catch (Throwable e) {
                  logger.error("Unexpected exception " + e.getMessage(), e);
                  relocateThrowable.set(e);
                }
              }
            });

    return new FutureStatefulProcessingUnitInstance() {

      Throwable throwable;
      ProcessingUnitInstance newInstance;

      public boolean isTimedOut() {
        return System.currentTimeMillis() > end;
      }

      public boolean isDone() {

        endRelocation();

        return isTimedOut() || throwable != null || newInstance != null;
      }

      public ProcessingUnitInstance get()
          throws ExecutionException, IllegalStateException, TimeoutException {

        endRelocation();

        ExecutionException exception = getException();
        if (exception != null) {
          throw exception;
        }
        if (newInstance == null) {
          if (isTimedOut()) {
            throw new TimeoutException("Relocation timeout");
          }
          throw new IllegalStateException("Async operation is not done yet.");
        }

        return newInstance;
      }

      public Date getTimestamp() {
        return new Date(start);
      }

      public ExecutionException getException() {

        endRelocation();
        if (throwable != null) {
          return new ExecutionException(throwable.getMessage(), throwable);
        }
        return null;
      }

      /** populates this.exception or this.newInstance if relocation is complete */
      private void endRelocation() {
        boolean inProgress = true;

        tryStateChange(); // this makes relocation synchronous
        if (newInstance != null || throwable != null) {
          inProgress = false;
        }

        if (inProgress) {
          if (logger.isDebugEnabled()) {
            logger.debug(
                "Relocation from "
                    + ContainersSlaUtils.gscToString(getSourceContainer())
                    + " to "
                    + ContainersSlaUtils.gscToString(getTargetContainer())
                    + " is in progress.");
          }
          // do nothing. relocate() method running on another thread has not returned yet.
        }
      }

      private void tryStateChange() {
        ProcessingUnitInstance relocatedInstance = getRelocatedProcessingUnitInstance();
        if (relocatedInstance != null) {

          if (relocatedInstance.getGridServiceContainer().equals(targetContainer)) {
            if (relocatedInstance.getSpaceInstance() != null
                && relocatedInstance.getSpaceInstance().getMode() != SpaceMode.NONE) {
              if (logger.isDebugEnabled()) {
                logger.debug(
                    "Relocation from "
                        + ContainersSlaUtils.gscToString(getSourceContainer())
                        + " to "
                        + ContainersSlaUtils.gscToString(getTargetContainer())
                        + " had ended successfully.");
              }
              newInstance = relocatedInstance;
            }
          } else {
            if (logger.isDebugEnabled()) {
              logger.debug(
                  "Relocation from "
                      + ContainersSlaUtils.gscToString(getSourceContainer())
                      + " to "
                      + ContainersSlaUtils.gscToString(getTargetContainer())
                      + " has ended with an error.");
            }
            throwable =
                new WrongContainerProcessingUnitRelocationException(puInstance, targetContainer);
          }
        }
      }

      private ProcessingUnitInstance getRelocatedProcessingUnitInstance() {
        for (GridServiceContainer container : admin.getGridServiceContainers()) {
          for (ProcessingUnitInstance instance : container.getProcessingUnitInstances(puName)) {
            if (!instance.equals(puInstance)
                && instance.getClusterInfo().getRunningNumber() == runningNumber
                && !puInstancesFromSamePartition.contains(instance)) {
              return instance;
            }
          }
        }
        return null;
      }

      private boolean isAtLeastOneInstanceValid(Set<ProcessingUnitInstance> instances) {
        boolean isValidState = false;
        for (ProcessingUnitInstance instance : instances) {
          if (instance.isDiscovered() && instance.getGridServiceContainer().isDiscovered()) {
            isValidState = true;
            break;
          }
        }
        return isValidState;
      }

      public String getFailureMessage() {
        if (isTimedOut()) {
          return "relocation timeout of processing unit instance "
              + instanceId
              + " from "
              + gscToString(sourceContainer)
              + " to "
              + gscToString(targetContainer);
        }

        if (getException() != null) {
          return getException().getMessage();
        }

        throw new IllegalStateException("Relocation has not encountered any failure.");
      }

      public GridServiceContainer getTargetContainer() {
        return targetContainer;
      }

      public ProcessingUnit getProcessingUnit() {
        return pu;
      }

      public int getInstanceId() {
        return instanceId;
      }

      public GridServiceContainer getSourceContainer() {
        return sourceContainer;
      }

      public GridServiceContainer[] getReplicaitonSourceContainers() {
        return replicationSourceContainers;
      }
    };
  }