@Test public void testProcessMatchingLock() throws Exception { ExpiryMarker original = new ExpiryMarker(null, 100L, "the-marker-id").markForExpiration(100L, "some-marker-id"); Map.Entry<Object, Expirable> entry = mockEntry(original); UnlockEntryProcessor processor = new UnlockEntryProcessor( new ExpiryMarker(null, 100L, "the-marker-id"), "next-marker-id", 150L); ArgumentCaptor<Expirable> captor = ArgumentCaptor.forClass(Expirable.class); processor.process(entry); verify(entry).setValue(captor.capture()); Expirable result = captor.getValue(); assertNotSame(original, result); assertThat(result, instanceOf(ExpiryMarker.class)); ExpiryMarker marker = (ExpiryMarker) result; assertTrue(marker.matches(original)); }
@Test public void testProcessWithValue() throws Exception { Map.Entry<Object, Expirable> entry = mockEntry(new Value(null, 100L, "some-value")); UnlockEntryProcessor processor = new UnlockEntryProcessor( new ExpiryMarker(null, 100L, "other-marker-id"), "next-marker-id", 150L); ArgumentCaptor<Expirable> captor = ArgumentCaptor.forClass(Expirable.class); processor.process(entry); verify(entry).setValue(captor.capture()); Expirable result = captor.getValue(); assertThat(result, instanceOf(ExpiryMarker.class)); ExpiryMarker marker = (ExpiryMarker) result; assertFalse( "market must be expirable after timestamp", marker.isReplaceableBy(150L, null, null)); assertTrue( "market must be expirable after timestamp", marker.isReplaceableBy(151L, null, null)); }
@Override public Boolean process(Map.Entry<Object, Expirable> entry) { Expirable expirable = entry.getValue(); boolean updated; if (expirable == null) { // Nothing there. The entry was evicted? It should be safe to replace it expirable = new Value(newVersion, timestamp, newValue); updated = true; } else { if (expirable.matches(lock)) { final ExpiryMarker marker = (ExpiryMarker) expirable; if (marker.isConcurrent()) { // Multiple transactions are attempting to update the same entry. Its highly // likely that the value we are attempting to set is invalid. Instead just // expire the entry and allow the next put to the cache to succeed if no more // transactions are in-flight. expirable = marker.expire(timestamp); updated = false; } else { // Only one transaction attempted to update the entry so it is safe to replace // it with the value supplied expirable = new Value(newVersion, timestamp, newValue); updated = true; } } else if (expirable.getValue() == null) { // It's a different marker, Leave it as is return false; } else { // It's a value. We have no way to see which is correct so we expire the entry. // It is expired instead of removed to prevent in progress transactions from // putting stale values into the cache expirable = new ExpiryMarker(newVersion, timestamp, nextMarkerId).expire(timestamp); updated = false; } } entry.setValue(expirable); return updated; }