[Major] Refactored locking xmlpers to always first lock parent, and unlock in TX

This commit is contained in:
Robert von Burg 2019-09-04 12:38:59 +02:00
parent eefdbb7613
commit 49731f5862
11 changed files with 306 additions and 317 deletions

View File

@ -115,54 +115,46 @@ public class FileDao {
throw new IllegalArgumentException("IdRefs don't reference directories!"); //$NON-NLS-1$ throw new IllegalArgumentException("IdRefs don't reference directories!"); //$NON-NLS-1$
} }
objectRef.lock(); File directoryPath = objectRef.getPath(this.pathBuilder);
if (!directoryPath.getAbsolutePath().startsWith(this.pathBuilder.getRootPath().getAbsolutePath())) {
try { String msg = "The path for {0} is invalid as not child of {1}"; //$NON-NLS-1$
msg = MessageFormat
File directoryPath = objectRef.getPath(this.pathBuilder); .format(msg, directoryPath.getAbsolutePath(), this.pathBuilder.getRootPath().getAbsolutePath());
if (!directoryPath.getAbsolutePath().startsWith(this.pathBuilder.getRootPath().getAbsolutePath())) { throw new IllegalArgumentException(msg);
String msg = "The path for {0} is invalid as not child of {1}"; //$NON-NLS-1$
msg = MessageFormat
.format(msg, directoryPath.getAbsolutePath(), this.pathBuilder.getRootPath().getAbsolutePath());
throw new IllegalArgumentException(msg);
}
if (!directoryPath.isDirectory()) {
String msg = "The path for {0} is not a directory: {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath());
throw new IllegalArgumentException(msg);
}
String[] list = directoryPath.list();
if (list == null) {
String msg = "The path for {0} is not a directory: {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath());
throw new IllegalArgumentException(msg);
}
// stop if empty
if (list.length != 0)
return;
// delete
if (!directoryPath.delete()) {
String msg = "Deletion of empty directory for {0} at {1} failed! Check file permissions!"; //$NON-NLS-1$
msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath());
throw new XmlPersistenceException(msg);
}
// log
if (this.verbose) {
String msg = "Deleted empty directory for {0} at {1}"; //$NON-NLS-1$
logger.info(MessageFormat.format(msg, objectRef.getName(), directoryPath));
}
// recursively delete
ObjectRef parent = objectRef.getParent(this.tx);
deleteEmptyDirectories(parent);
} finally {
objectRef.unlock();
} }
if (!directoryPath.isDirectory()) {
String msg = "The path for {0} is not a directory: {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath());
throw new IllegalArgumentException(msg);
}
String[] list = directoryPath.list();
if (list == null) {
String msg = "The path for {0} is not a directory: {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath());
throw new IllegalArgumentException(msg);
}
// stop if empty
if (list.length != 0)
return;
// delete
if (!directoryPath.delete()) {
String msg = "Deletion of empty directory for {0} at {1} failed! Check file permissions!"; //$NON-NLS-1$
msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath());
throw new XmlPersistenceException(msg);
}
// log
if (this.verbose) {
String msg = "Deleted empty directory for {0} at {1}"; //$NON-NLS-1$
logger.info(MessageFormat.format(msg, objectRef.getName(), directoryPath));
}
// recursively delete
ObjectRef parent = objectRef.getParent(this.tx);
deleteEmptyDirectories(parent);
} }
private void logPath(IoOperation operation, File path, ObjectRef objectRef) { private void logPath(IoOperation operation, File path, ObjectRef objectRef) {
@ -175,16 +167,11 @@ public class FileDao {
private void createMissingParents(File path, ObjectRef objectRef) { private void createMissingParents(File path, ObjectRef objectRef) {
ObjectRef parentRef = objectRef.getParent(this.tx); ObjectRef parentRef = objectRef.getParent(this.tx);
parentRef.lock(); File parentFile = parentRef.getPath(this.pathBuilder);
try { if (!parentFile.exists() && !parentFile.mkdirs()) {
File parentFile = parentRef.getPath(this.pathBuilder); String msg = "Could not create parent path for {0} at {1}"; //$NON-NLS-1$
if (!parentFile.exists() && !parentFile.mkdirs()) { msg = MessageFormat.format(msg, objectRef.getName(), path.getAbsolutePath());
String msg = "Could not create parent path for {0} at {1}"; //$NON-NLS-1$ throw new XmlPersistenceException(msg);
msg = MessageFormat.format(msg, objectRef.getName(), path.getAbsolutePath());
throw new XmlPersistenceException(msg);
}
} finally {
parentRef.unlock();
} }
} }

View File

@ -26,9 +26,8 @@ import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.*; import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource; import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamResult;
import java.io.File; import java.io.*;
import java.io.FileWriter; import java.nio.charset.Charset;
import java.io.IOException;
import java.text.MessageFormat; import java.text.MessageFormat;
import javanet.staxutils.IndentingXMLStreamWriter; import javanet.staxutils.IndentingXMLStreamWriter;
@ -59,27 +58,29 @@ public class FileIo {
public <T> void writeSax(PersistenceContext<T> ctx) { public <T> void writeSax(PersistenceContext<T> ctx) {
XMLStreamWriter writer; XMLStreamWriter xmlWriter;
try { try {
try (FileWriter fileWriter = new FileWriter(this.tmpPath)) { try (Writer ioWriter = new OutputStreamWriter(new FileOutputStream(this.tmpPath), DEFAULT_ENCODING)) {
XMLOutputFactory factory = XMLOutputFactory.newInstance(); XMLOutputFactory factory = XMLOutputFactory.newInstance();
writer = factory.createXMLStreamWriter(fileWriter); xmlWriter = factory.createXMLStreamWriter(ioWriter);
writer = new IndentingXMLStreamWriter(writer); xmlWriter = new IndentingXMLStreamWriter(xmlWriter);
// start document // start document
writer.writeStartDocument(DEFAULT_ENCODING, DEFAULT_XML_VERSION); xmlWriter.writeStartDocument(DEFAULT_ENCODING, DEFAULT_XML_VERSION);
// then delegate object writing to caller // then delegate object writing to caller
SaxParser<T> saxParser = ctx.getParserFactor().getSaxParser(); SaxParser<T> saxParser = ctx.getParserFactor().getSaxParser();
saxParser.setObject(ctx.getObject()); saxParser.setObject(ctx.getObject());
saxParser.write(writer); saxParser.write(xmlWriter);
// and now end // and now end
writer.writeEndDocument(); xmlWriter.writeEndDocument();
writer.flush(); xmlWriter.flush();
} }
if (this.path.exists() && !this.path.delete())
throw new IllegalStateException("Failed to delete existing file " + this.path.getAbsolutePath());
if (!this.tmpPath.renameTo(this.path)) { if (!this.tmpPath.renameTo(this.path)) {
throw new IllegalStateException( throw new IllegalStateException(
"Failed to rename temp file " + this.tmpPath.getName() + " to " + this.path.getAbsolutePath()); "Failed to rename temp file " + this.tmpPath.getName() + " to " + this.path.getAbsolutePath());
@ -158,21 +159,25 @@ public class FileIo {
// transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); // transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t");
// Transform to file // Transform to file
StreamResult result = new StreamResult(this.tmpPath); try (Writer ioWriter = new OutputStreamWriter(new FileOutputStream(this.tmpPath), encoding)) {
Source xmlSource = new DOMSource(document); StreamResult result = new StreamResult(this.tmpPath);
transformer.transform(xmlSource, result); Source xmlSource = new DOMSource(document);
transformer.transform(xmlSource, result);
}
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
String msg = MessageFormat.format("Wrote DOM to {0}", this.tmpPath.getAbsolutePath()); //$NON-NLS-1$ String msg = MessageFormat.format("Wrote DOM to {0}", this.tmpPath.getAbsolutePath()); //$NON-NLS-1$
logger.info(msg); logger.info(msg);
} }
if (this.path.exists() && !this.path.delete())
throw new IllegalStateException("Failed to delete existing file " + this.path.getAbsolutePath());
if (!this.tmpPath.renameTo(this.path)) { if (!this.tmpPath.renameTo(this.path)) {
throw new IllegalStateException( throw new IllegalStateException(
"Failed to rename temp file " + this.tmpPath.getName() + " to " + this.path.getAbsolutePath()); "Failed to rename temp file " + this.tmpPath.getName() + " to " + this.path.getAbsolutePath());
} }
} catch (TransformerFactoryConfigurationError | TransformerException e) { } catch (IOException | TransformerFactoryConfigurationError | TransformerException e) {
if (this.tmpPath.exists()) { if (this.tmpPath.exists()) {
if (!this.tmpPath.delete()) if (!this.tmpPath.delete())
logger.error("Failed to delete existing temp file " + this.tmpPath.getAbsolutePath()); logger.error("Failed to delete existing temp file " + this.tmpPath.getAbsolutePath());

View File

@ -49,21 +49,17 @@ public class MetadataDao {
assertNotClosed(this.tx); assertNotClosed(this.tx);
assertNotIdRef(parentRef); assertNotIdRef(parentRef);
parentRef.lock(); this.tx.lock(parentRef);
try { File queryPath = parentRef.getPath(this.pathBuilder);
File queryPath = parentRef.getPath(this.pathBuilder); Set<String> keySet = queryTypeSet(queryPath);
Set<String> keySet = queryTypeSet(queryPath);
if (this.verbose) { if (this.verbose) {
String msg = "Found {0} types for {1}"; //$NON-NLS-1$ String msg = "Found {0} types for {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, keySet.size(), parentRef.getName()); msg = MessageFormat.format(msg, keySet.size(), parentRef.getName());
logger.info(msg); logger.info(msg);
}
return keySet;
} finally {
parentRef.unlock();
} }
return keySet;
} }
public Set<String> queryKeySet(ObjectRef parentRef) { public Set<String> queryKeySet(ObjectRef parentRef) {
@ -75,21 +71,17 @@ public class MetadataDao {
assertNotRootRef(parentRef); assertNotRootRef(parentRef);
assertNotIdRef(parentRef); assertNotIdRef(parentRef);
parentRef.lock(); this.tx.lock(parentRef);
try { File queryPath = parentRef.getPath(this.pathBuilder);
File queryPath = parentRef.getPath(this.pathBuilder); Set<String> keySet = queryKeySet(queryPath, reverse);
Set<String> keySet = queryKeySet(queryPath, reverse);
if (this.verbose) { if (this.verbose) {
String msg = "Found {0} objects for {1}"; //$NON-NLS-1$ String msg = "Found {0} objects for {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, keySet.size(), parentRef.getName()); msg = MessageFormat.format(msg, keySet.size(), parentRef.getName());
logger.info(msg); logger.info(msg);
}
return keySet;
} finally {
parentRef.unlock();
} }
return keySet;
} }
public long queryTypeSize(ObjectRef parentRef) { public long queryTypeSize(ObjectRef parentRef) {
@ -97,41 +89,33 @@ public class MetadataDao {
assertNotRootRef(parentRef); assertNotRootRef(parentRef);
assertNotIdRef(parentRef); assertNotIdRef(parentRef);
parentRef.lock(); this.tx.lock(parentRef);
try { File queryPath = parentRef.getPath(this.pathBuilder);
File queryPath = parentRef.getPath(this.pathBuilder); long numberOfFiles = queryTypeSize(queryPath);
long numberOfFiles = queryTypeSize(queryPath);
if (this.verbose) { if (this.verbose) {
String msg = "Found {0} types for {1}"; //$NON-NLS-1$ String msg = "Found {0} types for {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, numberOfFiles, parentRef.getName()); msg = MessageFormat.format(msg, numberOfFiles, parentRef.getName());
logger.info(msg); logger.info(msg);
}
return numberOfFiles;
} finally {
parentRef.unlock();
} }
return numberOfFiles;
} }
public long querySize(ObjectRef parentRef) { public long querySize(ObjectRef parentRef) {
assertNotClosed(this.tx); assertNotClosed(this.tx);
parentRef.lock(); this.tx.lock(parentRef);
try { File queryPath = parentRef.getPath(this.pathBuilder);
File queryPath = parentRef.getPath(this.pathBuilder); long numberOfFiles = querySize(queryPath);
long numberOfFiles = querySize(queryPath);
if (this.verbose) { if (this.verbose) {
String msg = "Found {0} objects for {1}"; //$NON-NLS-1$ String msg = "Found {0} objects for {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, numberOfFiles, parentRef.getName()); msg = MessageFormat.format(msg, numberOfFiles, parentRef.getName());
logger.info(msg); logger.info(msg);
}
return numberOfFiles;
} finally {
parentRef.unlock();
} }
return numberOfFiles;
} }
/** /**

View File

@ -45,19 +45,22 @@ public class ObjectDao {
assertNotClosed(); assertNotClosed();
assertNotNull(object); assertNotNull(object);
PersistenceContext<T> ctx = createCtx(object); PersistenceContext<T> ctx = createCtx(object);
ctx.getObjectRef().lock(); this.tx.lock(ctx.getObjectRef().getParent(this.tx));
this.tx.lock(ctx.getObjectRef());
this.objectFilter.add(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx); this.objectFilter.add(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx);
} }
public <T> void addAll(List<T> objects) { public <T> void addAll(List<T> objects) {
assertNotClosed(); assertNotClosed();
assertNotNull(objects); assertNotNull(objects);
if (!objects.isEmpty()) { if (objects.isEmpty())
for (T object : objects) { return;
PersistenceContext<T> ctx = createCtx(object);
ctx.getObjectRef().lock(); for (T object : objects) {
this.objectFilter.add(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx); PersistenceContext<T> ctx = createCtx(object);
} this.tx.lock(ctx.getObjectRef().getParent(this.tx));
this.tx.lock(ctx.getObjectRef());
this.objectFilter.add(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx);
} }
} }
@ -65,19 +68,20 @@ public class ObjectDao {
assertNotClosed(); assertNotClosed();
assertNotNull(object); assertNotNull(object);
PersistenceContext<T> ctx = createCtx(object); PersistenceContext<T> ctx = createCtx(object);
ctx.getObjectRef().lock(); this.tx.lock(ctx.getObjectRef());
this.objectFilter.update(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx); this.objectFilter.update(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx);
} }
public <T> void updateAll(List<T> objects) { public <T> void updateAll(List<T> objects) {
assertNotClosed(); assertNotClosed();
assertNotNull(objects); assertNotNull(objects);
if (!objects.isEmpty()) { if (objects.isEmpty())
for (T object : objects) { return;
PersistenceContext<T> ctx = createCtx(object);
ctx.getObjectRef().lock(); for (T object : objects) {
this.objectFilter.update(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx); PersistenceContext<T> ctx = createCtx(object);
} this.tx.lock(ctx.getObjectRef());
this.objectFilter.update(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx);
} }
} }
@ -85,19 +89,22 @@ public class ObjectDao {
assertNotClosed(); assertNotClosed();
assertNotNull(object); assertNotNull(object);
PersistenceContext<T> ctx = createCtx(object); PersistenceContext<T> ctx = createCtx(object);
ctx.getObjectRef().lock(); this.tx.lock(ctx.getObjectRef().getParent(this.tx));
this.tx.lock(ctx.getObjectRef());
this.objectFilter.remove(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx); this.objectFilter.remove(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx);
} }
public <T> void removeAll(List<T> objects) { public <T> void removeAll(List<T> objects) {
assertNotClosed(); assertNotClosed();
assertNotNull(objects); assertNotNull(objects);
if (!objects.isEmpty()) { if (objects.isEmpty())
for (T object : objects) { return;
PersistenceContext<T> ctx = createCtx(object);
ctx.getObjectRef().lock(); for (T object : objects) {
this.objectFilter.remove(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx); PersistenceContext<T> ctx = createCtx(object);
} this.tx.lock(ctx.getObjectRef().getParent(this.tx));
this.tx.lock(ctx.getObjectRef());
this.objectFilter.remove(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx);
} }
} }
@ -106,31 +113,22 @@ public class ObjectDao {
long removed = 0; long removed = 0;
Set<ObjectRef> refs = new TreeSet<>(); this.tx.lock(typeRef);
typeRef.lock();
refs.add(typeRef);
try {
Set<String> types = this.tx.getMetadataDao().queryTypeSet(typeRef); Set<String> types = this.tx.getMetadataDao().queryTypeSet(typeRef);
for (String type : types) { for (String type : types) {
ObjectRef childTypeRef = typeRef.getChildTypeRef(this.tx, type); ObjectRef childTypeRef = typeRef.getChildTypeRef(this.tx, type);
childTypeRef.lock(); this.tx.lock(childTypeRef);
refs.add(childTypeRef);
Set<String> ids = queryKeySet(childTypeRef, false); Set<String> ids = queryKeySet(childTypeRef, false);
for (String id : ids) { for (String id : ids) {
ObjectRef idRef = childTypeRef.getChildIdRef(this.tx, id); ObjectRef idRef = childTypeRef.getChildIdRef(this.tx, id);
PersistenceContext<T> ctx = createCtx(idRef); PersistenceContext<T> ctx = createCtx(idRef);
ctx.getObjectRef().lock(); this.tx.lock(ctx.getObjectRef());
this.objectFilter.remove(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx); this.objectFilter.remove(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx);
removed++; removed++;
}
}
} finally {
for (ObjectRef ref : refs) {
ref.unlock();
} }
} }
@ -144,20 +142,17 @@ public class ObjectDao {
long removed = 0; long removed = 0;
subTypeRef.lock(); this.tx.lock(subTypeRef);
try {
Set<String> ids = queryKeySet(subTypeRef, false);
for (String id : ids) {
ObjectRef idRef = subTypeRef.getChildIdRef(this.tx, id); Set<String> ids = queryKeySet(subTypeRef, false);
for (String id : ids) {
PersistenceContext<T> ctx = createCtx(idRef); ObjectRef idRef = subTypeRef.getChildIdRef(this.tx, id);
ctx.getObjectRef().lock();
this.objectFilter.remove(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx); PersistenceContext<T> ctx = createCtx(idRef);
removed++; this.tx.lock(ctx.getObjectRef());
} this.objectFilter.remove(ctx.getObjectRef().getType(), ctx.getObjectRef(), ctx);
} finally { removed++;
subTypeRef.unlock();
} }
return removed; return removed;
@ -167,7 +162,8 @@ public class ObjectDao {
assertNotClosed(); assertNotClosed();
assertIsIdRef(objectRef); assertIsIdRef(objectRef);
PersistenceContext<T> ctx = createCtx(objectRef); PersistenceContext<T> ctx = createCtx(objectRef);
ctx.getObjectRef().lock(); this.tx.lock(ctx.getObjectRef().getParent(this.tx));
this.tx.lock(ctx.getObjectRef());
this.objectFilter.remove(objectRef.getType(), ctx.getObjectRef(), ctx); this.objectFilter.remove(objectRef.getType(), ctx.getObjectRef(), ctx);
} }
@ -176,19 +172,15 @@ public class ObjectDao {
assertIsNotIdRef(parentRef); assertIsNotIdRef(parentRef);
assertIsNotRootRef(parentRef); assertIsNotRootRef(parentRef);
parentRef.lock(); this.tx.lock(parentRef);
try {
Set<String> keySet = queryKeySet(parentRef, false); Set<String> keySet = queryKeySet(parentRef, false);
for (String id : keySet) { for (String id : keySet) {
ObjectRef childRef = parentRef.getChildIdRef(this.tx, id); ObjectRef childRef = parentRef.getChildIdRef(this.tx, id);
PersistenceContext<T> ctx = createCtx(childRef); PersistenceContext<T> ctx = createCtx(childRef);
ctx.getObjectRef().lock(); this.tx.lock(ctx.getObjectRef());
this.objectFilter.remove(childRef.getType(), ctx.getObjectRef(), ctx); this.objectFilter.remove(childRef.getType(), ctx.getObjectRef(), ctx);
}
} finally {
parentRef.unlock();
} }
} }
@ -196,27 +188,19 @@ public class ObjectDao {
assertNotClosed(); assertNotClosed();
assertIsIdRef(objectRef); assertIsIdRef(objectRef);
objectRef.lock(); this.tx.lock(objectRef);
try { PersistenceContext<T> ctx = objectRef.<T>createPersistenceContext(this.tx);
PersistenceContext<T> ctx = objectRef.<T>createPersistenceContext(this.tx); return this.fileDao.exists(ctx);
return this.fileDao.exists(ctx);
} finally {
objectRef.unlock();
}
} }
public <T> T queryById(ObjectRef objectRef) { public <T> T queryById(ObjectRef objectRef) {
assertNotClosed(); assertNotClosed();
assertIsIdRef(objectRef); assertIsIdRef(objectRef);
objectRef.lock(); this.tx.lock(objectRef);
try { PersistenceContext<T> ctx = objectRef.<T>createPersistenceContext(this.tx);
PersistenceContext<T> ctx = objectRef.<T>createPersistenceContext(this.tx); this.fileDao.performRead(ctx);
this.fileDao.performRead(ctx); return ctx.getObject();
return ctx.getObject();
} finally {
objectRef.unlock();
}
} }
public <T> List<T> queryAll(ObjectRef parentRef) { public <T> List<T> queryAll(ObjectRef parentRef) {
@ -227,62 +211,45 @@ public class ObjectDao {
assertNotClosed(); assertNotClosed();
assertIsNotIdRef(parentRef); assertIsNotIdRef(parentRef);
parentRef.lock(); this.tx.lock(parentRef);
try {
MetadataDao metadataDao = this.tx.getMetadataDao(); MetadataDao metadataDao = this.tx.getMetadataDao();
Set<String> keySet = metadataDao.queryKeySet(parentRef, reverse); Set<String> keySet = metadataDao.queryKeySet(parentRef, reverse);
int i = 0; int i = 0;
List<T> result = new ArrayList<>(); List<T> result = new ArrayList<>();
for (String id : keySet) { for (String id : keySet) {
ObjectRef childRef = parentRef.getChildIdRef(this.tx, id); ObjectRef childRef = parentRef.getChildIdRef(this.tx, id);
PersistenceContext<T> childCtx = childRef.createPersistenceContext(this.tx); PersistenceContext<T> childCtx = childRef.createPersistenceContext(this.tx);
childCtx.getObjectRef().lock(); this.tx.lock(childCtx.getObjectRef());
try { this.fileDao.performRead(childCtx);
this.fileDao.performRead(childCtx); assertObjectRead(childCtx);
assertObjectRead(childCtx); result.add(childCtx.getObject());
result.add(childCtx.getObject());
} finally {
childCtx.getObjectRef().unlock();
}
if (maxSize != Integer.MAX_VALUE && i >= maxSize) if (maxSize != Integer.MAX_VALUE && i >= maxSize)
break; break;
}
return result;
} finally {
parentRef.unlock();
} }
return result;
} }
private Set<String> queryKeySet(ObjectRef parentRef, boolean reverse) { private Set<String> queryKeySet(ObjectRef parentRef, boolean reverse) {
assertNotClosed(); assertNotClosed();
assertIsNotIdRef(parentRef); assertIsNotIdRef(parentRef);
parentRef.lock(); this.tx.lock(parentRef);
try { MetadataDao metadataDao = this.tx.getMetadataDao();
MetadataDao metadataDao = this.tx.getMetadataDao(); return metadataDao.queryKeySet(parentRef, reverse);
return metadataDao.queryKeySet(parentRef, reverse);
} finally {
parentRef.unlock();
}
} }
public long querySize(ObjectRef parentRef) { public long querySize(ObjectRef parentRef) {
assertNotClosed(); assertNotClosed();
assertIsNotIdRef(parentRef); assertIsNotIdRef(parentRef);
parentRef.lock(); this.tx.lock(parentRef);
try { MetadataDao metadataDao = this.tx.getMetadataDao();
MetadataDao metadataDao = this.tx.getMetadataDao(); return metadataDao.querySize(parentRef);
return metadataDao.querySize(parentRef);
} finally {
parentRef.unlock();
}
} }
public <T> PersistenceContext<T> createCtx(T object) { public <T> PersistenceContext<T> createCtx(T object) {

View File

@ -15,6 +15,8 @@
*/ */
package li.strolch.xmlpers.api; package li.strolch.xmlpers.api;
import li.strolch.xmlpers.objref.LockableObject;
/** /**
* @author Robert von Burg <eitch@eitchnet.ch> * @author Robert von Burg <eitch@eitchnet.ch>
*/ */
@ -61,4 +63,6 @@ public interface PersistenceTransaction extends AutoCloseable {
public FileDao getFileDao(); public FileDao getFileDao();
public PersistenceManager getManager(); public PersistenceManager getManager();
void lock(LockableObject lockableObject);
} }

View File

@ -15,6 +15,9 @@
*/ */
package li.strolch.xmlpers.impl; package li.strolch.xmlpers.impl;
import static li.strolch.utils.helper.PropertiesHelper.*;
import static li.strolch.xmlpers.api.PersistenceConstants.*;
import java.io.File; import java.io.File;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.text.MessageFormat; import java.text.MessageFormat;
@ -49,25 +52,14 @@ public class DefaultPersistenceManager implements PersistenceManager {
String context = DefaultPersistenceManager.class.getSimpleName(); String context = DefaultPersistenceManager.class.getSimpleName();
// get properties // get properties
boolean verbose = PropertiesHelper boolean verbose = getPropertyBool(properties, context, PROP_VERBOSE, Boolean.FALSE);
.getPropertyBool(properties, context, PersistenceConstants.PROP_VERBOSE, Boolean.FALSE); IoMode ioMode = IoMode.valueOf(getProperty(properties, context, PROP_XML_IO_MOD, IoMode.DOM.name()));
String ioModeS = PropertiesHelper long lockTime = getPropertyLong(properties, context, PROP_LOCK_TIME_MILLIS, LockableObject.getLockTime());
.getProperty(properties, context, PersistenceConstants.PROP_XML_IO_MOD, IoMode.DOM.name()); String basePath = getProperty(properties, context, PROP_BASEPATH, null);
IoMode ioMode = IoMode.valueOf(ioModeS);
long lockTime = PropertiesHelper
.getPropertyLong(properties, context, PersistenceConstants.PROP_LOCK_TIME_MILLIS, 10000L);
String basePath = PropertiesHelper.getProperty(properties, context, PersistenceConstants.PROP_BASEPATH, null);
// set lock time on LockableObject // set lock time on LockableObject
try { if (lockTime != LockableObject.getLockTime())
Field lockTimeField = LockableObject.class.getDeclaredField("tryLockTime");//$NON-NLS-1$ LockableObject.setTryLockTime(lockTime);
lockTimeField.setAccessible(true);
lockTimeField.setLong(null, lockTime);
logger.info("Using a max lock acquire time of " + StringHelper
.formatMillisecondsDuration(lockTime)); //$NON-NLS-1$
} catch (SecurityException | NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException("Failed to configure tryLockTime on LockableObject!", e); //$NON-NLS-1$
}
// validate base path exists and is writable // validate base path exists and is writable
File basePathF = new File(basePath).getAbsoluteFile(); File basePathF = new File(basePath).getAbsoluteFile();

View File

@ -22,6 +22,7 @@ import java.util.*;
import li.strolch.utils.objectfilter.ObjectFilter; import li.strolch.utils.objectfilter.ObjectFilter;
import li.strolch.xmlpers.api.*; import li.strolch.xmlpers.api.*;
import li.strolch.xmlpers.objref.LockableObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -48,6 +49,8 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction {
private Date startTimeDate; private Date startTimeDate;
private TransactionResult txResult; private TransactionResult txResult;
private Set<LockableObject> lockedObjects;
public DefaultPersistenceTransaction(PersistenceManager manager, IoMode ioMode, boolean verbose) { public DefaultPersistenceTransaction(PersistenceManager manager, IoMode ioMode, boolean verbose) {
this.startTime = System.nanoTime(); this.startTime = System.nanoTime();
this.startTimeDate = new Date(); this.startTimeDate = new Date();
@ -60,6 +63,7 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction {
this.closeStrategy = TransactionCloseStrategy.COMMIT; this.closeStrategy = TransactionCloseStrategy.COMMIT;
this.state = TransactionState.OPEN; this.state = TransactionState.OPEN;
this.lockedObjects = new HashSet<>();
} }
@Override @Override
@ -113,27 +117,31 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction {
@Override @Override
public void autoCloseableRollback() { public void autoCloseableRollback() {
long start = System.nanoTime(); try {
if (this.state == TransactionState.COMMITTED) long start = System.nanoTime();
throw new IllegalStateException("Transaction has already been committed!"); //$NON-NLS-1$ if (this.state == TransactionState.COMMITTED)
throw new IllegalStateException("Transaction has already been committed!"); //$NON-NLS-1$
if (this.state != TransactionState.ROLLED_BACK) { if (this.state != TransactionState.ROLLED_BACK) {
unlockObjectRefs(); this.state = TransactionState.ROLLED_BACK;
this.state = TransactionState.ROLLED_BACK;
this.objectFilter.clearCache();
long end = System.nanoTime(); long end = System.nanoTime();
long txDuration = end - this.startTime; long txDuration = end - this.startTime;
long closeDuration = end - start; long closeDuration = end - start;
if (this.txResult != null) { if (this.txResult != null) {
this.txResult.clear(); this.txResult.clear();
this.txResult.setState(this.state); this.txResult.setState(this.state);
this.txResult.setStartTime(this.startTimeDate); this.txResult.setStartTime(this.startTimeDate);
this.txResult.setTxDuration(txDuration); this.txResult.setTxDuration(txDuration);
this.txResult.setCloseDuration(closeDuration); this.txResult.setCloseDuration(closeDuration);
this.txResult.setModificationByKey(Collections.emptyMap()); this.txResult.setModificationByKey(Collections.emptyMap());
}
} }
} finally {
// clean up
this.objectFilter.clearCache();
releaseAllLocks();
} }
} }
@ -242,10 +250,9 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction {
this.txResult.setFailCause(e); this.txResult.setFailCause(e);
} finally { } finally {
// clean up // clean up
unlockObjectRefs();
this.objectFilter.clearCache(); this.objectFilter.clearCache();
releaseAllLocks();
} }
long end = System.nanoTime(); long end = System.nanoTime();
@ -274,16 +281,21 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction {
} }
} }
@SuppressWarnings("rawtypes") private void releaseAllLocks() {
private void unlockObjectRefs() { for (LockableObject lockedObject : this.lockedObjects) {
List<PersistenceContext> lockedObjects = this.objectFilter.getAll(PersistenceContext.class); lockedObject.releaseLock();
for (PersistenceContext lockedObject : lockedObjects) {
lockedObject.getObjectRef().unlock();
} }
this.lockedObjects.clear();
} }
@Override @Override
public boolean isOpen() { public boolean isOpen() {
return this.state == TransactionState.OPEN; return this.state == TransactionState.OPEN;
} }
@Override
public void lock(LockableObject lockableObject) {
lockableObject.lock();
this.lockedObjects.add(lockableObject);
}
} }

View File

@ -15,7 +15,12 @@
*/ */
package li.strolch.xmlpers.objref; package li.strolch.xmlpers.objref;
import static java.lang.Thread.currentThread;
import static java.text.MessageFormat.format;
import static li.strolch.utils.helper.StringHelper.formatMillisecondsDuration;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -29,12 +34,22 @@ public class LockableObject {
private static final Logger logger = LoggerFactory.getLogger(LockableObject.class); private static final Logger logger = LoggerFactory.getLogger(LockableObject.class);
private static long tryLockTime = 10000L; private static long tryLockTime = 10000L;
private final ReentrantLock lock; public static void setTryLockTime(long tryLockTime) {
LockableObject.tryLockTime = tryLockTime;
}
public LockableObject() { private final ReentrantLock lock;
protected final String name;
public LockableObject(String name) {
this.name = name;
this.lock = new ReentrantLock(true); this.lock = new ReentrantLock(true);
} }
public String getName() {
return this.name;
}
public static long getLockTime() { public static long getLockTime() {
return tryLockTime; return tryLockTime;
} }
@ -43,11 +58,32 @@ public class LockableObject {
* @see java.util.concurrent.locks.ReentrantLock#tryLock(long, TimeUnit) * @see java.util.concurrent.locks.ReentrantLock#tryLock(long, TimeUnit)
*/ */
public void lock() { public void lock() {
// don't lock multiple times
if (this.lock.isHeldByCurrentThread() && this.lock.isLocked())
return;
try { try {
if (!this.lock.tryLock(tryLockTime, TimeUnit.MILLISECONDS)) { if (!this.lock.tryLock(tryLockTime, TimeUnit.MILLISECONDS)) {
String msg = "Failed to acquire lock after {0} for {1}"; //$NON-NLS-1$ String msg = "Thread {0} failed to acquire lock after {1} for {2}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, StringHelper.formatMillisecondsDuration(tryLockTime), toString()); msg = format(msg, currentThread().getName(), formatMillisecondsDuration(tryLockTime), this);
try {
logger.error(msg);
logger.error("Listing all active threads: ");
Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
for (Thread thread : allStackTraces.keySet()) {
StackTraceElement[] trace = allStackTraces.get(thread);
StringBuilder sb = new StringBuilder();
for (StackTraceElement traceElement : trace)
sb.append("\n\tat ").append(traceElement);
logger.error("\nThread " + thread.getName() + ":\n" + sb.toString() + "\n");
}
} catch (Exception e) {
logger.error("Failed to log active threads: " + e.getMessage(), e);
}
throw new XmlPersistenceException(msg); throw new XmlPersistenceException(msg);
} }
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
@ -60,9 +96,11 @@ public class LockableObject {
/** /**
* @see java.util.concurrent.locks.ReentrantLock#unlock() * @see java.util.concurrent.locks.ReentrantLock#unlock()
*/ */
public void unlock() { public void releaseLock() {
this.lock.unlock(); while (this.lock.isHeldByCurrentThread() && this.lock.isLocked()) {
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
logger.debug("unlocking " + toString()); //$NON-NLS-1$ logger.debug("unlocking " + toString()); //$NON-NLS-1$
this.lock.unlock();
}
} }
} }

View File

@ -24,14 +24,8 @@ import li.strolch.xmlpers.impl.PathBuilder;
public abstract class ObjectRef extends LockableObject implements Comparable<ObjectRef> { public abstract class ObjectRef extends LockableObject implements Comparable<ObjectRef> {
protected final String name;
protected ObjectRef(String name) { protected ObjectRef(String name) {
this.name = name; super(name);
}
public String getName() {
return this.name;
} }
public File getPath(PathBuilder pathBuilder) { public File getPath(PathBuilder pathBuilder) {

View File

@ -15,10 +15,14 @@
*/ */
package li.strolch.xmlpers.test; package li.strolch.xmlpers.test;
import static li.strolch.utils.helper.SystemHelper.isLinux;
import static li.strolch.utils.helper.SystemHelper.isWindows;
import java.io.File; import java.io.File;
import java.util.Properties; import java.util.Properties;
import li.strolch.utils.helper.FileHelper; import li.strolch.utils.helper.FileHelper;
import li.strolch.utils.helper.SystemHelper;
import li.strolch.xmlpers.api.IoMode; import li.strolch.xmlpers.api.IoMode;
import li.strolch.xmlpers.api.PersistenceConstants; import li.strolch.xmlpers.api.PersistenceConstants;
import li.strolch.xmlpers.api.PersistenceManager; import li.strolch.xmlpers.api.PersistenceManager;
@ -42,8 +46,14 @@ public abstract class AbstractPersistenceTest {
File file = new File(path).getAbsoluteFile(); File file = new File(path).getAbsoluteFile();
File parent = file.getParentFile(); File parent = file.getParentFile();
if (!parent.getAbsolutePath().endsWith("/target/db")) if (isWindows()) {
throw new RuntimeException("Bad parent! Must be /target/db/: " + parent); if (!parent.getAbsolutePath().endsWith("\\target\\db"))
throw new RuntimeException("Bad parent! Must be \\target\\db: " + parent);
} else {
if (!parent.getAbsolutePath().endsWith("/target/db"))
throw new RuntimeException("Bad parent! Must be /target/db: " + parent);
}
if (!parent.exists() && !parent.mkdirs()) if (!parent.exists() && !parent.mkdirs())
throw new RuntimeException("Failed to create path " + parent); throw new RuntimeException("Failed to create path " + parent);
@ -51,7 +61,7 @@ public abstract class AbstractPersistenceTest {
if (!FileHelper.deleteFiles(file.listFiles(), true)) if (!FileHelper.deleteFiles(file.listFiles(), true))
throw new RuntimeException("Could not clean up path " + file.getAbsolutePath()); throw new RuntimeException("Could not clean up path " + file.getAbsolutePath());
if (!file.exists() && !file.mkdir()) if (!file.exists() && !file.mkdirs())
throw new RuntimeException("Failed to create path " + file); throw new RuntimeException("Failed to create path " + file);
File domFile = new File(file, IoMode.DOM.name()); File domFile = new File(file, IoMode.DOM.name());

View File

@ -57,7 +57,7 @@ public class LockingTest extends AbstractPersistenceTest {
properties.setProperty(PersistenceConstants.PROP_LOCK_TIME_MILLIS, Long.toString(500L)); properties.setProperty(PersistenceConstants.PROP_LOCK_TIME_MILLIS, Long.toString(500L));
setup(properties); setup(properties);
this.waitForWorkersTime = LockableObject.getLockTime() + getWaitForWorkersTime() + 300L; this.waitForWorkersTime = LockableObject.getLockTime() + 300L;
} }
@Test @Test
@ -112,15 +112,18 @@ public class LockingTest extends AbstractPersistenceTest {
logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$
} }
// create resource which is to be updated int nrOfSuccess;
try (PersistenceTransaction tx = this.persistenceManager.openTx()) { try (PersistenceTransaction tx = this.persistenceManager.openTx()) {
// create resource which is to be updated
MyModel resource = createResource(resourceId); MyModel resource = createResource(resourceId);
tx.getObjectDao().add(resource); tx.getObjectDao().add(resource);
// and before closing the TX, run the workers, which should fail as we are still holding the locks
nrOfSuccess = runWorkers(workers);
} }
int nrOfSuccess = runWorkers(workers); assertEquals("Only one thread should be able to perform the TX!", 0, nrOfSuccess); //$NON-NLS-1$
assertEquals("Only one thread should be able to perform the TX!", 1, nrOfSuccess); //$NON-NLS-1$
} }
private int runWorkers(List<? extends AbstractWorker> workers) throws InterruptedException { private int runWorkers(List<? extends AbstractWorker> workers) throws InterruptedException {
@ -128,7 +131,7 @@ public class LockingTest extends AbstractPersistenceTest {
setRun(true); setRun(true);
for (AbstractWorker worker : workers) { for (AbstractWorker worker : workers) {
worker.join(getWaitForWorkersTime() + 2000L); worker.join(getWaitForWorkersTime() + 5000L);
} }
int nrOfSuccess = 0; int nrOfSuccess = 0;
@ -177,16 +180,9 @@ public class LockingTest extends AbstractPersistenceTest {
logger.info("Starting work..."); //$NON-NLS-1$ logger.info("Starting work..."); //$NON-NLS-1$
try (PersistenceTransaction tx = LockingTest.this.persistenceManager.openTx()) { try (PersistenceTransaction tx = LockingTest.this.persistenceManager.openTx()) {
doWork(tx); doWork(tx);
try {
Thread.sleep(getWaitForWorkersTime());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.success = true;
} }
this.success = true;
logger.info("Work completed."); //$NON-NLS-1$ logger.info("Work completed."); //$NON-NLS-1$
} }