package net.xqj.basex.local;
import static net.xqj.basex.BaseXXQInsertOptions.*;
import static org.junit.Assert.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
import javax.xml.xquery.*;
import net.xqj.basex.*;
import org.junit.*;
import com.xqj2.*;
/**
* Test XQJ concurrency, both reads and writes.
*
* @author Charles Foster
*/
public final class XQJConcurrencyTest extends XQJBaseTest {
/** Number of threads used when executing read only queries. */
private static final int CONCURRENT_READ_THREADS = 256;
/** Numbers of iterations, when perform a ready query. */
private static final int ITERATE_TO = 1024;
/** Number of threads used when writing documents. */
private static final int CONCURRENT_WRITE_THREADS = 12;
/** Total number of documents to insert when writing. */
private static final int DOCS_TO_INSERT = CONCURRENT_WRITE_THREADS * 30;
/** BaseX insert strategy for inserting documents. */
private static final BaseXXQInsertOptions INSERT_STRATEGY = options(REPLACE);
/**
* Runs read concurrency test.
* @throws Throwable any exception or error
*/
@Test
public void testConcurrentXQuery1to1024() throws Throwable {
final ArrayList<SimpleQueryThread> sqtList = new ArrayList<>();
for(int i = 0; i < CONCURRENT_READ_THREADS; i++)
sqtList.add(new SimpleQueryThread());
for(final SimpleQueryThread s : sqtList) s.start();
for(final SimpleQueryThread s : sqtList) s.join();
for(final SimpleQueryThread s : sqtList) if(s.thrown != null) throw s.thrown;
}
/**
* Runs insert concurrency test.
* @throws Exception exceptions
*/
@Test
public void testConcurrentInsert() throws Exception {
final XQExpression xqpe = xqc.createExpression();
try {
xqpe.executeCommand("CREATE DB xqj-concurrent-insert-test");
xqpe.executeCommand("OPEN xqj-concurrent-insert-test");
xqpe.executeCommand("SET DEFAULTDB true");
final HashMap<String, XQItem> docs = new HashMap<>();
final ThreadPoolExecutor tpe =
new ThreadPoolExecutor(
CONCURRENT_WRITE_THREADS, CONCURRENT_WRITE_THREADS, 4L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(CONCURRENT_READ_THREADS),
new CallerRunsPolicy());
final ArrayList<Future<?>> futures = new ArrayList<>();
for(int i = 0; i < DOCS_TO_INSERT; i++) {
final String uri = i + "-" + UUID.randomUUID() + ".xml";
final XQItem item = createDocument("<e>" + uri + "</e>");
docs.put(uri, item);
}
for(final Entry<String, XQItem> doc : docs.entrySet())
futures.add(tpe.submit(new InsertItemThread(doc.getKey(), doc.getValue())));
for(final Future<?> future : futures)
future.get();
for(final String uri : docs.keySet())
assertTrue(docAvailable(uri));
} finally {
xqpe.executeCommand("DROP DB xqj-concurrent-insert-test");
}
}
/**
* Closes a connection.
* @param conn connection to be closed
*/
private static void close(final XQConnection conn) {
if(conn != null) {
try {
conn.close();
} catch(final XQException ignored) {
/* ... superfluous ... */
}
}
}
/**
* Query Thread.
*/
private class SimpleQueryThread extends Thread {
/** Thrown exception or error. */
Throwable thrown;
@Override
public void run() {
XQConnection newConnection = null;
try {
newConnection = xqds.getConnection();
final XQExpression xqpe = newConnection.createExpression();
final XQResultSequence rs = xqpe.executeQuery("1 to " + ITERATE_TO);
for(int expected = 1; expected != ITERATE_TO; expected++) {
if(!rs.next()) {
thrown = new AssertionError(
"Expecting a result item, but did not find one.");
return;
}
final int value = rs.getInt();
if(value != expected) {
thrown = new AssertionError(
"expected result item '" + expected + "', but got '" + value + "'.");
return;
}
}
} catch(final Throwable th) {
thrown = th;
} finally {
close(newConnection);
}
}
}
/** Insertion thread. */
private final class InsertItemThread extends Thread {
/** URI of document being inserted. */
private final String uri;
/** Content of document being inserted. */
private final XQItem item;
/**
* Constructor.
* @param u uri
* @param it item
*/
private InsertItemThread(final String u, final XQItem it) {
uri = u;
item = it;
}
@Override
public void run() {
try {
final XQConnection2 xqc2 = (XQConnection2) xqc;
xqc2.insertItem(uri, item, INSERT_STRATEGY);
} catch(final XQException ex) {
// a JUnit assertion WILL fail later because of this happening.
ex.printStackTrace();
}
}
}
}