diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java index 08986de050..81d537d7be 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java +++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java @@ -3080,9 +3080,33 @@ public String getBackupDatabasePath() { * @throws TskCoreException */ public CaseDbTransaction beginTransaction() throws TskCoreException { - return new CaseDbTransaction(this); + return new CaseDbTransaction(this, false); } + /** + *

Create a new transaction on the case database. The transaction object + * that is returned can be passed to methods that take a CaseDbTransaction. + * The caller is responsible for calling either commit() or rollback() on + * the transaction object.

+ * + *

Note that this beginning the transaction also acquires the single user + * case read lock, which will be automatically released when the + * transaction is closed.

+ * + *

WARNING: This API should only be used if the transaction is + * guaranteed to only ever perform reads and no updates to the database. + * Undefined behavior can occur if this API is used with database updates.

+ * + * @return A CaseDbTransaction object. + * + * @throws TskCoreException + */ + @Beta + public CaseDbTransaction beginReadOnlyTransaction() throws TskCoreException { + return new CaseDbTransaction(this, true); + } + + /** * Gets the case database name. * @@ -14458,6 +14482,7 @@ void executeCommand(DbCommand command) throws SQLException { public static final class CaseDbTransaction { private final CaseDbConnection connection; + private final boolean readOnlyTransaction; private SleuthkitCase sleuthkitCase; /* This class can store information about what was @@ -14474,15 +14499,32 @@ public static final class CaseDbTransaction { private List deletedOsAccountObjectIds = new ArrayList<>(); private List deletedResultObjectIds = new ArrayList<>(); + // Keep track of which threads have connections to debug deadlocks private static Set threadsWithOpenTransaction = new HashSet<>(); private static final Object threadsWithOpenTransactionLock = new Object(); - private CaseDbTransaction(SleuthkitCase sleuthkitCase) throws TskCoreException { + /** + * Constructor for a case database transaction. + * + * @param sleuthkitCase The TSK case. + * @param readOnlyTransaction True if the transaction will not make any + * writes to the database and therefore does + * not need the write lock. + * + * @throws TskCoreException + */ + private CaseDbTransaction(SleuthkitCase sleuthkitCase, boolean readOnlyTransaction) throws TskCoreException { this.sleuthkitCase = sleuthkitCase; + this.readOnlyTransaction = readOnlyTransaction; - sleuthkitCase.acquireSingleUserCaseWriteLock(); + if (readOnlyTransaction) { + sleuthkitCase.acquireSingleUserCaseReadLock(); + } else { + sleuthkitCase.acquireSingleUserCaseWriteLock(); + } + this.connection = sleuthkitCase.getConnection(); try { synchronized (threadsWithOpenTransactionLock) { @@ -14490,7 +14532,11 @@ private CaseDbTransaction(SleuthkitCase sleuthkitCase) throws TskCoreException { threadsWithOpenTransaction.add(Thread.currentThread().getId()); } } catch (SQLException ex) { - sleuthkitCase.releaseSingleUserCaseWriteLock(); + if (readOnlyTransaction) { + sleuthkitCase.releaseSingleUserCaseReadLock(); + } else { + sleuthkitCase.releaseSingleUserCaseWriteLock(); + } throw new TskCoreException("Failed to create transaction on case database", ex); } @@ -14671,7 +14717,11 @@ public void rollback() throws TskCoreException { */ void close() { this.connection.close(); - sleuthkitCase.releaseSingleUserCaseWriteLock(); + if (readOnlyTransaction) { + sleuthkitCase.releaseSingleUserCaseReadLock(); + } else { + sleuthkitCase.releaseSingleUserCaseWriteLock(); + } synchronized (threadsWithOpenTransactionLock) { threadsWithOpenTransaction.remove(Thread.currentThread().getId()); }