mirror of
https://github.com/usatiuk/dhfs.git
synced 2025-10-28 20:47:49 +01:00
working object edit
This commit is contained in:
@@ -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 {
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -43,7 +43,7 @@ public class TxRecord {
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user