Skip to content

Commit 6ffd1fe

Browse files
committed
fix: using tombstones to account for rapid deletion
closes: #2314 Signed-off-by: Steven Hawkins <shawkins@redhat.com>
1 parent 43b8591 commit 6ffd1fe

File tree

4 files changed

+56
-16
lines changed

4 files changed

+56
-16
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,17 @@ protected ManagedInformerEventSource(
5151

5252
@Override
5353
public void onAdd(R resource) {
54-
temporaryResourceCache.onEvent(resource, false);
54+
temporaryResourceCache.onAddOrUpdateEvent(resource);
5555
}
5656

5757
@Override
5858
public void onUpdate(R oldObj, R newObj) {
59-
temporaryResourceCache.onEvent(newObj, false);
59+
temporaryResourceCache.onAddOrUpdateEvent(newObj);
6060
}
6161

6262
@Override
6363
public void onDelete(R obj, boolean deletedFinalStateUnknown) {
64-
temporaryResourceCache.onEvent(obj, deletedFinalStateUnknown);
64+
temporaryResourceCache.onDeleteEvent(obj, deletedFinalStateUnknown);
6565
}
6666

6767
protected InformerManager<R, C> manager() {

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ public class TemporaryResourceCache<T extends HasMetadata> {
3838

3939
private static final Logger log = LoggerFactory.getLogger(TemporaryResourceCache.class);
4040
private static final int MAX_RESOURCE_VERSIONS = 256;
41+
private static final int TOMBSTONE_RETENTION_TIME = 120000;
4142

4243
private final Map<ResourceID, T> cache = new ConcurrentHashMap<>();
44+
45+
private final Map<String, Long> tombstones = new LinkedHashMap<String, Long>();
4346
private final ManagedInformerEventSource<T, ?, ?> managedInformerEventSource;
4447
private final boolean parseResourceVersions;
4548
private final Set<String> knownResourceVersions;
@@ -51,7 +54,7 @@ public TemporaryResourceCache(ManagedInformerEventSource<T, ?, ?> managedInforme
5154
if (parseResourceVersions) {
5255
knownResourceVersions = Collections.newSetFromMap(new LinkedHashMap<String, Boolean>() {
5356
@Override
54-
protected boolean removeEldestEntry(java.util.Map.Entry<String, Boolean> eldest) {
57+
protected boolean removeEldestEntry(Map.Entry<String, Boolean> eldest) {
5558
return size() >= MAX_RESOURCE_VERSIONS;
5659
}
5760
});
@@ -60,7 +63,23 @@ protected boolean removeEldestEntry(java.util.Map.Entry<String, Boolean> eldest)
6063
}
6164
}
6265

63-
public synchronized void onEvent(T resource, boolean unknownState) {
66+
public synchronized void onDeleteEvent(T resource, boolean unknownState) {
67+
tombstones.put(resource.getMetadata().getUid(), System.currentTimeMillis());
68+
onEvent(resource, unknownState);
69+
}
70+
71+
public synchronized void onAddOrUpdateEvent(T resource) {
72+
onEvent(resource, false);
73+
}
74+
75+
synchronized void onEvent(T resource, boolean unknownState) {
76+
if (!tombstones.isEmpty()) {
77+
var iter = tombstones.entrySet().iterator();
78+
var entry = iter.next();
79+
if (System.currentTimeMillis() - entry.getValue() > TOMBSTONE_RETENTION_TIME) {
80+
iter.remove();
81+
}
82+
}
6483
cache.computeIfPresent(ResourceID.fromResource(resource),
6584
(id, cached) -> (unknownState || !isLaterResourceVersion(id, cached, resource)) ? null
6685
: cached);
@@ -84,14 +103,25 @@ public synchronized void putResource(T newResource, String previousResourceVersi
84103
var cachedResource = getResourceFromCache(resourceId)
85104
.orElse(managedInformerEventSource.get(resourceId).orElse(null));
86105

87-
if ((previousResourceVersion == null && cachedResource == null)
106+
boolean moveAhead = false;
107+
if (previousResourceVersion == null && cachedResource == null) {
108+
if (tombstones.get(newResource.getMetadata().getUid()) != null) {
109+
log.debug(
110+
"Won't resurrect uid {} for resource id: {}",
111+
newResource.getMetadata().getUid(), resourceId);
112+
return;
113+
}
114+
moveAhead = true;
115+
}
116+
117+
if (moveAhead
88118
|| (cachedResource != null
89119
&& (cachedResource.getMetadata().getResourceVersion().equals(previousResourceVersion))
90120
|| isLaterResourceVersion(resourceId, newResource, cachedResource))) {
91121
log.debug(
92122
"Temporarily moving ahead to target version {} for resource id: {}",
93123
newResource.getMetadata().getResourceVersion(), resourceId);
94-
putToCache(newResource, resourceId);
124+
cache.put(resourceId, newResource);
95125
} else if (cache.remove(resourceId) != null) {
96126
log.debug("Removed an obsolete resource from cache for id: {}", resourceId);
97127
}
@@ -123,10 +153,6 @@ private boolean isLaterResourceVersion(ResourceID resourceId, T newResource, T c
123153
return false;
124154
}
125155

126-
private void putToCache(T resource, ResourceID resourceID) {
127-
cache.put(resourceID == null ? ResourceID.fromResource(resource) : resourceID, resource);
128-
}
129-
130156
public synchronized Optional<T> getResourceFromCache(ResourceID resourceID) {
131157
return Optional.ofNullable(cache.get(resourceID));
132158
}

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ void propagateEventAndRemoveResourceFromTempCacheIfResourceVersionMismatch() {
120120
informerEventSource.onUpdate(cachedDeployment, testDeployment());
121121

122122
verify(eventHandlerMock, times(1)).handleEvent(any());
123-
verify(temporaryResourceCacheMock, times(1)).onEvent(testDeployment(), false);
123+
verify(temporaryResourceCacheMock, times(1)).onAddOrUpdateEvent(testDeployment());
124124
}
125125

126126
@Test

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCacheTest.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ void addOperationNotAddsTheResourceIfInformerCacheNotEmpty() {
8181
void removesResourceFromCache() {
8282
ConfigMap testResource = propagateTestResourceToCache();
8383

84-
temporaryResourceCache.onEvent(testResource(), false);
84+
temporaryResourceCache.onAddOrUpdateEvent(testResource());
8585

8686
assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource)))
8787
.isNotPresent();
@@ -96,20 +96,33 @@ void resourceVersionParsing() {
9696
ConfigMap testResource = propagateTestResourceToCache();
9797

9898
// an event with a newer version will not remove
99-
temporaryResourceCache.onEvent(new ConfigMapBuilder(testResource).editMetadata()
100-
.withResourceVersion("1").endMetadata().build(), false);
99+
temporaryResourceCache.onAddOrUpdateEvent(new ConfigMapBuilder(testResource).editMetadata()
100+
.withResourceVersion("1").endMetadata().build());
101101

102102
assertThat(temporaryResourceCache.isKnownResourceVersion(testResource)).isTrue();
103103
assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource)))
104104
.isPresent();
105105

106106
// anything else will remove
107-
temporaryResourceCache.onEvent(testResource(), false);
107+
temporaryResourceCache.onAddOrUpdateEvent(testResource());
108108

109109
assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource)))
110110
.isNotPresent();
111111
}
112112

113+
@Test
114+
void rapidDeletion() {
115+
var testResource = testResource();
116+
117+
temporaryResourceCache.onAddOrUpdateEvent(testResource);
118+
temporaryResourceCache.onDeleteEvent(new ConfigMapBuilder(testResource).editMetadata()
119+
.withResourceVersion("3").endMetadata().build(), false);
120+
temporaryResourceCache.putAddedResource(testResource);
121+
122+
assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource)))
123+
.isEmpty();
124+
}
125+
113126
private ConfigMap propagateTestResourceToCache() {
114127
var testResource = testResource();
115128
when(informerEventSource.get(any())).thenReturn(Optional.empty());
@@ -127,6 +140,7 @@ ConfigMap testResource() {
127140
configMap.getMetadata().setName("test");
128141
configMap.getMetadata().setNamespace("default");
129142
configMap.getMetadata().setResourceVersion(RESOURCE_VERSION);
143+
configMap.getMetadata().setUid("test-uid");
130144
return configMap;
131145
}
132146

0 commit comments

Comments
 (0)