| /*========================================================================= |
| * Copyright (c) 2010-2014 Pivotal Software, Inc. All Rights Reserved. |
| * This product is protected by U.S. and international copyright |
| * and intellectual property laws. Pivotal products are covered by |
| * one or more patents listed at http://www.pivotal.io/patents. |
| *========================================================================= |
| */ |
| package com.gemstone.gemfire.internal.cache; |
| |
| import java.util.concurrent.atomic.AtomicInteger; |
| import com.gemstone.gemfire.CancelException; |
| import com.gemstone.gemfire.LogWriter; |
| import com.gemstone.gemfire.SystemFailure; |
| import com.gemstone.gemfire.cache.*; |
| import com.gemstone.gemfire.cache.util.*; |
| import com.gemstone.gemfire.distributed.internal.DistributionManager; |
| import com.gemstone.gemfire.distributed.internal.InternalDistributedSystem; |
| |
| import dunit.Host; |
| import dunit.RMIException; |
| import dunit.VM; |
| |
| import java.io.Serializable; |
| import java.util.ArrayList; |
| |
| import util.TestException; |
| |
| import junit.framework.Assert; |
| |
| /** |
| * This class tests the response of GemFire to various |
| * occurrences of {@link VirtualMachineError} |
| * |
| * @author jpenney |
| * @since 5.1 |
| */ |
| public class SystemFailureDUnitTest extends DistributedCacheTestCase { |
| |
| static final String REGION_NAME = "SystemFailureDUnitTest"; |
| static final Scope SCOPE = Scope.DISTRIBUTED_ACK; |
| |
| volatile static Object newValue, oldValue; |
| |
| public SystemFailureDUnitTest(String name) { |
| super(name); |
| } |
| |
| ///////// Public test methods |
| |
| public void testNullFailure() { |
| getLogWriter().info("TODO: this test needs to use VM#bounce."); |
| try { |
| SystemFailure.initiateFailure(null); |
| fail("Null failure set allowed"); |
| } |
| catch (IllegalArgumentException e) { |
| // pass |
| } |
| } |
| |
| /** |
| * @see StackOverflowError |
| */ |
| public void _testStackOverflow() |
| throws CacheException, InterruptedException { |
| String exceptions = |
| StackOverflowError.class.getName() |
| + "||" |
| // + OutOfMemoryError.class.getName() |
| // + "||" |
| + AssertionError.class.getName(); |
| |
| try { |
| String name = "testStackOverflow"; |
| |
| doMessage( |
| "<ExpectedException action=add>" |
| + exceptions |
| + "</ExpectedException>"); |
| doCreateEntry(name); |
| doVerifyDisconnected(); |
| } |
| finally { |
| doMessage( |
| "<ExpectedException action=remove>" |
| + exceptions |
| + "</ExpectedException>"); |
| resetVM(); |
| } |
| } |
| |
| /** |
| * @see OutOfMemoryError |
| * @throws CacheException |
| * @throws InterruptedException |
| */ |
| public void _testOutOfMemory() |
| throws CacheException, InterruptedException { |
| |
| String exceptions = |
| OutOfMemoryError.class.getName() |
| + "||" |
| + AssertionError.class.getName(); |
| try { |
| String name = "testOutOfMemory"; |
| doMessage( |
| "<ExpectedException action=add>" |
| + exceptions |
| + "</ExpectedException>"); |
| doCreateEntry(name); |
| doVerifyDisconnected(); |
| } |
| finally { |
| doMessage( |
| "<ExpectedException action=remove>" |
| + exceptions |
| + "</ExpectedException>"); |
| resetVM(); |
| } |
| |
| } |
| |
| /** |
| * @see OutOfMemoryError |
| * @throws CacheException |
| * @throws InterruptedException |
| */ |
| public void _testPersistentOutOfMemory() |
| throws CacheException, InterruptedException { |
| |
| String exceptions = |
| OutOfMemoryError.class.getName() |
| + "||" |
| + AssertionError.class.getName(); |
| try { |
| String name = "testPersistentOutOfMemory"; |
| doExec("setListener2"); |
| doMessage( |
| "<ExpectedException action=add>" |
| + exceptions |
| + "</ExpectedException>"); |
| doCreateEntry(name); |
| doVerifyDisconnected(); |
| } |
| finally { |
| doMessage( |
| "<ExpectedException action=remove>" |
| + exceptions |
| + "</ExpectedException>"); |
| resetVM(); |
| } |
| |
| } |
| |
| /** |
| * @see OutOfMemoryError |
| * @throws CacheException |
| * @throws InterruptedException |
| */ |
| public void _testMemoryMonitor() |
| throws CacheException, InterruptedException { |
| |
| String exceptions = |
| OutOfMemoryError.class.getName() |
| + "||" |
| + AssertionError.class.getName(); |
| try { |
| String name = "testMemoryMonitor"; |
| doExec("setListener2"); |
| doMessage( |
| "<ExpectedException action=add>" |
| + exceptions |
| + "</ExpectedException>"); |
| doCreateEntry(name); |
| doVerifyDisconnected(); |
| } |
| finally { |
| doMessage( |
| "<ExpectedException action=remove>" |
| + exceptions |
| + "</ExpectedException>"); |
| resetVM(); |
| } |
| |
| } |
| |
| /** |
| * @see InternalError |
| * @throws CacheException |
| * @throws InterruptedException |
| */ |
| public void _testInternalError() |
| throws CacheException, InterruptedException { |
| String exceptions = |
| InternalError.class.getName() |
| + "||" |
| + AssertionError.class.getName(); |
| try { |
| String name = "testInternalError"; |
| |
| doMessage( |
| "<ExpectedException action=add>" |
| + exceptions |
| + "</ExpectedException>"); |
| doCreateEntry(name); |
| doVerifyDisconnected(); |
| } |
| finally { |
| doMessage( |
| "<ExpectedException action=remove>" |
| + exceptions |
| + "</ExpectedException>"); |
| resetVM(); |
| } |
| } |
| |
| /** |
| * @see UnknownError |
| * @throws CacheException |
| * @throws InterruptedException |
| */ |
| public void _testUnknownError() |
| throws CacheException, InterruptedException { |
| String exceptions = |
| UnknownError.class.getName() |
| + "||" |
| + AssertionError.class.getName(); |
| try { |
| String name = "testUnknownError"; |
| doMessage( |
| "<ExpectedException action=add>" |
| + exceptions |
| + "</ExpectedException>"); |
| doCreateEntry(name); |
| doVerifyDisconnected(); |
| } |
| finally { |
| doMessage( |
| "<ExpectedException action=remove>" |
| + exceptions |
| + "</ExpectedException>"); |
| resetVM(); |
| } |
| } |
| |
| /** |
| * This class can never be successfully loaded. |
| * |
| * @author jpenney |
| */ |
| static class SickoClass { |
| static private boolean threeCardMonte() { |
| return true; |
| } |
| |
| static { |
| // Javac isn't keen about a static initializer |
| // that has a throw in it, so this test is |
| // to obfuscate my obviously bogus code... |
| if (System.currentTimeMillis() != 0 || threeCardMonte()) { |
| throw new Error("annoying, aren't I?"); |
| } |
| } |
| } |
| |
| /** |
| * Create some sort of horrible failure that is <em>not</em> |
| * a VirtualMachineError. |
| */ |
| public void _testError() |
| throws CacheException, InterruptedException { |
| // In this case we do NOT expect a failure |
| String exceptions = |
| Error.class.getName(); |
| |
| try { |
| String name = "testError"; |
| doCreateEntry(name); |
| Assert.assertTrue(doVerifyConnected()); |
| |
| doMessage( |
| "<ExpectedException action=add>" |
| + exceptions |
| + "</ExpectedException>"); |
| } |
| finally { |
| doMessage( |
| "<ExpectedException action=remove>" |
| + exceptions |
| + "</ExpectedException>"); |
| // just in case |
| resetVM(); |
| } |
| } |
| |
| static protected final AtomicInteger listenerCount = new AtomicInteger(0); |
| static protected Integer getListenerCount() { |
| return new Integer(listenerCount.get()); |
| } |
| |
| static private final Runnable listener1 = new Runnable() { |
| public void run() { |
| getLogWriter().info("Inside of preListener1"); |
| listenerCount.addAndGet(1); |
| } |
| }; |
| |
| protected static void setListener1() { |
| listenerCount.set(0); |
| SystemFailure.setFailureAction(listener1); |
| } |
| |
| protected static void setListener2() { |
| peskyMemory = null; |
| synchronized (SystemFailureDUnitTest.class) { |
| SystemFailureDUnitTest.class.notify(); |
| } |
| } |
| |
| /** |
| * Verify that listener gets called, and exactly once. |
| */ |
| public void _testListener() |
| throws CacheException, InterruptedException { |
| |
| String exceptions = |
| Error.class.getName(); |
| try { |
| String name = "testListener"; |
| doExec("setListener1"); |
| |
| doMessage( |
| "<ExpectedException action=add>" |
| + exceptions |
| + "</ExpectedException>"); |
| doCreateEntry(name); |
| |
| Integer count = (Integer)doExec("getListenerCount"); |
| Assert.assertEquals(1, count.intValue()); |
| doVerifyDisconnected(); |
| } |
| finally { |
| doMessage( |
| "<ExpectedException action=remove>" |
| + exceptions |
| + "</ExpectedException>"); |
| resetVM(); |
| } |
| |
| } |
| |
| // protected static void doReset() { |
| // // TODO instead of trying to patch up this VM, Lise should create |
| // // me a brand spanking new one |
| // throw new TestException("Sorry, ask Lise to fix this"); |
| // |
| // // You'll have to un-comment some methods in order to make |
| // // the following hack work ONCE on a VM... |
| //// try { |
| //// Thread.sleep(5000); |
| //// } |
| //// catch (InterruptedException e) { |
| //// fail("interrupted"); |
| //// } |
| //// |
| //// SystemFailure.reset(); <--- here |
| //// DistributedCacheTestCase.cache = null; |
| //// |
| //// // Discard the existing cache instance |
| //// GemFireCache.clearInstance(); <--- here |
| //// |
| //// // This is just to stop the stat sampler thread |
| //// HostStatSampler.emergencyStop(); <--- here |
| // } |
| |
| private void resetVM() { |
| Host host = Host.getHost(0); |
| VM vm = host.getVM(0); |
| vm.bounce(); |
| } |
| |
| private static final long MAX_WAIT = 60 * 1000; |
| |
| private boolean doVerifyConnected() { |
| Host host = Host.getHost(0); |
| VM vm = host.getVM(0); |
| Object o = vm.invoke(this.getClass(), "verifyConnected"); |
| return ((Boolean)o).booleanValue(); |
| } |
| |
| protected static Boolean verifyConnected() { |
| if (SystemFailure.getFailure() != null) { |
| fail("System failure present!", SystemFailure.getFailure()); |
| return Boolean.FALSE; |
| } |
| GemFireCacheImpl gfc = (GemFireCacheImpl)cache; |
| if (gfc.isClosed()) { |
| fail("Cache is closing/closed!"); |
| return Boolean.FALSE; |
| } |
| |
| // Let's inspect the distributed system. It should also |
| // be connected. |
| if (system.getCancelCriterion().cancelInProgress() != null) { |
| fail("distributed system cancel in progress"); |
| return Boolean.FALSE; |
| } |
| if (!system.isConnected()) { |
| fail("distributed system not connected"); |
| return Boolean.FALSE; |
| } |
| |
| return Boolean.TRUE; |
| } |
| |
| private boolean doVerifyDisconnected() { |
| Host host = Host.getHost(0); |
| VM vm = host.getVM(0); |
| return ((Boolean)vm.invoke(this.getClass(), "verifyDisconnected")) |
| .booleanValue(); |
| } |
| |
| protected static Boolean verifyDisconnected() { |
| if (SystemFailure.getFailure() == null) { |
| fail("No system failure present!"); |
| return Boolean.FALSE; |
| } |
| GemFireCacheImpl gfc = (GemFireCacheImpl)cache; |
| |
| // Allow cache time to finish disconnecting |
| long done = System.currentTimeMillis() + MAX_WAIT; |
| for (;;) { |
| long now = System.currentTimeMillis(); |
| if (now >= done) { |
| fail("Time out waiting for cache to close: " + cache.toString()); |
| return Boolean.FALSE; |
| } |
| if (gfc.isClosed()) { |
| break; |
| } |
| try { |
| Thread.sleep(2000); |
| } |
| catch (InterruptedException e) { |
| fail("interrupted"); |
| return Boolean.FALSE; |
| } |
| } |
| |
| // At this point, the cache we peeked earlier should be unavailable |
| Assert.assertTrue(GemFireCacheImpl.getInstance() == null); |
| |
| // Ditto for the distributed system |
| InternalDistributedSystem ids = (InternalDistributedSystem) |
| gfc.getDistributedSystem(); |
| if (ids == null) { |
| return Boolean.TRUE; // uhhh, pretty dead! |
| } |
| try { |
| DistributionManager dm = (DistributionManager)ids.getDistributionManager(); |
| if (dm == null) { |
| return Boolean.TRUE; |
| } |
| return new Boolean(dm.getCancelCriterion().cancelInProgress() != null); |
| } |
| catch (CancelException e) { |
| // TODO -- it would be nice to avoid the checkConnected() call above |
| return Boolean.TRUE; |
| } |
| } |
| |
| static protected volatile ArrayList peskyMemory; |
| |
| private Object doExec(String method) { |
| Host host = Host.getHost(0); |
| VM vm = host.getVM(0); |
| return vm.invoke(this.getClass(), method); |
| } |
| |
| private void doMessage(String text) { |
| Object args[] = new Object[] {text}; |
| Host host = Host.getHost(0); |
| VM vm = host.getVM(0); |
| vm.invoke(this.getClass(), "message", args); |
| } |
| |
| protected static void message(String s) { |
| System.out.println(s); |
| System.err.println(s); |
| getLogWriter().info(s); |
| cache.getLogger().info(s); |
| } |
| |
| /** |
| * Create a region with one entry in this test's region with the |
| * given name and attributes. |
| */ |
| private static void createEntry(String name, int ttl, |
| ExpirationAction action, |
| GenericListener l) |
| throws CacheException { |
| |
| Region region = getRegion(); |
| AttributesFactory factory = |
| new AttributesFactory(region.getAttributes()); |
| factory.setStatisticsEnabled(true); |
| factory.setEntryTimeToLive(new ExpirationAttributes(ttl, action)); |
| factory.setScope(SCOPE); |
| factory.setCacheListener(l); |
| |
| Region sub = |
| region.createSubregion(name, factory.create()); |
| sub.create(name, new Integer(0), sub.getCache().getDistributedSystem().getDistributedMember()); |
| } |
| |
| static private final GenericListener listener_stackOverflow = new GenericListener() { |
| /** |
| * gratuitous and stupid recursion |
| */ |
| private void forceOverflow() { |
| forceOverflow(); |
| } |
| |
| public void afterCreate(EntryEvent event) { |
| forceOverflow(); |
| } |
| }; |
| |
| private static final GenericListener listener_outOfMemory = new GenericListener() { |
| /** |
| * Allocate objects until death |
| */ |
| private void forceOutOfMemory() { |
| ArrayList junk = new ArrayList(); |
| for (;;) { |
| junk.add(new long[100000]); |
| } |
| } |
| public void afterCreate(EntryEvent event) { |
| getLogWriter().info("Invoking afterCreate on listener; name=" + |
| event.getKey()); |
| forceOutOfMemory(); |
| } |
| }; |
| |
| static private final GenericListener listener_persistentOutOfMemory = new GenericListener() { |
| /** |
| * Allocate objects until death |
| */ |
| private void forceOutOfMemory() { |
| peskyMemory = new ArrayList(); |
| // Allocate this _before_ exhausting memory :-) |
| final TestException whoops = new TestException("Timeout!"); |
| try { |
| for (;;) { |
| peskyMemory.add(new long[100000]); |
| } |
| } |
| catch (OutOfMemoryError e) { |
| // Now then...while we're out of memory... |
| // ...signal failure |
| SystemFailure.setFailure(e); |
| |
| // Next, wait for the listener to finish running |
| long fin = System.currentTimeMillis() + 60 * 1000; |
| for (;;) { |
| if (peskyMemory == null) { |
| break; |
| } |
| if (System.currentTimeMillis() > fin) { |
| throw whoops; |
| } |
| synchronized (SystemFailureDUnitTest.class) { |
| try { |
| SystemFailureDUnitTest.class.wait(2000); |
| } |
| catch (InterruptedException e2) { |
| fail("interrupted"); |
| } |
| } |
| } |
| } |
| } |
| public void afterCreate(EntryEvent event) { |
| getLogWriter().info("Invoking afterCreate on listener; name=" + |
| event.getKey()); |
| forceOutOfMemory(); |
| } |
| }; |
| |
| static private final GenericListener listener_memoryMonitor = new GenericListener() { |
| |
| /** |
| * Allocate objects until we are chronically low, but don't generate |
| * OutOfMemoryError |
| */ |
| private void forceLowMemory() { |
| long maxMem = Runtime.getRuntime().maxMemory(); |
| long avail = Runtime.getRuntime().freeMemory(); |
| long thresh = (long)(avail * 0.40); |
| long ferSure = (long)(avail * 0.30); |
| SystemFailure.setFailureMemoryThreshold(thresh); |
| SystemFailure.setFailureAction(new Runnable() { |
| public void run() { |
| peskyMemory = null; |
| System.gc(); |
| synchronized (SystemFailure.class) { |
| SystemFailure.class.notify(); |
| } |
| } |
| }); |
| |
| peskyMemory = new ArrayList(); |
| // Allocate this _before_ exhausting memory :-) |
| final TestException whoops = new TestException("Timeout!"); |
| |
| // Fill up a lot of memory |
| for (;;) { |
| peskyMemory.add(new long[100000]); |
| if (Runtime.getRuntime().totalMemory() < maxMem) { |
| continue; // haven't finished allocating max allowed |
| } |
| if (Runtime.getRuntime().freeMemory() < ferSure) { |
| break; |
| } |
| } // for |
| |
| |
| // Wait for the failure monitor to kick in |
| long fin = System.currentTimeMillis() + |
| (long)(SystemFailure.MEMORY_MAX_WAIT * 1.5 * 1000); |
| for (;;) { |
| long now = System.currentTimeMillis(); |
| if (now > fin) { |
| throw whoops; |
| } |
| synchronized (SystemFailure.class) { |
| try { |
| if (peskyMemory == null) { |
| break; |
| } |
| SystemFailure.class.wait(fin - now); |
| } |
| catch (InterruptedException e) { |
| fail("interrupted"); |
| } |
| } // synchronized |
| if (peskyMemory == null) { |
| break; |
| } |
| } // for |
| } |
| |
| public void afterCreate(EntryEvent event) { |
| getLogWriter().info("Invoking afterCreate on listener; name=" + |
| event.getKey()); |
| forceLowMemory(); |
| } |
| }; |
| |
| static private final GenericListener listener_internalError = new GenericListener() { |
| /** |
| * not really any good way to convince Java to do |
| * this, so I'm just gonna throw it directly. |
| */ |
| private void forceInternalError() { |
| throw new InternalError("gotcha"); |
| } |
| |
| public void afterCreate(EntryEvent event) { |
| getLogWriter().info("Invoking afterCreate on listener; name=" + |
| event.getKey()); |
| forceInternalError(); |
| } |
| }; |
| |
| static private final GenericListener listener_unknownError = new GenericListener() { |
| |
| /** |
| * Not actually used in current JRE? |
| */ |
| private void forceInternalError() { |
| throw new UnknownError("gotcha"); |
| } |
| |
| public void afterCreate(EntryEvent event) { |
| getLogWriter().info("Invoking afterCreate on listener; name=" + |
| event.getKey()); |
| forceInternalError(); |
| } |
| }; |
| |
| static private final GenericListener listener_error = new GenericListener() { |
| private void forceError() { |
| new SickoClass(); |
| } |
| |
| public void afterCreate(EntryEvent event) { |
| getLogWriter().info("Invoking afterCreate on listener; name=" + |
| event.getKey()); |
| forceError(); |
| } |
| }; |
| |
| /** |
| * Set a listener that generates some sort of error |
| * |
| * @param which makes it test dependent |
| * @return the listener |
| */ |
| static private GenericListener getListener(String which) { |
| GenericListener listener; |
| if (which.equals("testStackOverflow")) { |
| listener = listener_stackOverflow; |
| } |
| else |
| if (which.equals("testOutOfMemory")) { |
| listener = listener_outOfMemory; |
| } |
| else |
| if (which.equals("testPersistentOutOfMemory")) { |
| listener = listener_persistentOutOfMemory; |
| } |
| else |
| if (which.equals("testMemoryMonitor")) { |
| listener = listener_memoryMonitor; |
| } |
| else |
| if (which.equals("testListener")) { |
| listener = listener_internalError; |
| } |
| else |
| if (which.equals("testInternalError")) { |
| listener = listener_internalError; |
| } |
| else |
| if (which.equals("testUnknownError")) { |
| listener = listener_unknownError; |
| } |
| else |
| if (which.equals("testError")) { |
| listener = listener_error; |
| } |
| else { |
| throw new TestException("don't know which listener: " + which); |
| } |
| return listener; |
| } |
| |
| protected void doCreateEntry(String name) { |
| LogWriter log = getLogWriter(); |
| log.info( |
| "<ExpectedException action=add>" + |
| "dunit.RMIException" |
| + "</ExpectedException>"); |
| |
| Object[] args = new Object[] { name, }; |
| Host host = Host.getHost(0); |
| VM vm = host.getVM(0); |
| try { |
| vm.invoke(this.getClass(), "createEntry", args); |
| } |
| catch (RMIException e) { |
| // expected |
| } |
| |
| log.info( |
| "<ExpectedException action=add>" + |
| "dunit.RMIException" |
| + "</ExpectedException>"); |
| } |
| |
| /** |
| * Sets a listener based on the test, and then (attempts to) create |
| * an entry in this test's region with the given name |
| * |
| * @param name the test we are running |
| */ |
| protected static void createEntry(String name) |
| throws CacheException { |
| GenericListener l = getListener(name); |
| createEntry(name, 0, ExpirationAction.INVALIDATE, l); |
| } |
| |
| /** |
| * Gets or creates a region used in this test |
| */ |
| private static Region getRegion() |
| throws CacheException { |
| |
| Region root = getRootRegion(); |
| Region region = root.getSubregion(REGION_NAME);; |
| if (region == null) { |
| AttributesFactory factory = new AttributesFactory(); |
| factory.setScope(SCOPE); |
| region = root.createSubregion(REGION_NAME, |
| factory.create()); |
| } |
| |
| return region; |
| } |
| |
| /** |
| * A class that provides default implementations for the methods of |
| * several listener types. |
| */ |
| public static class GenericListener |
| extends CacheListenerAdapter implements Serializable { |
| |
| //////// CacheListener /////// |
| |
| public void close() { } |
| |
| /** |
| * is called when an object is newly loaded into cache. |
| * @param oevt the ObjectEvent object representing the source object |
| * of the event. |
| */ |
| public void afterCreate(EntryEvent oevt) { |
| fail("Unexpected listener callback: afterCreate"); |
| } |
| |
| /** |
| * is called when an object is invalidated. |
| * @param oevt the ObjectEvent object representing the source object |
| * of the event. |
| */ |
| public void afterInvalidate(EntryEvent oevt) { |
| fail("Unexpected listener callback: afterInvalidated"); |
| } |
| |
| /** |
| * is called when an object is destroyed. |
| * @param oevt the ObjectEvent object representing the source object |
| * of the event. |
| */ |
| public void afterDestroy(EntryEvent oevt) { |
| fail("Unexpected listener callback: afterDestroy"); |
| } |
| |
| /** |
| * is called when an object is replaced. |
| * @param oevt the ObjectEvent object representing the source object |
| * of the event. |
| */ |
| public void afterUpdate(EntryEvent oevt) { |
| fail("Unexpected listener callback: afterUpdate"); |
| } |
| |
| /** |
| * is called when a region is invalidated. |
| * @param revt a RegionEvent to represent the source region. |
| * @throws CacheException if any error occurs. |
| */ |
| public void afterRegionInvalidate(RegionEvent revt) { |
| fail("Unexpected listener callback: afterRegionInvalidate"); |
| } |
| |
| /** |
| * is called when a region is destroyed. |
| * @param revt a RegionEvent to represent the source region. |
| * @throws CacheException if any error occurs. |
| */ |
| public void afterRegionDestroy(RegionEvent revt) { |
| // fail("Unexpected listener callback: afterRegionDestroy"); |
| } |
| } |
| |
| } |