예제 #1
0
  /** {@inheritDoc} */
  @Override
  public boolean waitForAttributes(Map<?, ?> attrs, long timeout) throws InterruptedException {
    A.notNull(attrs, "attrs");

    if (attrs.isEmpty()) {
      return true;
    }

    if (timeout == 0) timeout = Long.MAX_VALUE;

    long now = System.currentTimeMillis();

    // Prevent overflow.
    long end = now + timeout < 0 ? Long.MAX_VALUE : now + timeout;

    // Don't wait longer than session timeout.
    if (end > endTime) end = endTime;

    synchronized (mux) {
      boolean isFound = false;

      while (!closed
          && !(isFound = this.attrs.entrySet().containsAll(attrs.entrySet()))
          && now < end) {
        mux.wait(end - now);

        now = System.currentTimeMillis();
      }

      if (closed) throw new InterruptedException("Session was closed: " + this);

      return isFound;
    }
  }
예제 #2
0
  /** {@inheritDoc} */
  @Override
  public Map<?, ?> waitForAttributes(Collection<?> keys, long timeout) throws InterruptedException {
    A.notNull(keys, "keys");

    if (keys.isEmpty()) return Collections.emptyMap();

    if (timeout == 0) timeout = Long.MAX_VALUE;

    long now = System.currentTimeMillis();

    // Prevent overflow.
    long end = now + timeout < 0 ? Long.MAX_VALUE : now + timeout;

    // Don't wait longer than session timeout.
    if (end > endTime) end = endTime;

    synchronized (mux) {
      while (!closed && !attrs.keySet().containsAll(keys) && now < end) {
        mux.wait(end - now);

        now = System.currentTimeMillis();
      }

      if (closed) throw new InterruptedException("Session was closed: " + this);

      Map<Object, Object> retVal = new HashMap<Object, Object>(keys.size());

      for (Object key : keys) retVal.put(key, attrs.get(key));

      return retVal;
    }
  }
예제 #3
0
  /** {@inheritDoc} */
  @Override
  public boolean waitForAttribute(Object key, Object val, long timeout)
      throws InterruptedException {
    A.notNull(key, "key");

    if (timeout == 0) timeout = Long.MAX_VALUE;

    long now = System.currentTimeMillis();

    // Prevent overflow.
    long end = now + timeout < 0 ? Long.MAX_VALUE : now + timeout;

    // Don't wait longer than session timeout.
    if (end > endTime) end = endTime;

    synchronized (mux) {
      boolean isFound = false;

      while (!closed && !(isFound = isAttributeSet(key, val)) && now < end) {
        mux.wait(end - now);

        now = System.currentTimeMillis();
      }

      if (closed) throw new InterruptedException("Session was closed: " + this);

      return isFound;
    }
  }
예제 #4
0
  /** {@inheritDoc} */
  @SuppressWarnings("unchecked")
  @Override
  public <K, V> V waitForAttribute(K key, long timeout) throws InterruptedException {
    A.notNull(key, "key");

    if (timeout == 0) timeout = Long.MAX_VALUE;

    long now = System.currentTimeMillis();

    // Prevent overflow.
    long end = now + timeout < 0 ? Long.MAX_VALUE : now + timeout;

    // Don't wait longer than session timeout.
    if (end > endTime) end = endTime;

    synchronized (mux) {
      while (!closed && !attrs.containsKey(key) && now < end) {
        mux.wait(end - now);

        now = System.currentTimeMillis();
      }

      if (closed) throw new InterruptedException("Session was closed: " + this);

      return (V) attrs.get(key);
    }
  }
예제 #5
0
  /** {@inheritDoc} */
  @Override
  public R get(long timeout, TimeUnit unit) throws GridException {
    A.ensure(timeout >= 0, "timeout cannot be negative: " + timeout);
    A.notNull(unit, "unit");

    checkValid();

    try {
      long now = System.currentTimeMillis();

      long end = timeout == 0 ? Long.MAX_VALUE : now + MILLISECONDS.convert(timeout, unit);

      // Account for overflow.
      if (end < 0) end = Long.MAX_VALUE;

      synchronized (mux) {
        while (!done && !cancelled && now < end) {
          mux.wait(end - now);

          if (!done) now = System.currentTimeMillis();
        }

        if (done) {
          if (err != null) throw U.cast(err);

          return res;
        }

        if (cancelled) throw new GridFutureCancelledException("Future was cancelled: " + this);

        throw new GridFutureTimeoutException(
            "Timeout was reached before computation completed [duration="
                + duration()
                + "ms, timeout="
                + unit.toMillis(timeout)
                + "ms]");
      }
    } catch (InterruptedException e) {
      throw new GridInterruptedException(
          "Got interrupted while waiting for future to complete [duration="
              + duration()
              + "ms, timeout="
              + unit.toMillis(timeout)
              + "ms]",
          e);
    }
  }
예제 #6
0
  /**
   * Copy constructor.
   *
   * @param orig Copy to create this instance from.
   * @param newParams Optional array of new parameters to override the ondes from {@code orig}.
   */
  public GridifyArgumentAdapter(GridifyArgument orig, Object... newParams) {
    A.notNull(orig, "orig");

    cls = orig.getMethodClass();
    mtdName = orig.getMethodName();
    target = orig.getTarget();

    types = new Class[orig.getMethodParameterTypes().length];
    params = new Object[orig.getMethodParameters().length];

    System.arraycopy(orig.getMethodParameters(), 0, params, 0, params.length);
    System.arraycopy(orig.getMethodParameterTypes(), 0, types, 0, types.length);

    // Override parameters, if any.
    if (newParams.length > 0) {
      setMethodParameters(newParams);
    }
  }
예제 #7
0
  /**
   * Starts Grid instance. Note that if grid is already started, then it will be looked up and
   * returned from this method.
   *
   * @return Started grid.
   */
  private Grid startGrid() {
    Properties props = System.getProperties();

    gridName = props.getProperty(GRIDGAIN_NAME.name());

    if (!props.containsKey(GRIDGAIN_NAME.name()) || G.state(gridName) != GridFactoryState.STARTED) {
      selfStarted = true;

      // Set class loader for the spring.
      ClassLoader curCl = Thread.currentThread().getContextClassLoader();

      // Add no-op logger to remove no-appender warning.
      Appender app = new NullAppender();

      Logger.getRootLogger().addAppender(app);

      try {
        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());

        Grid grid = G.start(cfgPath);

        gridName = grid.name();

        System.setProperty(GRIDGAIN_NAME.name(), grid.name());

        return grid;
      } catch (GridException e) {
        throw new GridRuntimeException("Failed to start grid: " + cfgPath, e);
      } finally {
        Logger.getRootLogger().removeAppender(app);

        Thread.currentThread().setContextClassLoader(curCl);
      }
    }

    return G.grid(gridName);
  }
예제 #8
0
  /** {@inheritDoc} */
  @Override
  public <T> T holdcc(long timeout) {
    if (ctx != null) {
      if (job == null) job = ctx.job().activeJob(jobId);

      // Completed?
      if (job != null) {
        if (timeout > 0 && !job.isDone()) {
          final long endTime = System.currentTimeMillis() + timeout;

          // Overflow.
          if (endTime > 0) {
            ctx.timeout()
                .addTimeoutObject(
                    new GridTimeoutObject() {
                      private final GridUuid id = GridUuid.randomUuid();

                      @Override
                      public GridUuid timeoutId() {
                        return id;
                      }

                      @Override
                      public long endTime() {
                        return endTime;
                      }

                      @Override
                      public void onTimeout() {
                        callcc();
                      }
                    });
          }
        }

        job.hold();
      }
    }

    return null;
  }
예제 #9
0
  /**
   * Callback to notify that future is finished. Note that if non-{@code null} exception is passed
   * in the result value will be ignored.
   *
   * @param res Optional result.
   * @param err Optional error.
   * @return {@code True} if result was set by this call.
   */
  public boolean onDone(@Nullable R res, @Nullable Throwable err) {
    checkValid();

    boolean notify = false;

    boolean gotDone = false;

    try {
      synchronized (mux) {
        if (!done) {
          gotDone = true;

          endTime = System.currentTimeMillis();

          this.res = res;
          this.err = err;

          done = true;

          notify = true;

          mux.notifyAll(); // Notify possibly waiting child classes.

          return true;
        }

        return false;
      }
    } finally {
      if (gotDone) {
        GridStopwatch w = watch;

        if (w != null) w.stop();
      }

      if (notify) notifyListeners();
    }
  }
예제 #10
0
/**
 * Future adapter.
 *
 * @author 2005-2011 Copyright (C) GridGain Systems, Inc.
 * @version 3.1.1c.19062011
 */
public class GridFutureAdapter<R> extends GridMetadataAwareAdapter
    implements GridFuture<R>, Externalizable {
  /** Synchronous notification flag. */
  private static final boolean SYNC_NOTIFY = U.isFutureNotificationSynchronous();

  /** Concurrent notification flag. */
  private static final boolean CONCUR_NOTIFY = U.isFutureNotificationConcurrent();

  /** Done flag. */
  private boolean done;

  /** Cancelled flag. */
  private boolean cancelled;

  /** Result. */
  @GridToStringInclude private R res;

  /** Error. */
  private Throwable err;

  /** Set to {@code false} on deserialization whenever incomplete future is serialized. */
  private boolean valid = true;

  /** Asynchronous listener. */
  private final Set<GridInClosure<? super GridFuture<R>>> lsnrs =
      new GridLeanSet<GridInClosure<? super GridFuture<R>>>();

  /** Creator thread. */
  private Thread thread = Thread.currentThread();

  /** Mutex. */
  private final Object mux = new Object();

  /** Context. */
  protected GridKernalContext ctx;

  /** Logger. */
  protected GridLogger log;

  /** Future start time. */
  protected final long startTime = System.currentTimeMillis();

  /** Synchronous notification flag. */
  private volatile boolean syncNotify = SYNC_NOTIFY;

  /** Concurrent notification flag. */
  private volatile boolean concurNotify = CONCUR_NOTIFY;

  /** Future end time. */
  private volatile long endTime;

  /** Watch. */
  protected GridStopwatch watch;

  /** Empty constructor required for {@link Externalizable}. */
  public GridFutureAdapter() {
    // No-op.
  }

  /** @param ctx Kernal context. */
  public GridFutureAdapter(GridKernalContext ctx) {
    assert ctx != null;

    this.ctx = ctx;

    log = ctx.log(getClass());
  }

  /** {@inheritDoc} */
  @Override
  public long startTime() {
    return startTime;
  }

  /** {@inheritDoc} */
  @Override
  public long duration() {
    long endTime = this.endTime;

    return endTime == 0 ? System.currentTimeMillis() - startTime : endTime - startTime;
  }

  /** {@inheritDoc} */
  @Override
  public boolean concurrentNotify() {
    return concurNotify;
  }

  /** {@inheritDoc} */
  @Override
  public void concurrentNotify(boolean concurNotify) {
    this.concurNotify = concurNotify;
  }

  /** {@inheritDoc} */
  @Override
  public boolean syncNotify() {
    return syncNotify;
  }

  /** {@inheritDoc} */
  @Override
  public void syncNotify(boolean syncNotify) {
    this.syncNotify = syncNotify;
  }

  /**
   * Adds a watch to this future.
   *
   * @param name Name of the watch.
   */
  public void addWatch(String name) {
    assert name != null;

    watch = W.stopwatch(name);
  }

  /**
   * Adds a watch to this future.
   *
   * @param watch Watch to add.
   */
  public void addWatch(GridStopwatch watch) {
    assert watch != null;

    this.watch = watch;
  }

  /** Checks that future is in usable state. */
  protected void checkValid() {
    if (!valid)
      throw new IllegalStateException(
          "Incomplete future was serialized and cannot " + "be used after deserialization.");
  }

  /** @return Valid flag. */
  protected boolean isValid() {
    return valid;
  }

  /**
   * Gets internal mutex.
   *
   * @return Internal mutex.
   */
  protected Object mutex() {
    checkValid();

    return mux;
  }

  /** @return Value of error. */
  protected Throwable error() {
    checkValid();

    synchronized (mux) {
      return err;
    }
  }

  /** @return Value of result. */
  protected R result() {
    checkValid();

    synchronized (mux) {
      return res;
    }
  }

  /** {@inheritDoc} */
  @Override
  public R call() throws Exception {
    return get();
  }

  /** {@inheritDoc} */
  @Override
  public R get(long timeout) throws GridException {
    return get(timeout, MILLISECONDS);
  }

  /** {@inheritDoc} */
  @Override
  public R get() throws GridException {
    checkValid();

    try {
      synchronized (mux) {
        while (!done && !cancelled) mux.wait();

        if (done) {
          if (err != null) throw U.cast(err);

          return res;
        }

        throw new GridFutureCancelledException("Future was cancelled: " + this);
      }
    } catch (InterruptedException e) {
      throw new GridInterruptedException(e);
    }
  }

  /** {@inheritDoc} */
  @Override
  public R get(long timeout, TimeUnit unit) throws GridException {
    A.ensure(timeout >= 0, "timeout cannot be negative: " + timeout);
    A.notNull(unit, "unit");

    checkValid();

    try {
      long now = System.currentTimeMillis();

      long end = timeout == 0 ? Long.MAX_VALUE : now + MILLISECONDS.convert(timeout, unit);

      // Account for overflow.
      if (end < 0) end = Long.MAX_VALUE;

      synchronized (mux) {
        while (!done && !cancelled && now < end) {
          mux.wait(end - now);

          if (!done) now = System.currentTimeMillis();
        }

        if (done) {
          if (err != null) throw U.cast(err);

          return res;
        }

        if (cancelled) throw new GridFutureCancelledException("Future was cancelled: " + this);

        throw new GridFutureTimeoutException(
            "Timeout was reached before computation completed [duration="
                + duration()
                + "ms, timeout="
                + unit.toMillis(timeout)
                + "ms]");
      }
    } catch (InterruptedException e) {
      throw new GridInterruptedException(
          "Got interrupted while waiting for future to complete [duration="
              + duration()
              + "ms, timeout="
              + unit.toMillis(timeout)
              + "ms]",
          e);
    }
  }

  /** {@inheritDoc} */
  @SuppressWarnings({"unchecked"})
  @Override
  public void listenAsync(@Nullable final GridInClosure<? super GridFuture<R>> lsnr) {
    if (lsnr != null) {
      checkValid();

      boolean done;

      synchronized (mux) {
        done = this.done;

        if (!done) lsnrs.add(lsnr);
      }

      if (done) {
        try {
          if (syncNotify) notifyListener(lsnr);
          else
            ctx.closure()
                .runLocalSafe(
                    new GPR() {
                      @Override
                      public void run() {
                        notifyListener(lsnr);
                      }
                    },
                    true);
        } catch (IllegalStateException ignore) {
          U.warn(
              null,
              "Future notification will not proceed because grid is stopped: " + ctx.gridName());
        }
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  public void stopListenAsync(@Nullable GridInClosure<? super GridFuture<R>>... lsnr) {
    if (F.isEmpty(lsnr))
      synchronized (mux) {
        lsnrs.clear();
      }
    else
      synchronized (mux) {
        lsnrs.removeAll(F.asList(lsnr));
      }
  }

  /** Notifies all registered listeners. */
  private void notifyListeners() {
    final Collection<GridInClosure<? super GridFuture<R>>> tmp;

    synchronized (mux) {
      tmp = new ArrayList<GridInClosure<? super GridFuture<R>>>(lsnrs);
    }

    boolean concurNotify = this.concurNotify;
    boolean syncNotify = this.syncNotify;

    if (concurNotify) {
      for (final GridInClosure<? super GridFuture<R>> lsnr : tmp)
        ctx.closure()
            .runLocalSafe(
                new GPR() {
                  @Override
                  public void run() {
                    notifyListener(lsnr);
                  }
                },
                true);
    } else {
      // Always notify in the thread different from start thread.
      if (Thread.currentThread() == thread && !syncNotify) {
        ctx.closure()
            .runLocalSafe(
                new GPR() {
                  @Override
                  public void run() {
                    // Since concurrent notifications are off, we notify
                    // all listeners in one thread.
                    for (GridInClosure<? super GridFuture<R>> lsnr : tmp) notifyListener(lsnr);
                  }
                },
                true);
      } else {
        for (GridInClosure<? super GridFuture<R>> lsnr : tmp) notifyListener(lsnr);
      }
    }
  }

  /**
   * Notifies single listener.
   *
   * @param lsnr Listener.
   */
  private void notifyListener(GridInClosure<? super GridFuture<R>> lsnr) {
    assert lsnr != null;

    try {
      lsnr.apply(this);
    } catch (IllegalStateException ignore) {
      U.warn(
          null,
          "Failed to notify listener (grid is stopped) [grid="
              + ctx.gridName()
              + ", lsnr="
              + lsnr
              + ']');
    } catch (RuntimeException e) {
      U.error(log, "Failed to notify listener: " + lsnr, e);

      throw e;
    } catch (Error e) {
      U.error(log, "Failed to notify listener: " + lsnr, e);

      throw e;
    }
  }

  /**
   * Default no-op implementation that always returns {@code false}. Futures that do support
   * cancellation should override this method and call {@link #onCancelled()} callback explicitly if
   * cancellation indeed did happen.
   */
  @Override
  public boolean cancel() throws GridException {
    checkValid();

    return false;
  }

  /** {@inheritDoc} */
  @Override
  public boolean isDone() {
    // Don't check for "valid" here, as "done" flag can be read
    // even in invalid state.
    synchronized (mux) {
      return done || cancelled;
    }
  }

  /** {@inheritDoc} */
  @Override
  public GridAbsPredicate predicate() {
    return new PA() {
      @Override
      public boolean apply() {
        return isDone();
      }
    };
  }

  /** {@inheritDoc} */
  @Override
  public boolean isCancelled() {
    checkValid();

    synchronized (mux) {
      return cancelled;
    }
  }

  /**
   * Callback to notify that future is finished with {@code null} result. This method must delegate
   * to {@link #onDone(Object, Throwable)} method.
   *
   * @return {@code True} if result was set by this call.
   */
  public final boolean onDone() {
    return onDone(null, null);
  }

  /**
   * Callback to notify that future is finished. This method must delegate to {@link #onDone(Object,
   * Throwable)} method.
   *
   * @param res Result.
   * @return {@code True} if result was set by this call.
   */
  public final boolean onDone(@Nullable R res) {
    return onDone(res, null);
  }

  /**
   * Callback to notify that future is finished. This method must delegate to {@link #onDone(Object,
   * Throwable)} method.
   *
   * @param err Error.
   * @return {@code True} if result was set by this call.
   */
  public final boolean onDone(@Nullable Throwable err) {
    return onDone(null, err);
  }

  /**
   * Callback to notify that future is finished. Note that if non-{@code null} exception is passed
   * in the result value will be ignored.
   *
   * @param res Optional result.
   * @param err Optional error.
   * @return {@code True} if result was set by this call.
   */
  public boolean onDone(@Nullable R res, @Nullable Throwable err) {
    checkValid();

    boolean notify = false;

    boolean gotDone = false;

    try {
      synchronized (mux) {
        if (!done) {
          gotDone = true;

          endTime = System.currentTimeMillis();

          this.res = res;
          this.err = err;

          done = true;

          notify = true;

          mux.notifyAll(); // Notify possibly waiting child classes.

          return true;
        }

        return false;
      }
    } finally {
      if (gotDone) {
        GridStopwatch w = watch;

        if (w != null) w.stop();
      }

      if (notify) notifyListeners();
    }
  }

  /**
   * Callback to notify that future is cancelled.
   *
   * @return {@code True} if cancel flag was set by this call.
   */
  public boolean onCancelled() {
    checkValid();

    synchronized (mux) {
      if (cancelled || done) return false;

      cancelled = true;

      mux.notifyAll(); // Notify possibly waiting child classes.
    }

    return true;
  }

  /** {@inheritDoc} */
  @Override
  public void writeExternal(ObjectOutput out) throws IOException {
    boolean done;
    boolean cancelled;
    Object res;
    Throwable err;
    boolean syncNotify;
    boolean concurNotify;

    synchronized (mux) {
      done = this.done;
      cancelled = this.cancelled;
      res = this.res;
      err = this.err;
      syncNotify = this.syncNotify;
      concurNotify = this.concurNotify;
    }

    out.writeBoolean(done);
    out.writeBoolean(syncNotify);
    out.writeBoolean(concurNotify);

    // Don't write any further if not done, as deserialized future
    // will be invalid anyways.
    if (done) {
      out.writeBoolean(cancelled);
      out.writeObject(res);
      out.writeObject(err);
    }
  }

  /** {@inheritDoc} */
  @SuppressWarnings({"unchecked"})
  @Override
  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    boolean done = in.readBoolean();

    syncNotify = in.readBoolean();
    concurNotify = in.readBoolean();

    if (!done) valid = false;
    else {
      boolean cancelled = in.readBoolean();

      R res = (R) in.readObject();

      Throwable err = (Throwable) in.readObject();

      synchronized (mux) {
        this.done = done;
        this.cancelled = cancelled;
        this.res = res;
        this.err = err;
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  public String toString() {
    return S.toString(GridFutureAdapter.class, this);
  }
}
예제 #11
0
  /** {@inheritDoc} */
  @Override
  public long duration() {
    long endTime = this.endTime;

    return endTime == 0 ? System.currentTimeMillis() - startTime : endTime - startTime;
  }
예제 #12
0
  /**
   * Validates incoming packet and deserializes all fields that need to be deserialized.
   *
   * @param ses Session on which packet is being parsed.
   * @param req Raw packet.
   * @return Same packet with fields deserialized.
   * @throws IOException If parsing failed.
   * @throws GridException If deserialization failed.
   */
  private GridClientMessage assemble(GridNioSession ses, GridTcpRestPacket req)
      throws IOException, GridException {
    byte[] extras = req.extras();

    // First, decode key and value, if any
    if (req.key() != null || req.value() != null) {
      short keyFlags = 0;
      short valFlags = 0;

      if (req.hasFlags()) {
        if (extras == null || extras.length < FLAGS_LENGTH)
          throw new IOException(
              "Failed to parse incoming packet (flags required for command) [ses="
                  + ses
                  + ", opCode="
                  + Integer.toHexString(req.operationCode() & 0xFF)
                  + ']');

        keyFlags = U.bytesToShort(extras, 0);
        valFlags = U.bytesToShort(extras, 2);
      }

      if (req.key() != null) {
        assert req.key() instanceof byte[];

        byte[] rawKey = (byte[]) req.key();

        // Only values can be hessian-encoded.
        req.key(decodeObj(keyFlags, rawKey));
      }

      if (req.value() != null) {
        assert req.value() instanceof byte[];

        byte[] rawVal = (byte[]) req.value();

        req.value(decodeObj(valFlags, rawVal));
      }
    }

    if (req.hasExpiration()) {
      if (extras == null || extras.length < 8)
        throw new IOException(
            "Failed to parse incoming packet (expiration value required for command) [ses="
                + ses
                + ", opCode="
                + Integer.toHexString(req.operationCode() & 0xFF)
                + ']');

      req.expiration(U.bytesToInt(extras, 4) & 0xFFFFFFFFL);
    }

    if (req.hasInitial()) {
      if (extras == null || extras.length < 16)
        throw new IOException(
            "Failed to parse incoming packet (initial value required for command) [ses="
                + ses
                + ", opCode="
                + Integer.toHexString(req.operationCode() & 0xFF)
                + ']');

      req.initial(U.bytesToLong(extras, 8));
    }

    if (req.hasDelta()) {
      if (extras == null || extras.length < 8)
        throw new IOException(
            "Failed to parse incoming packet (delta value required for command) [ses="
                + ses
                + ", opCode="
                + Integer.toHexString(req.operationCode() & 0xFF)
                + ']');

      req.delta(U.bytesToLong(extras, 0));
    }

    if (extras != null) {
      // Clients that include cache name must always include flags.
      int length = 4;

      if (req.hasExpiration()) length += 4;

      if (req.hasDelta()) length += 8;

      if (req.hasInitial()) length += 8;

      if (extras.length - length > 0) {
        byte[] cacheName = new byte[extras.length - length];

        System.arraycopy(extras, length, cacheName, 0, extras.length - length);

        req.cacheName(new String(cacheName));
      }
    }

    return req;
  }
예제 #13
0
/**
 * Test suite for distributing JUnit3 tests. Simply add tests to this suite just like you would for
 * regular JUnit3 suites, and these tests will be executed in parallel on the grid. Note that if
 * there are no other grid nodes, this suite will still ensure parallel test execution within single
 * JVM.
 *
 * <p>Below is an example of distributed JUnit3 test suite:
 *
 * <pre name="code" class="java">
 * public class GridJunit3ExampleTestSuite {
 *     // Standard JUnit3 static suite method.
 *     public static TestSuite suite() {
 *         TestSuite suite = new GridJunit3TestSuite("Example Grid Test Suite");
 *
 *         // Add tests.
 *         suite.addTestSuite(TestA.class);
 *         suite.addTestSuite(TestB.class);
 *         suite.addTestSuite(TestC.class);
 *
 *         return suite;
 *     }
 * }
 * </pre>
 *
 * If you have four tests A, B, C, and D, and if you need to run A and B sequentially, then you
 * should create a nested test suite with test A and B as follows:
 *
 * <pre name="code" class="java">
 * public class GridJunit3ExampleTestSuite {
 *     // Standard JUnit3 static suite method.
 *     public static TestSuite suite() {
 *         TestSuite suite = new GridJunit3TestSuite("Example Grid Test Suite");
 *
 *         // Nested test suite to run tests A and B sequentially.
 *         TestSuite nested = new TestSuite("Example Nested Sequential Suite");
 *
 *         nested.addTestSuite(TestA.class);
 *         nested.addTestSuite(TestB.class);
 *
 *         // Add tests A and B.
 *         suite.addTest(nested);
 *
 *         // Add other tests.
 *         suite.addTestSuite(TestC.class);
 *
 *         return suite;
 *     }
 * }
 * </pre>
 *
 * <p>Note that you can also grid-enable existing JUnit3 tests using {@link
 * GridifyTest @GridifyTest} annotation which you can attach to your {@code suite()} methods of
 * existing test suite. Refer to {@link GridifyTest @GridifyTest} documentation for more
 * information.
 *
 * <p>Also note that some tests can only be executed locally mostly due to some environment issues.
 * However they still can benefit from parallel execution with other tests. GridGain supports it via
 * {@link GridJunit3LocalTestSuite} suites that can be added to {@code GridJunit3TestSuite}. Refer
 * to {@link GridJunit3LocalTestSuite} documentation for more information.
 *
 * <h1 class="header">Logging</h1>
 *
 * When running distributed JUnit, all the logging that is done to {@link System#out} or {@link
 * System#err} is preserved. GridGain will accumulate all logging that is done on remote nodes, send
 * them back to originating node and associate all log statements with their corresponding tests.
 * This way, for example, if you are running tests from and IDEA or Eclipse (or any other IDE) you
 * would still see the logs as if it was a local run. However, since remote nodes keep all log
 * statements done within a single individual test case in memory, you must make sure that enough
 * memory is allocated on every node and that individual test cases do not spit out gigabytes of log
 * statements. Also note, that logs will be sent back to originating node upon completion of every
 * test, so don't be alarmed if you don't see any log statements for a while and then all of them
 * appear at once.
 *
 * <p>GridGain achieves such log transparency via reassigning {@link System#out} or {@link
 * System#err} to internal {@link PrintStream} implementation. However, when using {@code Log4J} (or
 * any other logging framework) within your tests you must make sure that it is configured with
 * {@link ConsoleAppender} and that {@link ConsoleAppender#setFollow(boolean)} attribute is set to
 * {@code true}. Logging to files is not supported yet and is planned for next point release.
 *
 * <p>
 *
 * <h1 class="header">Test Suite Nesting</h1>
 *
 * {@code GridJunit3TestSuite} instances can be nested within each other as deep as needed. However
 * all nested distributed test suites will be treated just like regular JUnit test suites, and not
 * as distributed test suites. This approach becomes convenient when you have several distributed
 * test suites that you would like to be able to execute separately in distributed fashion, but at
 * the same time you would like to be able to execute them as a part of larger distributed suites.
 *
 * <p>
 *
 * <h1 class="header">Configuration</h1>
 *
 * To run distributed JUnit tests you need to start other instances of GridGain. You can do so by
 * running {@code GRIDGAIN_HOME/bin/ggjunit.{sh|bat}} script, which will start default
 * configuration. If configuration other than default is required, then use regular {@code
 * GRIDGAIN_HOME/bin/ggstart.{sh|bat}} script and pass your own Spring XML configuration file as a
 * parameter to the script.
 *
 * <p>You can use the following configuration parameters to configure distributed test suite
 * locally. Note that many parameters can be overridden by setting corresponding VM parameters
 * defined in {@link GridTestVmParameters} at VM startup.
 *
 * <table class="doctable">
 *   <tr>
 *     <th>GridConfiguration Method</th>
 *     <th>Default Value</th>
 *     <th>Description</th>
 *   </tr>
 *   <tr>
 *     <td>{@link #setDisabled(boolean) setDisabled(boolean)}</td>
 *     <td>{@code false}</td>
 *     <td>
 *       If {@code true} then GridGain will be turned off and suite will run locally.
 *       This value can be overridden by setting {@link GridTestVmParameters#GRIDGAIN_DISABLED} VM
 *       parameter to {@code true}. This parameter comes handy when you would like to
 *       turn off GridGain without changing the actual code.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td>{@link #setConfigurationPath(String) setConfigurationPath(String)}</td>
 *     <td>{@link #DFLT_JUNIT_CONFIG DFLT_JUNIT_CONFIG}</td>
 *     <td>
 *       Optional path to GridGain Spring XML configuration file for running JUnit tests. This
 *       property can be overridden by setting {@link GridTestVmParameters#GRIDGAIN_CONFIG} VM
 *       parameter. Note that the value can be either absolute value or relative to
 *       ${GRIDGAIN_HOME} installation folder.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td>{@link #setRouterClassName(String) setRouterClassName(String)}</td>
 *     <td>{@link #DFLT_JUNIT_ROUTER DFLT_JUNIT_ROUTER}</td>
 *     <td>
 *       Optional name of test router class that implements {@link GridTestRouter} interface.
 *       If not provided, then tests will be routed in round-robin fashion using default
 *       {@link GridTestRouterAdapter}. The value of this parameter can be overridden by setting
 *       {@link GridTestVmParameters#GRIDGAIN_TEST_ROUTER} VM parameter to the name of your
 *       own customer router class.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td>{@link #setRouterClass(Class) setRouterClass(Class)}</td>
 *     <td>{@code null}</td>
 *     <td>
 *       Same as {@link #setRouterClassName(String) setRouterClassName(String)}, but sets the
 *       actual class instead of the name.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td>{@link #setTimeout(long) setTimeout(long)}</td>
 *     <td>{@code 0} which means that tests will never timeout.</td>
 *     <td>
 *       Maximum timeout value in milliseconds after which test suite will return without
 *       waiting for the remaining tests to complete. This value can be overridden by setting
 *       {@link GridTestVmParameters#GRIDGAIN_TEST_TIMEOUT} VM parameter to the timeout value
 *       for the tests.
 *     </td>
 *   </tr>
 * </table>
 *
 * @author 2005-2011 Copyright (C) GridGain Systems, Inc.
 * @version 3.1.1c.19062011
 */
public class GridJunit3TestSuite extends TestSuite {
  /**
   * Default GridGain configuration file for JUnits (value is {@code
   * config/junit/junit-spring.xml}).
   */
  public static final String DFLT_JUNIT_CONFIG = "config/junit/junit-spring.xml";

  /**
   * Default JUnit test router (value is {@link GridTestRouterAdapter
   * GridTestRouterAdapter.class.getName()}).
   */
  public static final String DFLT_JUNIT_ROUTER = GridTestRouterAdapter.class.getName();

  /** */
  private final Collection<String> locTests = new HashSet<String>();

  /** JUnit3 JavaAssist proxy. */
  private final GridJunit3ProxyFactory factory = new GridJunit3ProxyFactory();

  /** Flag indicating whether grid was started in this suite. */
  private boolean selfStarted;

  /** Junit3 Spring configuration path. */
  private String cfgPath =
      System.getProperty(GRIDGAIN_CONFIG.name()) == null
          ? DFLT_JUNIT_CONFIG
          : System.getProperty(GRIDGAIN_CONFIG.name());

  /**
   * Check if GridGain is disabled by checking {@link GridTestVmParameters#GRIDGAIN_DISABLED} system
   * property.
   */
  private boolean isDisabled = Boolean.getBoolean(GRIDGAIN_DISABLED.name());

  /** JUnit test router class name. */
  private String routerClsName =
      System.getProperty(GRIDGAIN_TEST_ROUTER.name()) == null
          ? DFLT_JUNIT_ROUTER
          : System.getProperty(GRIDGAIN_TEST_ROUTER.name());

  /** JUnit test router class. */
  private Class<? extends GridTestRouter> routerCls;

  /**
   * Local suite in case if grid is disabled or if this is a nested suite within other distributed
   * suite.
   */
  private TestSuite copy;

  /** JUnit grid name. */
  private String gridName;

  /** Test timeout. */
  private long timeout =
      Long.getLong(GRIDGAIN_TEST_TIMEOUT.name()) == null
          ? 0
          : Long.getLong(GRIDGAIN_TEST_TIMEOUT.name());

  /** */
  private ClassLoader clsLdr;

  /** Empty test suite. */
  public GridJunit3TestSuite() {
    if (copy == null) {
      copy = new TestSuite();
    }
  }

  /** @param name Test suite name. */
  public GridJunit3TestSuite(String name) {
    super(name);

    if (copy == null) {
      copy = new TestSuite(name);
    }
  }

  /**
   * Test suite for one class.
   *
   * @param cls Class for test suite.
   */
  public GridJunit3TestSuite(Class<? extends TestCase> cls) {
    super(cls);

    if (copy == null) {
      copy = new TestSuite(cls);
    }
  }

  /**
   * Test suite for a given test class with specified test name.
   *
   * @param cls Test class.
   * @param name Test name.
   */
  public GridJunit3TestSuite(Class<? extends TestCase> cls, String name) {
    super(cls, name);

    if (copy == null) {
      copy = new TestSuite(cls, name);
    }
  }

  /**
   * Copies non-distributed test suite into distributed one.
   *
   * @param suite Test suite to copy.
   */
  public GridJunit3TestSuite(TestSuite suite) {
    super(suite.getName());

    if (copy == null) {
      copy = new TestSuite(suite.getName());
    }

    for (int i = 0; i < suite.testCount(); i++) {
      addTest(suite.testAt(i));
    }
  }

  /**
   * Empty test suite with given class loader.
   *
   * @param clsLdr Tests class loader.
   */
  public GridJunit3TestSuite(ClassLoader clsLdr) {
    this();

    assert clsLdr != null;

    this.clsLdr = clsLdr;
  }

  /**
   * @param name Test suite name.
   * @param clsLdr Tests class loader.
   */
  public GridJunit3TestSuite(String name, ClassLoader clsLdr) {
    this(name);

    assert clsLdr != null;

    this.clsLdr = clsLdr;
  }

  /**
   * Test suite for one class.
   *
   * @param cls Class for test suite.
   * @param clsLdr Tests class loader.
   */
  public GridJunit3TestSuite(Class<? extends TestCase> cls, ClassLoader clsLdr) {
    this(cls);

    assert clsLdr != null;

    this.clsLdr = clsLdr;
  }

  /**
   * Test suite for a given test class with specified test name.
   *
   * @param cls Test class.
   * @param name Test name.
   * @param clsLdr Tests class loader.
   */
  public GridJunit3TestSuite(Class<? extends TestCase> cls, String name, ClassLoader clsLdr) {
    this(cls, name);

    assert clsLdr != null;

    this.clsLdr = clsLdr;
  }

  /**
   * Copies non-distributed test suite into distributed one.
   *
   * @param suite Test suite to copy.
   * @param clsLdr Tests class loader.
   */
  public GridJunit3TestSuite(TestSuite suite, ClassLoader clsLdr) {
    this(suite);

    assert clsLdr != null;

    this.clsLdr = clsLdr;
  }

  /**
   * Sets path to GridGain configuration file. By default {@code
   * {GRIDGAIN_HOME}/config/junit/junit-spring.xml} is used.
   *
   * @param cfgPath Path to GridGain configuration file.
   */
  public void setConfigurationPath(String cfgPath) {
    this.cfgPath = cfgPath;
  }

  /**
   * Gets path to GridGain configuration file. By default {@code
   * {GRIDGAIN_HOME}/config/junit/junit-spring.xml} is used.
   *
   * @return Path to GridGain configuration file.
   */
  public String getConfigurationPath() {
    return cfgPath;
  }

  /**
   * Disables GridGain. If set to {@code true} then this suite will execute locally as if GridGain
   * was not in a picture at all.
   *
   * @param disabled If set to {@code true} then this suite will execute locally as if GridGain was
   *     not in a picture at all.
   */
  public void setDisabled(boolean disabled) {
    isDisabled = disabled;
  }

  /**
   * Gets flag indicating whether GridGain should be enabled or not. If set to {@code true} then
   * this suite will execute locally as if GridGain was not in a picture at all.
   *
   * @return Flag indicating whether GridGain should be enabled or not. If set to {@code true} then
   *     this suite will execute locally as if GridGain was not in a picture at all.
   */
  public boolean isDisabled() {
    return isDisabled;
  }

  /**
   * Sets name of class for routing JUnit tests. By default {@link #DFLT_JUNIT_ROUTER} class name is
   * used.
   *
   * @param routerClsName Junit test router class name.
   */
  public void setRouterClassName(String routerClsName) {
    this.routerClsName = routerClsName;
  }

  /**
   * Gets JUnit test router class name.
   *
   * @return JUnit test router class name.
   */
  public String getRouterClassName() {
    return routerClsName;
  }

  /**
   * Sets router class. By default {@link GridTestRouterAdapter} is used.
   *
   * @param routerCls Router class to use for test routing.
   */
  public void setRouterClass(Class<? extends GridTestRouter> routerCls) {
    this.routerCls = routerCls;
  }

  /**
   * Gets router class used for test routing.
   *
   * @return Router class used for test routing.
   */
  public Class<? extends GridTestRouter> getRouterClass() {
    return routerCls;
  }

  /**
   * Gets identical suite for local (non-distributed) execution.
   *
   * @return Local suite.
   */
  public TestSuite getLocalCopy() {
    return copy;
  }

  /** {@inheritDoc} */
  @Override
  public void setName(String name) {
    if (copy != null) {
      copy.setName(name);
    }

    super.setName(name);
  }

  /**
   * Gets timeout for running distributed test suite.
   *
   * @return Timeout for tests.
   */
  public long getTimeout() {
    return timeout;
  }

  /**
   * Sets timeout for running distributed test suite. By default, test execution does not expire.
   *
   * @param timeout Timeout for tests.
   */
  public void setTimeout(long timeout) {
    this.timeout = timeout;
  }

  /** {@inheritDoc} */
  @Override
  public Test testAt(int index) {
    return isDisabled ? copy.testAt(index) : super.testAt(index);
  }

  /** {@inheritDoc} */
  @Override
  public int testCount() {
    return isDisabled ? copy.testCount() : super.testCount();
  }

  /** {@inheritDoc} */
  @Override
  public Enumeration<Test> tests() {
    return isDisabled ? copy.tests() : super.tests();
  }

  /**
   * The added suite will be always executed locally, but in parallel with other locally or remotely
   * running tests. This comes handy for tests that cannot be distributed for some environmental
   * reasons, but still would benefit from parallel execution.
   *
   * <p>Note, that local suites will be executed on local node even if grid topology only allows
   * remote nodes.
   *
   * @param localSuite Test to execute locally in parallel with other local or distributed tests.
   */
  @SuppressWarnings({"TypeMayBeWeakened"})
  public void addTest(GridJunit3LocalTestSuite localSuite) {
    if (!locTests.contains(localSuite.getName())) {
      locTests.add(localSuite.getName());
    }

    addTest((Test) localSuite);
  }

  /**
   * Adds a test to be executed on the grid. In case of test suite, all tests inside of test suite
   * will be executed sequentially on some remote node.
   *
   * @param test Test to add.
   */
  @Override
  public void addTest(Test test) {
    if (copy == null) {
      copy = new TestSuite(getName());
    }

    // Add test to the list of local ones.
    if (test instanceof GridJunit3LocalTestSuite) {
      String testName = ((TestSuite) test).getName();

      if (!locTests.contains(testName)) {
        locTests.add(testName);
      }
    }

    if (test instanceof GridJunit3TestSuite) {
      copy.addTest(((GridJunit3TestSuite) test).copy);

      super.addTest(new GridJunit3TestSuiteProxy(((GridJunit3TestSuite) test).copy, factory));
    } else if (test instanceof GridJunit3TestSuiteProxy) {
      copy.addTest(((GridJunit3TestSuiteProxy) test).getOriginal());

      super.addTest(test);
    } else if (test instanceof GridJunit3TestCaseProxy) {
      copy.addTest(((GridJunit3TestCaseProxy) test).getGridGainJunit3OriginalTestCase());

      super.addTest(test);
    } else if (test instanceof TestSuite) {
      copy.addTest(test);

      super.addTest(new GridJunit3TestSuiteProxy((TestSuite) test, factory));
    } else {
      assert test instanceof TestCase
          : "Test must be either instance of TestSuite or TestCase: " + test;

      copy.addTest(test);

      super.addTest(factory.createProxy((TestCase) test));
    }
  }

  /**
   * Creates JUnit test router. Note that router must have a no-arg constructor.
   *
   * @return JUnit router instance.
   */
  @SuppressWarnings({"unchecked"})
  private GridTestRouter createRouter() {
    try {
      if (routerCls == null) {
        routerCls = (Class<? extends GridTestRouter>) Class.forName(routerClsName);
      } else {
        routerClsName = routerCls.getName();
      }

      return routerCls.newInstance();
    } catch (ClassNotFoundException e) {
      throw new GridRuntimeException("Failed to initialize JUnit router: " + routerClsName, e);
    } catch (IllegalAccessException e) {
      throw new GridRuntimeException("Failed to initialize JUnit router: " + routerClsName, e);
    } catch (InstantiationException e) {
      throw new GridRuntimeException("Failed to initialize JUnit router: " + routerClsName, e);
    }
  }

  /**
   * Runs all tests belonging to this test suite on the grid.
   *
   * @param result Test result collector.
   */
  @Override
  public void run(TestResult result) {
    if (isDisabled) {
      copy.run(result);
    } else {
      GridTestRouter router = createRouter();

      Grid grid = startGrid();

      try {
        List<GridTaskFuture<?>> futs = new ArrayList<GridTaskFuture<?>>(testCount());

        List<GridJunit3SerializableTest> tests =
            new ArrayList<GridJunit3SerializableTest>(testCount());

        for (int i = 0; i < testCount(); i++) {
          Test junit = testAt(i);

          GridJunit3SerializableTest test;

          if (junit instanceof TestSuite) {
            test = new GridJunit3SerializableTestSuite((TestSuite) junit);
          } else {
            assert junit instanceof TestCase
                : "Test must be either TestSuite or TestCase: " + junit;

            test = new GridJunit3SerializableTestCase((TestCase) junit);
          }

          tests.add(test);

          if (clsLdr == null) {
            clsLdr = U.detectClassLoader(junit.getClass());
          }

          futs.add(
              grid.execute(
                  new GridJunit3Task(junit.getClass(), clsLdr),
                  new GridJunit3Argument(router, test, locTests.contains(test.getName())),
                  timeout));
        }

        for (int i = 0; i < testCount(); i++) {
          GridTaskFuture<?> fut = futs.get(i);

          GridJunit3SerializableTest origTest = tests.get(i);

          try {
            GridJunit3SerializableTest resTest = (GridJunit3SerializableTest) fut.get();

            origTest.setResult(resTest);

            origTest.getTest().run(result);
          } catch (GridException e) {
            handleFail(result, origTest, e);
          }
        }
      } finally {
        stopGrid();
      }
    }
  }

  /**
   * Handles test fail.
   *
   * @param result Test result.
   * @param origTest Original JUnit test.
   * @param e Exception thrown from grid.
   */
  private void handleFail(TestResult result, GridJunit3SerializableTest origTest, Throwable e) {
    // Simulate that all tests were run.
    origTest.getTest().run(result);

    // For the tests suite we assume that all tests failed because
    // entire test suite execution failed and there is no way to get
    // broken tests.
    if (origTest.getTest() instanceof GridJunit3TestSuiteProxy) {
      TestSuite suite = (((TestSuite) origTest.getTest()));

      for (int j = 0; j < suite.testCount(); j++) {
        result.addError(suite.testAt(j), e);
      }
    } else if (origTest.getTest() instanceof GridJunit3TestCaseProxy) {
      result.addError(origTest.getTest(), e);
    }
  }

  /**
   * Starts Grid instance. Note that if grid is already started, then it will be looked up and
   * returned from this method.
   *
   * @return Started grid.
   */
  private Grid startGrid() {
    Properties props = System.getProperties();

    gridName = props.getProperty(GRIDGAIN_NAME.name());

    if (!props.containsKey(GRIDGAIN_NAME.name()) || G.state(gridName) != GridFactoryState.STARTED) {
      selfStarted = true;

      // Set class loader for the spring.
      ClassLoader curCl = Thread.currentThread().getContextClassLoader();

      // Add no-op logger to remove no-appender warning.
      Appender app = new NullAppender();

      Logger.getRootLogger().addAppender(app);

      try {
        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());

        Grid grid = G.start(cfgPath);

        gridName = grid.name();

        System.setProperty(GRIDGAIN_NAME.name(), grid.name());

        return grid;
      } catch (GridException e) {
        throw new GridRuntimeException("Failed to start grid: " + cfgPath, e);
      } finally {
        Logger.getRootLogger().removeAppender(app);

        Thread.currentThread().setContextClassLoader(curCl);
      }
    }

    return G.grid(gridName);
  }

  /** Stops grid only if it was started by this test suite. */
  private void stopGrid() {
    // Only stop grid if it was started here.
    if (selfStarted) {
      G.stop(gridName, true);
    }
  }
}
  /**
   * Removes obsolete deployments in case of redeploy.
   *
   * @param meta Request metadata.
   * @return List of shares deployment.
   */
  private GridTuple2<Boolean, SharedDeployment> checkRedeploy(GridDeploymentMetadata meta) {
    assert Thread.holdsLock(mux);

    SharedDeployment newDep = null;

    for (List<SharedDeployment> deps : cache.values()) {
      for (SharedDeployment dep : deps) {
        if (!dep.isUndeployed() && !dep.isPendingUndeploy()) {
          long undeployTimeout = ctx.config().getNetworkTimeout();

          SharedDeployment doomed = null;

          // Only check deployments with no participants.
          if (!dep.hasParticipants()) {
            // In case of SHARED deployment it is possible to get hear if
            // unmarshalling happens during undeploy. In this case, we
            // simply don't do anything.
            if (dep.deployMode() == CONTINUOUS) {
              if (dep.existingDeployedClass(meta.className()) != null) {
                // Change from shared deploy to shared undeploy or user version change.
                // Simply remove all deployments with no participating nodes.
                if (meta.deploymentMode() == SHARED
                    || !meta.userVersion().equals(dep.userVersion())) doomed = dep;
              }
            }
          }
          // If there are participants, we undeploy if class loader ID on some node changed.
          else if (dep.existingDeployedClass(meta.className()) != null) {
            GridTuple2<GridUuid, Long> ldr = dep.getClassLoaderId(meta.senderNodeId());

            if (ldr != null) {
              if (!ldr.get1().equals(meta.classLoaderId())) {
                // If deployed sequence number is less, then schedule for undeployment.
                if (ldr.get2() < meta.sequenceNumber()) {
                  if (log.isDebugEnabled())
                    log.debug(
                        "Received request for a class with newer sequence number "
                            + "(will schedule current class for undeployment) [newSeq="
                            + meta.sequenceNumber()
                            + ", oldSeq="
                            + ldr.get2()
                            + ", senderNodeId="
                            + meta.senderNodeId()
                            + ", newClsLdrId="
                            + meta.classLoaderId()
                            + ", oldClsLdrId="
                            + ldr.get1()
                            + ']');

                  doomed = dep;
                } else if (ldr.get2() > meta.sequenceNumber()) {
                  long time = System.currentTimeMillis() - dep.timestamp();

                  if (newDep == null && time < ctx.config().getNetworkTimeout()) {
                    // Set undeployTimeout, so the class will be scheduled
                    // for undeployment.
                    undeployTimeout = ctx.config().getNetworkTimeout() - time;

                    if (log.isDebugEnabled())
                      log.debug(
                          "Received execution request for a stale class (will deploy and "
                              + "schedule undeployment in "
                              + undeployTimeout
                              + "ms) "
                              + "[curSeq="
                              + ldr.get2()
                              + ", staleSeq="
                              + meta.sequenceNumber()
                              + ", cls="
                              + meta.className()
                              + ", senderNodeId="
                              + meta.senderNodeId()
                              + ", curLdrId="
                              + ldr.get1()
                              + ", staleLdrId="
                              + meta.classLoaderId()
                              + ']');

                    // We got the redeployed class before the old one.
                    // Simply create a temporary deployment for the sender node,
                    // and schedule undeploy for it.
                    newDep = createNewDeployment(meta, false);

                    doomed = newDep;
                  } else {
                    U.warn(
                        log,
                        "Received execution request for a class that has been redeployed "
                            + "(will ignore): "
                            + meta.alias());

                    if (log.isDebugEnabled())
                      log.debug(
                          "Received execution request for a class that has been redeployed "
                              + "(will ignore) [alias="
                              + meta.alias()
                              + ", dep="
                              + dep
                              + ']');

                    return F.t(false, null);
                  }
                } else {
                  U.error(
                      log,
                      "Sequence number does not correspond to class loader ID [seqNum="
                          + meta.sequenceNumber()
                          + ", dep="
                          + dep
                          + ']');

                  return F.t(false, null);
                }
              }
            }
          }

          if (doomed != null) {
            doomed.onUndeployScheduled();

            if (log.isDebugEnabled()) log.debug("Deployment was scheduled for undeploy: " + doomed);

            // Lifespan time.
            final long endTime = System.currentTimeMillis() + undeployTimeout;

            // Deployment to undeploy.
            final SharedDeployment undep = doomed;

            ctx.timeout()
                .addTimeoutObject(
                    new GridTimeoutObject() {
                      @Override
                      public GridUuid timeoutId() {
                        return undep.classLoaderId();
                      }

                      @Override
                      public long endTime() {
                        return endTime < 0 ? Long.MAX_VALUE : endTime;
                      }

                      @Override
                      public void onTimeout() {
                        boolean removed = false;

                        // Hot redeployment.
                        synchronized (mux) {
                          assert undep.isPendingUndeploy();

                          if (!undep.isUndeployed()) {
                            undep.undeploy();

                            undep.onRemoved();

                            removed = true;

                            Collection<SharedDeployment> deps = cache.get(undep.userVersion());

                            if (deps != null) {
                              for (Iterator<SharedDeployment> i = deps.iterator(); i.hasNext(); )
                                if (i.next() == undep) i.remove();

                              if (deps.isEmpty()) cache.remove(undep.userVersion());
                            }

                            if (log.isInfoEnabled())
                              log.info(
                                  "Undeployed class loader due to deployment mode change, "
                                      + "user version change, or hot redeployment: "
                                      + undep);
                          }
                        }

                        // Outside synchronization.
                        if (removed) undep.recordUndeployed(null);
                      }
                    });
          }
        }
      }
    }

    if (newDep != null) {
      List<SharedDeployment> list =
          F.addIfAbsent(cache, meta.userVersion(), F.<SharedDeployment>newList());

      assert list != null;

      list.add(newDep);
    }

    return F.t(true, newDep);
  }