/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.voltdb.client;
import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import org.voltcore.network.ReverseDNSCache;
import org.voltdb.ServerThread;
import org.voltdb.VoltDB;
import org.voltdb.VoltDB.Configuration;
import org.voltdb.VoltTable;
import org.voltdb.compiler.CatalogBuilder;
import org.voltdb.compiler.DeploymentBuilder;
import junit.framework.TestCase;
public class TestClientClose extends TestCase {
ServerThread localServer;
DeploymentBuilder depBuilder;
@Override
public void setUp()
{
try {
CatalogBuilder catBuilder = new CatalogBuilder();
catBuilder.addSchema(getClass().getResource("clientfeatures.sql"));
catBuilder.addProcedures(ArbitraryDurationProc.class);
boolean success = catBuilder.compile(Configuration.getPathToCatalogForTest("timeouts.jar"));
assert(success);
depBuilder = new DeploymentBuilder(1, 1, 0);
depBuilder.writeXML(Configuration.getPathToCatalogForTest("timeouts.xml"));
VoltDB.Configuration config = new VoltDB.Configuration();
config.m_pathToCatalog = Configuration.getPathToCatalogForTest("timeouts.jar");
config.m_pathToDeployment = Configuration.getPathToCatalogForTest("timeouts.xml");
localServer = new ServerThread(config);
localServer.start();
localServer.waitForInitialization();
ClientFactory.m_preserveResources = false;
while (ClientFactory.m_activeClientCount > 0) {
try {
ClientFactory.decreaseClientNum();
}
catch (InterruptedException e) {}
}
// The DNS cache is always initialized in the started state
ReverseDNSCache.start();
}
catch (Exception e) {
e.printStackTrace();
fail();
}
}
@Override
public void tearDown() throws Exception {
localServer.shutdown();
}
public void testThreadsKilledClientClose() throws Exception {
int preNumClientReaper = 0;
Map<Thread, StackTraceElement[]> preStMap = Thread.getAllStackTraces();
for (Thread t : preStMap.keySet()) {
if (t.getName().contains("VoltDB Client Reaper Thread")) {
preNumClientReaper++;
}
}
ClientConfig config = new ClientConfig();
Client client = ClientFactory.createClient(config);
client.createConnection("localhost");
client.close();
ClientFactory.decreaseClientNum();
Thread.sleep(2000);
Map<Thread, StackTraceElement[]> stMap = Thread.getAllStackTraces();
int postNumClientReaper = 0;
for (Entry<Thread, StackTraceElement[]> e : stMap.entrySet()) {
// skip the current thread
Thread t = e.getKey();
if (t == Thread.currentThread()) {
continue;
}
// check thread name and whether the thread should be close.
String threadName = t.getName();
if (threadName.contains("VoltDB Client Reaper Thread")) {
postNumClientReaper++;
}
assertFalse(threadName.contains("Reverse DNS lookups") || threadName.contains("Async Logger")
|| threadName.contains("Estimated Time Updater"));
}
assertEquals(preNumClientReaper, postNumClientReaper);
}
public void testThreadsKilledOneOfClientClose() throws Exception {
ClientConfig config = new ClientConfig();
Client client1 = ClientFactory.createClient(config);
Client client2 = ClientFactory.createClient(config);
try {
client1.createConnection("localhost");
client2.createConnection("localhost");
VoltTable configData1 = client1.callProcedure("@SystemCatalog", "TYPEINFO").getResults()[0];
client1.close();
VoltTable configData2 = client2.callProcedure("@SystemCatalog", "TYPEINFO").getResults()[0];
} catch (IOException | ProcCallException e) {
fail("Something failed in call procedure for a client after close another one.");
} finally {
client2.close();
}
}
public void testCreateCloseAllClientInParallel() throws Exception{
clientCreateCloseAll(50, 3);
Thread.sleep(500);
Map<Thread, StackTraceElement[]> stMap = Thread.getAllStackTraces();
for (Entry<Thread, StackTraceElement[]> e : stMap.entrySet()) {
// skip the current thread
Thread t = e.getKey();
if (t == Thread.currentThread()) {
continue;
}
// check thread name and whether the thread should be close.
String threadName = t.getName();
assertFalse(threadName.contains("Reverse DNS lookups") || threadName.contains("Async Logger")
|| threadName.contains("Estimated Time Updater"));
}
}
public void testCreateCloseInParallelRemainOne() throws Exception {
Client remainClient = ClientFactory.createClient();
remainClient.createConnection("localhost");
clientCreateCloseAll(50, 3);
Thread.sleep(500);
assertTrue(checkThreadsAllExist());
remainClient.close();
}
public void testCreateCloseInParallelStartOne() throws Exception {
clientCreateCloseAll(50, 3);
Client client = ClientFactory.createClient();
client.createConnection("localhost");
Thread.sleep(500);
assertTrue(checkThreadsAllExist());
client.close();
}
private void clientCreateCloseAll(int clientNum, int loops) throws Exception {
CountDownLatch latch = new CountDownLatch(loops);
for (int i = 0; i < loops; i++) {
(new clientCreateCloseAllLauncher(clientNum, latch, i)).start();
}
latch.await();
}
private boolean checkThreadsAllExist() {
boolean haveReverseDNSLookups = false;
boolean haveAsyncLogger = false;
boolean haveEstTimeUpdater = false;
boolean haveClientReaper = false;
Map<Thread, StackTraceElement[]> stMap = Thread.getAllStackTraces();
for (Entry<Thread, StackTraceElement[]> e : stMap.entrySet()) {
// skip the current thread
Thread t = e.getKey();
if (t == Thread.currentThread()) {
continue;
}
// check thread name and whether the thread should be close.
String threadName = t.getName();
if (threadName.contains("Reverse DNS lookups")) {
haveReverseDNSLookups = true;
} else if (threadName.contains("Async Logger")) {
haveAsyncLogger = true;
} else if (threadName.contains("Estimated Time Updater")) {
haveEstTimeUpdater = true;
} else if (threadName.contains("VoltDB Client Reaper Thread")) {
haveClientReaper = true;
}
}
return haveReverseDNSLookups && haveAsyncLogger && haveEstTimeUpdater && haveClientReaper;
}
class clientCreateCloseAllLauncher extends Thread{
private final int m_clientNum;
private final CountDownLatch m_latch;
public clientCreateCloseAllLauncher (int clientNum, CountDownLatch latch, int loopNum) {
m_clientNum = clientNum;
m_latch = latch;
}
@Override
public void run () {
try {
for (int i = 0; i < m_clientNum; i++) {
Client client = ClientFactory.createClient();
client.createConnection("localhost");
client.close();
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
m_latch.countDown();
}
}
}
}