package co.codewizards.cloudstore.test; import static org.assertj.core.api.Assertions.*; import java.util.ArrayList; import java.util.Collections; import java.util.List; import mockit.Invocation; import mockit.Mock; import mockit.MockUp; import mockit.integration.junit4.JMockit; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import co.codewizards.cloudstore.ls.client.LocalServerClient; import co.codewizards.cloudstore.ls.core.invoke.ObjectManager; import co.codewizards.cloudstore.ls.core.invoke.ObjectRef; import co.codewizards.cloudstore.ls.rest.client.LocalServerRestClient; import co.codewizards.cloudstore.test.model.ExampleService; import co.codewizards.cloudstore.test.model.ExampleServiceImpl; import co.codewizards.cloudstore.test.model.ExampleServiceRegistry; import co.codewizards.cloudstore.test.model.ExampleServiceRegistryImpl; @RunWith(JMockit.class) public class LocalServerClientGarbageCollectionIT extends AbstractIT { private static final Logger logger = LoggerFactory.getLogger(LocalServerClientGarbageCollectionIT.class); private final List<MockUp<?>> mockUps = new ArrayList<>(); // private LocalServer localServer; // LocalServer is already started by main server. private LocalServerClient client; private List<ObjectRef> removedObjectRefs; private final List<Exception> errors = Collections.synchronizedList(new ArrayList<Exception>()); @Override public void before() throws Exception { super.before(); errors.clear(); removedObjectRefs = new ArrayList<>(); mockUps.add(new MockUp<ObjectManager>() { @Mock void remove(Invocation invocation, ObjectRef objectRef) { removedObjectRefs.add(objectRef); invocation.proceed(objectRef); } }); final LocalServerRestClient localServerRestClient = new LocalServerRestClient() { }; client = new LocalServerClient() { @Override public LocalServerRestClient _getLocalServerRestClient() { return localServerRestClient; } }; } @SuppressWarnings("deprecation") @Override public void after() throws Exception { if (client != null) { client.close(); client = null; } for (MockUp<?> mockUp : mockUps) mockUp.tearDown(); mockUps.clear(); ObjectManager.clearObjectManagers(); super.after(); } @Test public void testSimpleGarbageCollection() throws Exception { List<ExampleService> exampleServices = new ArrayList<>(); String stringValue = "testGarbageCollection"; // Test that GC was *not* (yet) done on server-side, because we still hold references on the client-side. removedObjectRefs.clear(); for (int i = 0; i < 1000; ++i) { ExampleService exampleService = client.invokeConstructor(ExampleServiceImpl.class); exampleService.setStringValue(stringValue); exampleServices.add(exampleService); if (i % 100 == 0) { System.gc(); Thread.sleep(5000L); } } System.gc(); Thread.sleep(5000L); assertThat(removedObjectRefs).isEmpty(); // Now, release the client-side references and expect garbage-collection to happen on the server-side. exampleServices.clear(); for (int i = 0; i < 1000; ++i) { ExampleService exampleService = client.invokeConstructor(ExampleServiceImpl.class); exampleService.setStringValue(stringValue); exampleServices.add(exampleService); if (i % 100 == 0) { System.gc(); Thread.sleep(5000L); } } System.gc(); Thread.sleep(5000L); assertThat(removedObjectRefs.size()).isGreaterThan(200); } @Test public void testMultiThreadGarbageCollection() throws Exception { final ExampleServiceRegistry registry = client.invokeStatic(ExampleServiceRegistryImpl.class, "getInstance"); final long startTimestamp = System.currentTimeMillis(); final List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < 3; ++i) { Thread t = new Thread() { @Override public void run() { while (System.currentTimeMillis() - startTimestamp < 30000L && errors.isEmpty()) { try { ExampleService exampleService = registry.getExampleServiceOrCreate(random.nextInt(5)); exampleService.setStringValue("bla"); try { Thread.sleep(100); } catch (InterruptedException e) { } exampleService.setStringValue("blubb"); exampleService = null; if (random.nextInt(100) < 30) System.gc(); } catch (Exception x) { logger.error(x.toString(), x); errors.add(x); } } } }; threads.add(t); t.start(); } for (Thread thread : threads) thread.join(); if (!errors.isEmpty()) throw errors.get(0); } }