working object edit

This commit is contained in:
2024-12-02 22:30:52 +01:00
parent 7bb509024f
commit 70b8105451
9 changed files with 109 additions and 42 deletions

View File

@@ -1,4 +1,6 @@
package com.usatiuk.dhfs.objects;
public record JObjectKey(String name) {
import java.io.Serializable;
public record JObjectKey(String name) implements Serializable {
}

View File

@@ -188,6 +188,13 @@ public class JObjectManager {
if (current.lastWriteTx > tx.getId()) {
throw new IllegalStateException("Transaction race");
}
var newWrapper = new JDataWrapper<>(record.copy().wrapped());
newWrapper.lock.writeLock().lock();
if (!_objects.replace(record.copy().wrapped().getKey(), current, newWrapper)) {
throw new IllegalStateException("Object changed during transaction");
}
toUnlock.add(newWrapper.lock.writeLock()::unlock);
} else if (record instanceof TxRecord.TxObjectRecordNew<?> created) {
var wrapper = new JDataWrapper<>(created.created());
wrapper.lock.writeLock().lock();
@@ -204,8 +211,7 @@ public class JObjectManager {
for (var record : toFlush) {
Log.trace("Flushing " + record.toString());
if (!record.copy().isModified())
continue;
assert record.copy().isModified();
var obj = record.copy().wrapped();
var key = obj.getKey();

View File

@@ -43,7 +43,7 @@ public class TxRecord {
@Override
public boolean isModified() {
return false;
return true;
}
};
}

View File

@@ -0,0 +1,15 @@
package com.usatiuk.dhfs.objects;
import java.util.concurrent.Callable;
public abstract class Just {
public static void run(Callable<?> callable) {
new Thread(() -> {
try {
callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}).start();
}
}

View File

@@ -40,51 +40,92 @@ public class ObjectsTest {
}
}
@Test
void editObject() {
{
txm.begin();
var newParent = alloc.create(Parent.class, new JObjectKey("Parent3"));
newParent.setLastName("John");
curTx.putObject(newParent);
txm.commit();
}
{
txm.begin();
var parent = curTx.getObject(Parent.class, new JObjectKey("Parent3"), LockingStrategy.OPTIMISTIC).orElse(null);
Assertions.assertEquals("John", parent.getLastName());
parent.setLastName("John2");
txm.commit();
}
{
txm.begin();
var parent = curTx.getObject(Parent.class, new JObjectKey("Parent3"), LockingStrategy.WRITE).orElse(null);
Assertions.assertEquals("John2", parent.getLastName());
parent.setLastName("John3");
txm.commit();
}
{
txm.begin();
var parent = curTx.getObject(Parent.class, new JObjectKey("Parent3"), LockingStrategy.READ_ONLY).orElse(null);
Assertions.assertEquals("John3", parent.getLastName());
txm.commit();
}
}
@Test
void createObjectConflict() throws InterruptedException {
AtomicBoolean thread1Failed = new AtomicBoolean(true);
AtomicBoolean thread2Failed = new AtomicBoolean(true);
var signal = new Semaphore(0);
var signalFin = new Semaphore(2);
new Thread(() -> {
Log.warn("Thread 1");
txm.begin();
var newParent = alloc.create(Parent.class, new JObjectKey("Parent2"));
newParent.setLastName("John");
curTx.putObject(newParent);
try {
signal.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Log.warn("Thread 1 commit");
txm.commit();
thread1Failed.set(false);
}).start();
new Thread(() -> {
Log.warn("Thread 2");
txm.begin();
var newParent = alloc.create(Parent.class, new JObjectKey("Parent2"));
newParent.setLastName("John2");
curTx.putObject(newParent);
Just.run(() -> {
try {
signalFin.acquire();
Log.warn("Thread 1");
txm.begin();
var newParent = alloc.create(Parent.class, new JObjectKey("Parent2"));
newParent.setLastName("John");
curTx.putObject(newParent);
signal.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
Log.warn("Thread 1 commit");
txm.commit();
thread1Failed.set(false);
signal.release();
return null;
} finally {
signalFin.release();
}
Log.warn("Thread 2 commit");
txm.commit();
thread2Failed.set(false);
}).start();
});
Just.run(() -> {
try {
signalFin.acquire();
Log.warn("Thread 2");
txm.begin();
var newParent = alloc.create(Parent.class, new JObjectKey("Parent2"));
newParent.setLastName("John2");
curTx.putObject(newParent);
signal.acquire();
Log.warn("Thread 2 commit");
txm.commit();
thread2Failed.set(false);
signal.release();
return null;
} finally {
signalFin.release();
}
});
signal.release(2);
Thread.sleep(500);
signalFin.acquire(2);
txm.begin();
var got = curTx.getObject(Parent.class, new JObjectKey("Parent2"), LockingStrategy.READ_ONLY).orElse(null);
txm.commit();
if (!thread1Failed.get()) {
Assertions.assertTrue(thread2Failed.get());
@@ -94,6 +135,7 @@ public class ObjectsTest {
} else {
Assertions.fail("No thread succeeded");
}
}
// @Test

View File

@@ -4,9 +4,11 @@ import com.usatiuk.dhfs.objects.JData;
import com.usatiuk.dhfs.objects.ObjectAllocator;
import lombok.Getter;
public abstract class ChangeTrackerBase<T extends JData> implements ObjectAllocator.ChangeTrackingJData<T> {
import java.io.Serializable;
public abstract class ChangeTrackerBase<T extends JData> implements ObjectAllocator.ChangeTrackingJData<T>, Serializable {
@Getter
private boolean _modified = false;
private transient boolean _modified = false;
protected void onChange() {
_modified = true;

View File

@@ -16,7 +16,7 @@ public class KidDataCT extends ChangeTrackerBase<Kid> implements Kid {
onChange();
}
public KidDataCT(KidDataNormal normal) {
public KidDataCT(Kid normal) {
_key = normal.getKey();
_name = normal.getName();
}

View File

@@ -22,7 +22,7 @@ public class ParentDataCT extends ChangeTrackerBase<Parent> implements Parent {
onChange();
}
public ParentDataCT(ParentDataNormal normal) {
public ParentDataCT(Parent normal) {
_name = normal.getKey();
_kidKey = normal.getKidKey();
_lastName = normal.getLastName();

View File

@@ -22,13 +22,13 @@ public class TestObjectAllocator implements ObjectAllocator {
@Override
public <T extends JData> ChangeTrackingJData<T> copy(T obj) {
if (obj instanceof ChangeTrackerBase<?>) {
throw new IllegalArgumentException("Cannot copy a ChangeTrackerBase object");
}
// if (obj instanceof ChangeTrackerBase<?>) {
// throw new IllegalArgumentException("Cannot copy a ChangeTrackerBase object");
// }
return switch (obj) {
case KidDataNormal kid -> (ChangeTrackingJData<T>) new KidDataCT(kid);
case ParentDataNormal parent -> (ChangeTrackingJData<T>) new ParentDataCT(parent);
case Kid kid -> (ChangeTrackingJData<T>) new KidDataCT(kid);
case Parent parent -> (ChangeTrackingJData<T>) new ParentDataCT(parent);
default -> throw new IllegalStateException("Unexpected value: " + obj);
};
}