/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.core.data;
import org.apache.jackrabbit.api.management.DataStoreGarbageCollector;
import org.apache.jackrabbit.api.management.MarkEventListener;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.test.AbstractJCRTest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import EDU.oswego.cs.dl.util.concurrent.SynchronousChannel;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import javax.jcr.Binary;
import javax.jcr.Credentials;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.ValueFactory;
/**
* Test cases for data store garbage collection.
*/
public class GarbageCollectorTest extends AbstractJCRTest implements ScanEventListener {
/** logger instance */
private static final Logger LOG = LoggerFactory.getLogger(GarbageCollectorTest.class);
public void testCloseSessionWhileRunningGc() throws Exception {
final Session session = getHelper().getReadWriteSession();
final DataStoreGarbageCollector gc = ((SessionImpl) session).createDataStoreGarbageCollector();
gc.setPersistenceManagerScan(false);
final Exception[] ex = new Exception[1];
gc.setMarkEventListener(new MarkEventListener() {
boolean closed;
public void beforeScanning(Node n) throws RepositoryException {
closeTest();
}
private void closeTest() {
if (closed) {
ex[0] = new Exception("Scanning after the session is closed");
}
closed = true;
session.logout();
}
});
try {
gc.mark();
fail("Exception 'session has been closed' expected");
} catch (RepositoryException e) {
LOG.debug("Expected exception caught: " + e.getMessage());
}
if (ex[0] != null) {
throw ex[0];
}
gc.close();
}
public void testConcurrentGC() throws Exception {
Node root = testRootNode;
Session session = root.getSession();
final SynchronousChannel sync = new SynchronousChannel();
final Node node = root.addNode("slowBlob");
final int blobLength = 1000;
final ValueFactory vf = session.getValueFactory();
new Thread() {
public void run() {
try {
node.setProperty("slowBlob", vf.createBinary(new InputStream() {
int pos;
public int read() throws IOException {
pos++;
if (pos < blobLength) {
return pos % 80 == 0 ? '\n' : '.';
} else if (pos == blobLength) {
try {
sync.put("x");
// deleted
sync.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return 'x';
}
return -1;
}
}));
node.getSession().save();
sync.put("saved");
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
assertEquals("x", sync.take());
DataStoreGarbageCollector gc = ((SessionImpl) session).createDataStoreGarbageCollector();
gc.setPersistenceManagerScan(false);
gc.mark();
gc.sweep();
sync.put("deleted");
assertEquals("saved", sync.take());
InputStream in = node.getProperty("slowBlob").getBinary().getStream();
for (int pos = 1; pos < blobLength; pos++) {
int expected = pos % 80 == 0 ? '\n' : '.';
assertEquals(expected, in.read());
}
assertEquals('x', in.read());
in.close();
gc.close();
}
public void testGC() throws Exception {
Node root = testRootNode;
Session session = root.getSession();
deleteMyNodes();
runGC(session, true);
root.addNode("node1");
Node node2 = root.addNode("node2");
Node n = node2.addNode("nodeWithBlob").addNode("sub");
ValueFactory vf = session.getValueFactory();
Binary b = vf.createBinary(new RandomInputStream(20, 1000));
n.setProperty("test", b);
session.save();
n = node2.addNode("nodeWithTemporaryBlob");
n.setProperty("test", vf.createBinary(new RandomInputStream(11, 1000)));
session.save();
n.remove();
session.save();
GarbageCollector gc = ((SessionImpl)session).createDataStoreGarbageCollector();
gc.getDataStore().clearInUse();
gc.setPersistenceManagerScan(false);
gc.setMarkEventListener(this);
if (gc.getDataStore() instanceof FileDataStore) {
// make sure the file is old (access time resolution is 2 seconds)
Thread.sleep(2000);
}
LOG.debug("scanning...");
gc.mark();
int count = listIdentifiers(gc);
LOG.debug("stop scanning; currently " + count + " identifiers");
LOG.debug("deleting...");
gc.getDataStore().clearInUse();
assertTrue(gc.sweep() > 0);
int count2 = listIdentifiers(gc);
assertEquals(count - 1, count2);
// verify the node was moved, and that the binary is still there
n = root.getNode("node1").getNode("nodeWithBlob").getNode("sub");
b = n.getProperty("test").getValue().getBinary();
InputStream in = b.getStream();
InputStream in2 = new RandomInputStream(20, 1000);
verifyInputStream(in, in2);
deleteMyNodes();
gc.close();
}
/**
* Test to validate that two GC cannot run simulatenously. one
* exits throwing exception
*/
public void testSimulatenousRunGC() throws Exception {
Node root = testRootNode;
Session session = root.getSession();
GCThread gct1 = new GCThread(session);
GCThread gct2 = new GCThread(session);
Thread gcThread1 = new Thread(gct1, "Datastore Garbage Collector 1");
Thread gcThread2 = new Thread(gct2, "Datastore Garbage Collector 2");
// run simulatensou gc
gcThread1.start();
gcThread2.start();
Thread.sleep(100);
gct1.setStop(true);
gct2.setStop(true);
// allow them to complete
gcThread1.join();
gcThread2.join();
// only one should throw error
int count = (gct1.getException() == null ? 0 : 1) + (gct2.getException() == null ? 0 : 1);
assertEquals("only one gc should throw exception ", 1, count);
}
private void runGC(Session session, boolean all) throws Exception {
GarbageCollector gc = ((SessionImpl)session).createDataStoreGarbageCollector();
gc.setMarkEventListener(this);
gc.setPersistenceManagerScan(false);
if (gc.getDataStore() instanceof FileDataStore) {
// make sure the file is old (access time resolution is 2 seconds)
Thread.sleep(2000);
}
gc.mark();
gc.stopScan();
if (all) {
gc.getDataStore().clearInUse();
}
gc.sweep();
gc.close();
}
private static int listIdentifiers(GarbageCollector gc) throws DataStoreException {
LOG.debug("identifiers:");
int count = 0;
Iterator<DataIdentifier> it = gc.getDataStore().getAllIdentifiers();
while (it.hasNext()) {
DataIdentifier id = it.next();
LOG.debug(" " + id);
count++;
}
return count;
}
public void testTransientObjects() throws Exception {
Node root = testRootNode;
Session session = root.getSession();
deleteMyNodes();
Credentials cred = getHelper().getSuperuserCredentials();
Session s2 = getHelper().getRepository().login(cred);
root = s2.getRootNode();
Node node2 = root.addNode("node3");
Node n = node2.addNode("nodeWithBlob");
ValueFactory vf = session.getValueFactory();
n.setProperty("test", vf.createBinary(new RandomInputStream(10, 1000)));
runGC(session, false);
s2.save();
InputStream in = n.getProperty("test").getBinary().getStream();
InputStream in2 = new RandomInputStream(10, 1000);
verifyInputStream(in, in2);
deleteMyNodes();
s2.logout();
}
private static void verifyInputStream(InputStream in, InputStream in2) throws IOException {
while (true) {
int a = in.read();
int b = in2.read();
assertEquals(a, b);
if (a < 0) {
break;
}
}
}
public void afterScanning(Node n) throws RepositoryException {
if (n != null && n.getPath().startsWith("/testroot/node")) {
String path = n.getPath();
LOG.debug("scanned: " + path);
}
}
private void list(Node n) throws RepositoryException {
if (!n.getName().startsWith("jcr:")) {
for (NodeIterator it = n.getNodes(); it.hasNext();) {
list(it.nextNode());
}
}
}
public void beforeScanning(Node n) throws RepositoryException {
if (n != null && n.getPath().equals("/testroot/node2")) {
Session session = n.getSession();
list(session.getRootNode());
session.move("/testroot/node2/nodeWithBlob", "/testroot/node1/nodeWithBlob");
session.save();
LOG.debug("moved /testroot/node2/nodeWithBlob to /testroot/node1");
}
}
private void deleteMyNodes() throws RepositoryException {
Node root = testRootNode;
while (root.hasNode("testroot")) {
root.getNode("testroot").remove();
}
root.getSession().save();
}
}