use of org.cojen.tupl.Sorter in project Tupl by cojen.
the class IndexBackfill method addBatch.
/**
* @return false if closed
*/
private boolean addBatch(byte[][] primaryBatch, byte[][] secondaryBatch, int length) throws IOException {
Sorter sorter = mSorter;
if (sorter == null) {
// Closed.
return false;
}
for (int i = 0; i < length; i += 2) {
encode(primaryBatch[i], primaryBatch[i + 1], secondaryBatch, i);
}
sorter.addBatch(secondaryBatch, 0, length >> 1);
if (mSorter != null) {
// Still in use.
return true;
}
// Closed. Clean up the mess.
try {
sorter.reset();
} catch (Exception e) {
// Ignore.
}
return false;
}
use of org.cojen.tupl.Sorter in project Tupl by cojen.
the class IndexBackfill method doRun.
/**
* @return false if closed
*/
private boolean doRun() throws IOException {
var primaryBatch = new byte[100 * 2][];
var secondaryBatch = new byte[primaryBatch.length][];
Transaction txn = mRowStore.mDatabase.newTransaction();
txn.lockMode(LockMode.READ_COMMITTED);
txn.lockTimeout(-1, null);
txn.durabilityMode(DurabilityMode.NO_REDO);
try (Cursor c = mManager.mPrimaryIndex.newCursor(txn)) {
c.autoload(mAutoload);
c.first();
int length = 0;
while (true) {
byte[] key = c.key();
if (key == null) {
if (length > 0) {
if (!addBatch(primaryBatch, secondaryBatch, length)) {
return false;
}
}
break;
}
primaryBatch[length++] = key;
primaryBatch[length++] = c.value();
if (length >= primaryBatch.length) {
if (!addBatch(primaryBatch, secondaryBatch, length)) {
return false;
}
length = 0;
}
c.next();
}
}
Sorter sorter = mSorter;
if (sorter == null) {
return false;
}
Index newIndex;
try {
newIndex = sorter.finish();
} catch (InterruptedIOException e) {
if (mSorter != null) {
throw e;
}
return false;
}
Index deleted;
synchronized (this) {
deleted = mDeleted;
if (deleted == null) {
return false;
}
// Lock all triggers to prevent concurrent modifications, and change their behavior
// now that backfill is finishing.
withTriggerLock(() -> {
mNewSecondaryIndex = newIndex;
});
}
txn.lockMode(LockMode.UPGRADABLE_READ);
try (Cursor secondaryCursor = mSecondaryIndex.newCursor(txn);
// secondary index entry suffices.
Cursor deletedCursor = deleted.newCursor(Transaction.BOGUS)) {
secondaryCursor.first();
for (byte[] key; (key = secondaryCursor.key()) != null; secondaryCursor.next()) {
deletedCursor.findNearby(key);
if (deletedCursor.value() == null) {
newIndex.store(txn, key, secondaryCursor.value());
} else {
deletedCursor.delete();
}
secondaryCursor.commit(null);
}
}
// deadlocks. Proper lock acquisition order is mSecondaryIndex and then mDeleted.
try (Cursor deletedCursor = deleted.newCursor(Transaction.BOGUS)) {
deletedCursor.first();
for (byte[] key; (key = deletedCursor.key()) != null; deletedCursor.next()) {
newIndex.lockUpgradable(txn, key);
try {
deletedCursor.load();
if (deletedCursor.value() != null) {
newIndex.store(txn, key, null);
deletedCursor.store(null);
txn.commit();
}
} finally {
txn.exit();
}
}
}
// Swap the fully populated new secondary index with the original, which is now
// empty. As a side-effect, the contents of the new secondary index which was temporary
// are now associated with a real index, and the original secondary is associated with
// the temporary index.
withTriggerLock(() -> {
try {
mRowStore.mDatabase.rootSwap(newIndex, mSecondaryIndex);
} catch (IOException e) {
throw RowUtils.rethrow(e);
}
// Backfill is finished, so stop tracking changes.
mDeleted = null;
mNewSecondaryIndex = null;
});
try {
// Eagerly delete the temporary index.
mRowStore.mDatabase.deleteIndex(newIndex).run();
} catch (Exception e) {
// Ignore.
}
try {
// Eagerly delete the temporary index.
mRowStore.mDatabase.deleteIndex(deleted).run();
} catch (Exception e) {
// Ignore.
}
return true;
}
use of org.cojen.tupl.Sorter in project Tupl by cojen.
the class IndexBackfill method unused.
private void unused(Trigger<R> trigger, boolean close) {
Sorter sorter;
Index deleted;
synchronized (this) {
if (mTriggers == null) {
return;
}
mTriggers.remove(trigger);
if (!close && !mTriggers.isEmpty()) {
return;
}
mTriggers = null;
sorter = mSorter;
mSorter = null;
deleted = mDeleted;
mDeleted = null;
}
if (sorter == null && deleted == null) {
return;
}
CoreDatabase db = mRowStore.mDatabase;
db.removeRedoListener(this);
Runner.start(() -> {
Runnable deleteTask = null;
if (deleted != null) {
try {
deleteTask = db.deleteIndex(deleted);
} catch (Exception e) {
// Ignore.
}
}
if (sorter != null) {
try {
sorter.reset();
} catch (Exception e) {
// Ignore.
}
}
if (deleteTask != null) {
deleteTask.run();
}
});
}
Aggregations