This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 81f086b40b fix(base): resolve partial identities when removing
metadata (#7790) (#7858)
81f086b40b is described below
commit 81f086b40b36b2631992141e716a278e1a9b410e
Author: konlue <[email protected]>
AuthorDate: Tue Oct 20 12:37:15 2026 +0800
fix(base): resolve partial identities when removing metadata (#7790) (#7858)
---
.../shenyu/plugin/base/cache/MetaDataCache.java | 31 +++++++++++++-
.../plugin/base/cache/MetaDataCacheTest.java | 50 ++++++++++++++++++++++
2 files changed, 79 insertions(+), 2 deletions(-)
diff --git
a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/MetaDataCache.java
b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/MetaDataCache.java
index f15df36785..a6990e23d3 100644
---
a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/MetaDataCache.java
+++
b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/MetaDataCache.java
@@ -98,13 +98,40 @@ public final class MetaDataCache {
/**
* Remove auth data.
*
+ * <p>Deletion events may carry only half of the identity: node sync sends
+ * an id without a path, path sync sends a path without an id (#7790).
+ * The partial identity is resolved against the cached canonical record so
+ * both the id map and the derived path results are invalidated, and the
+ * removal stays idempotent instead of throwing.</p>
+ *
* @param data the data
*/
public synchronized void remove(final MetaData data) {
- META_DATA_MAP.remove(data.getId());
+ final String id = data.getId();
+ final String path = data.getPath();
+ final MetaData cached = Objects.nonNull(id) ? META_DATA_MAP.get(id) :
findByPath(path);
+ if (Objects.nonNull(id)) {
+ META_DATA_MAP.remove(id);
+ } else if (Objects.nonNull(cached)) {
+ META_DATA_MAP.remove(cached.getId());
+ }
indexDirty = true;
negativeCacheDirty = true;
- clean(data.getPath());
+ if (Objects.nonNull(path)) {
+ clean(path);
+ } else if (Objects.nonNull(cached) &&
Objects.nonNull(cached.getPath())) {
+ clean(cached.getPath());
+ }
+ }
+
+ private MetaData findByPath(final String path) {
+ if (Objects.isNull(path)) {
+ return null;
+ }
+ return META_DATA_MAP.values().stream()
+ .filter(metaData -> path.equals(metaData.getPath()))
+ .findFirst()
+ .orElse(null);
}
private void rebuildPathIndex() {
diff --git
a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/cache/MetaDataCacheTest.java
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/cache/MetaDataCacheTest.java
index a7e6b43690..4088c173e3 100644
---
a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/cache/MetaDataCacheTest.java
+++
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/cache/MetaDataCacheTest.java
@@ -21,6 +21,8 @@ import org.apache.shenyu.common.dto.MetaData;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
class MetaDataCacheTest {
@@ -45,6 +47,54 @@ class MetaDataCacheTest {
assertSame(specific, cache.obtain("/api/users/42"));
}
+ @Test
+ void shouldRemoveByIdOnlyRecordWithoutPath() {
+ MetaData full = createMetaData("id-1", "/rpc/dubbo/save");
+ cache.cache(full);
+ assertSame(full, cache.obtain("/rpc/dubbo/save"));
+
+ MetaData idOnly = new MetaData();
+ idOnly.setId("id-1");
+
+ assertDoesNotThrow(() -> cache.remove(idOnly));
+ assertNull(cache.obtain("/rpc/dubbo/save"));
+ }
+
+ @Test
+ void shouldRemoveByPathOnlyRecordWithoutId() {
+ MetaData full = createMetaData("id-2", "/rpc/sofa/find");
+ cache.cache(full);
+ assertSame(full, cache.obtain("/rpc/sofa/find"));
+
+ MetaData pathOnly = new MetaData();
+ pathOnly.setPath("/rpc/sofa/find");
+
+ assertDoesNotThrow(() -> cache.remove(pathOnly));
+ assertNull(cache.obtain("/rpc/sofa/find"));
+ }
+
+ @Test
+ void shouldKeepRemovingFullRecord() {
+ MetaData full = createMetaData("id-3", "/rpc/http/get");
+ cache.cache(full);
+
+ cache.remove(createMetaData("id-3", "/rpc/http/get"));
+
+ assertNull(cache.obtain("/rpc/http/get"));
+ }
+
+ @Test
+ void shouldBeIdempotentForPartialRecords() {
+ MetaData idOnly = new MetaData();
+ idOnly.setId("missing-id");
+ MetaData pathOnly = new MetaData();
+ pathOnly.setPath("/rpc/never/cached");
+
+ assertDoesNotThrow(() -> cache.remove(idOnly));
+ assertDoesNotThrow(() -> cache.remove(pathOnly));
+ assertDoesNotThrow(() -> cache.remove(idOnly));
+ }
+
private MetaData createMetaData(final String id, final String path) {
MetaData metaData = new MetaData();
metaData.setId(id);