/** * It might happen that the locked record is locked by a zombie or by us which is ok (the latter requires us to reset * lock to us). * * @param lockedRecord * @return true if the lock could be acquired */ private boolean checkLockedRecord(StateRecord lockedRecord) { // It is locked bu us if(lockedRecord.getLockedByRecordId() == recordId ) return true; StateRecord currentOwner = ledger.getLockOwnerOf(lockedRecord); // we can acquire the lock - it is dead if( currentOwner == null ) return true; // valid lock if( currentOwner.state.isPending() ) return false; // This section process data structure errors than can opccur due to unhandled exceptions, data corruption and like // in a safe manner: // The locker is bad? if( currentOwner.state == ItemState.DECLINED || currentOwner.state == ItemState.DISCARDED ) return true; // report inconsistent data. We are not 100% sure this lock could be reacquired, further exploration // needed. As for now, we can't lock it. return false; }
@Override public StateRecord getLockOwnerOf(StateRecord rc) { StateRecord cached = getFromCacheById(rc.getLockedByRecordId()); if (cached != null) { return cached; } StateRecord sr = protect(() -> dbPool.execute(db -> { try (ResultSet rs = db.queryRow("SELECT * FROM ledger WHERE id = ? limit 1", rc.getLockedByRecordId())) { if (rs == null) return null; StateRecord r = new StateRecord(this, rs); putToCache(r); return r; } catch (Exception e) { e.printStackTrace(); throw e; } }) ); if (sr != null && sr.isExpired()) { sr.destroy(); return null; } return sr; }
statement.setLong(3, StateRecord.unixTime(stateRecord.getCreatedAt())); statement.setLong(4, StateRecord.unixTime(stateRecord.getExpiresAt())); statement.setLong(5, stateRecord.getLockedByRecordId()); statement.executeUpdate(); try (ResultSet keys = statement.getGeneratedKeys()) { stateRecord.getState().ordinal(), StateRecord.unixTime(stateRecord.getExpiresAt()), stateRecord.getLockedByRecordId(), stateRecord.getRecordId() );
statement.setLong(3, StateRecord.unixTime(stateRecord.getCreatedAt())); statement.setLong(4, StateRecord.unixTime(stateRecord.getExpiresAt())); statement.setLong(5, stateRecord.getLockedByRecordId()); db.updateWithStatement(statement); try (ResultSet keys = statement.getGeneratedKeys()) { stateRecord.getState().ordinal(), StateRecord.unixTime(stateRecord.getExpiresAt()), stateRecord.getLockedByRecordId(), stateRecord.getRecordId() );
protected void assertSameRecords(StateRecord r, StateRecord r1) { assertEquals(r.getId(), r1.getId()); assertEquals(r.getState(), r1.getState()); assertAlmostSame(r.getCreatedAt(), r1.getCreatedAt()); assertEquals(r.getRecordId(), r1.getRecordId()); assertEquals(r.getLockedByRecordId(), r1.getLockedByRecordId()); }
Set<HashId> idsToRemove = new HashSet<>(); for (StateRecord r : recordsToSanitate.values()) { if (r.getLockedByRecordId() == record.getRecordId()) { try { itemLock.synchronize(r.getId(), lock -> {
@Test public void checkLockOwner() throws Exception { ledger.enableCache(true); StateRecord existing = ledger.findOrCreate(HashId.createRandom()); existing.approve(); StateRecord r = ledger.findOrCreate(HashId.createRandom()); StateRecord r1 = r.lockToRevoke(existing.getId()); existing.reload(); r.reload(); assertSameRecords(existing, r1); assertEquals(ItemState.LOCKED, existing.getState()); assertEquals(r.getRecordId(), existing.getLockedByRecordId()); StateRecord currentOwner = ledger.getLockOwnerOf(existing); System.out.println("existing: " + existing.getId()); System.out.println("locker: " + r.getId()); System.out.println("locked: " + r1.getId()); System.out.println("currentOwner: " + currentOwner.getId()); assertSameRecords(r, currentOwner); }
@Test public void lockForRevoking() throws Exception { ledger.enableCache(true); StateRecord existing = ledger.findOrCreate(HashId.createRandom()); existing.approve(); StateRecord existing2 = ledger.findOrCreate(HashId.createRandom()); existing2.approve(); StateRecord r = ledger.findOrCreate(HashId.createRandom()); StateRecord r1 = r.lockToRevoke(existing.getId()); existing.reload(); r.reload(); assertSameRecords(existing, r1); assertEquals(ItemState.LOCKED, existing.getState()); assertEquals(r.getRecordId(), existing.getLockedByRecordId()); StateRecord r2 = r.lockToRevoke(existing.getId()); existing.reload(); r.reload(); assertSameRecords(existing, r1); assertSameRecords(existing, r2); assertSame(r1, r2); assertEquals(ItemState.LOCKED, existing.getState()); assertEquals(r.getRecordId(), existing.getLockedByRecordId()); StateRecord r3 = r.lockToRevoke(existing2.getId()); existing2.reload(); assertSameRecords(existing2, r3); assertEquals(ItemState.LOCKED, existing2.getState()); assertEquals(r.getRecordId(), existing2.getLockedByRecordId()); }
@Test public void lockForRevoking() throws Exception { ledger.enableCache(true); StateRecord existing = ledger.findOrCreate(HashId.createRandom()); existing.approve(); StateRecord existing2 = ledger.findOrCreate(HashId.createRandom()); existing2.approve(); StateRecord r = ledger.findOrCreate(HashId.createRandom()); StateRecord r1 = r.lockToRevoke(existing.getId()); existing.reload(); r.reload(); assertSameRecords(existing, r1); assertEquals(ItemState.LOCKED, existing.getState()); assertEquals(r.getRecordId(), existing.getLockedByRecordId()); // we lock again the same record, everything should be still ok: StateRecord r2 = r.lockToRevoke(existing.getId()); assertNotNull(r2); existing.reload(); r.reload(); assertSameRecords(existing, r1); assertSameRecords(existing, r2); assertSame(r1, r2); assertEquals(ItemState.LOCKED, existing.getState()); assertEquals(r.getRecordId(), existing.getLockedByRecordId()); StateRecord r3 = r.lockToRevoke(existing2.getId()); existing2.reload(); assertSameRecords(existing2, r3); assertEquals(ItemState.LOCKED, existing2.getState()); assertEquals(r.getRecordId(), existing2.getLockedByRecordId()); }
@Test public void createOutputLockRecord() throws Exception { ledger.enableCache(true); StateRecord owner = ledger.findOrCreate(HashId.createRandom()); StateRecord other = ledger.findOrCreate(HashId.createRandom()); HashId id = HashId.createRandom(); StateRecord r1 = owner.createOutputLockRecord(id); r1.reload(); assertEquals(id, r1.getId()); assertEquals(ItemState.LOCKED_FOR_CREATION, r1.getState()); assertEquals(owner.getRecordId(), r1.getLockedByRecordId()); StateRecord r2 = owner.createOutputLockRecord(id); assertSame(r2, r1); assertNull(owner.createOutputLockRecord(other.getId())); // And hacked low level operation must fail too assertNull(ledger.createOutputLockRecord(owner.getRecordId(), other.getId())); }
@Test public void createOutputLockRecord() throws Exception { ledger.enableCache(true); StateRecord owner = ledger.findOrCreate(HashId.createRandom()); StateRecord other = ledger.findOrCreate(HashId.createRandom()); HashId id = HashId.createRandom(); StateRecord r1 = owner.createOutputLockRecord(id); r1.reload(); assertEquals(id, r1.getId()); assertEquals(ItemState.LOCKED_FOR_CREATION, r1.getState()); assertEquals(owner.getRecordId(), r1.getLockedByRecordId()); StateRecord r2 = owner.createOutputLockRecord(id); assertSame(r2, r1); assertNull(owner.createOutputLockRecord(other.getId())); // And hacked low level operation must fail too assertNull(ledger.createOutputLockRecord(owner.getRecordId(), other.getId())); }