Skip to content

[Bug] CacheFilter returns the wrong result for distinct string argument tuples containing commas #16502

Open

Description

@hickey-ai

Pre-check

  • I am sure that all the content I provide is in English.

Search before asking

  • I had searched in the issues and found no similar issues.

Apache Dubbo Component

Java SDK (apache/dubbo)

Dubbo Version

Apache Dubbo source: 3.3.7-SNAPSHOT, branch 3.3, commit ae809de2fc81b408194240965eb20bf27e6971df.
Windows, OpenJDK 21, Maven 3.6.3.

Verification used an isolated Maven/JUnit harness: eight relevant implementation/interface files were copied byte-for-byte from this commit, with released Dubbo 3.3.6 artifacts supplying supporting dependencies. The paths under review were compiled from the current source, not taken from those dependency jars. This is not a full current-branch reactor build or a networked RPC deployment.

Steps to reproduce this issue

The result-cache key does not preserve string argument boundaries. For the same two-string method, arguments ("a,b", "c") and ("a", "b,c") both become a,b,c. The second invocation then receives the first invocation result without calling the underlying invoker.

This reproducer executes the current CacheFilter and ThreadLocalCache with a deterministic local Invoker; it is not just a comparison of helper outputs.

import java.util.concurrent.atomic.AtomicInteger;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.cache.filter.CacheFilter;
import org.apache.dubbo.cache.support.threadlocal.ThreadLocalCache;
import org.apache.dubbo.rpc.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class ArgumentCacheKeyTest {
    @Test void differentTuplesMustNotShareResults() {
        URL url = URL.valueOf(
            "dubbo://127.0.0.1:20880/example.Service?cache=threadlocal");
        ThreadLocalCache cache = new ThreadLocalCache(url);
        CacheFilter filter = new CacheFilter();
        filter.setCacheFactory((u, i) -> cache);
        AtomicInteger calls = new AtomicInteger();
        Invoker<Object> invoker = new Invoker<Object>() {
            public Class<Object> getInterface() { return Object.class; }
            public URL getUrl() { return url; }
            public boolean isAvailable() { return true; }
            public void destroy() {}
            public Result invoke(Invocation invocation) {
                calls.incrementAndGet();
                Object[] a = invocation.getArguments();
                return new AppResponse(a[0] + "|" + a[1]);
            }
        };

        RpcInvocation first = call("a,b", "c");
        RpcInvocation second = call("a", "b,c");
        assertEquals("a,b|c", filter.invoke(invoker, first).getValue());
        assertEquals("a|b,c", filter.invoke(invoker, second).getValue());
        assertEquals(2, calls.get());
    }

    static RpcInvocation call(String a, String b) {
        RpcInvocation invocation = new RpcInvocation();
        invocation.setMethodName("combine");
        invocation.setParameterTypes(
            new Class<?>[]{String.class, String.class});
        invocation.setArguments(new Object[]{a, b});
        return invocation;
    }
}

No registry, exported service, or remote network access is used.

What you expected to happen

Distinct argument tuples must not alias the same result-cache entry. The second call should invoke the provider and return a|b,c. Repeating an identical tuple can legitimately hit the cache.

Actual executed result:

cache keys: a,b,c / a,b,c
second result: a,b|c, invocations: 1
expected: <a|b,c> but was: <a,b|c>

Anything else

Root-cause path: CacheFilter.invoke obtains its cache key from StringUtils.toArgumentString. Strings are appended directly, separated by commas, with no escaping or structural encoding. Both tuples therefore become the same key.

The report concerns normal application-result correctness for two arguments of the same declared type and the same method, so it does not depend on overloading, concurrency, or DTO serialization. A possible fix is a structural key or an unambiguous argument encoding that preserves boundaries. Regression coverage should verify both distinct tuples and repeated identical tuples.

Searched CacheFilter/key, toArgumentString, cache/collision, cache/arguments and cache/comma issues/PRs; no matching collision report was found. Existing reports about async placeholder results, expiration, cache backend key restrictions and reference-cache registry collisions concern different paths.

Do you have a (mini) reproduction demo?

  • Yes, I have a minimal reproduction demo to help resolve this issue more effectively!

Are you willing to submit a pull request to fix on your own?

  • Yes I am willing to submit a pull request on my own!

Code of Conduct

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions