/** {@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; } }
/** {@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; } }
/** {@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; } }
/** {@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); } }
/** {@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); } }
/** * 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); } }
/** * 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); }
/** {@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; }
/** * 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(); } }
/** * 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); } }
/** {@inheritDoc} */ @Override public long duration() { long endTime = this.endTime; return endTime == 0 ? System.currentTimeMillis() - startTime : endTime - startTime; }
/** * 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; }
/** * 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); }