Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

guava cache asMap() can raise AssertError or StackOverflowError when there is massive cache loading failed with exception #7625

Open
2 tasks done
stxmic opened this issue Jan 17, 2025 · 1 comment
Labels
type=defect Bug, not working as expected

Comments

@stxmic
Copy link

stxmic commented Jan 17, 2025

Guava Version

33.2.1 and old versions use asMap() and ComputingValueReference

Description

the bug is specifically caused since a failure loading attempt results in ComputingValueReference keeps reference to previous loading attempt, repeating this behavior builds reference chain. When the specific entry is expired and to be evicted via preWriteCleanup(), the underlying copyEntry() call would evaluate the reference chain, if there is number of references in the chain exceeds a threshold, evaluating it would lead to the StackOverflowError, if there is other cache keys share the same bucket in the segment, and ahead of this key in the ReferenceEntry linked list, the accessQueue & the linked list will be out of sync, that access queue points to a new entry while the reference linked list failed to be updated as the result of the StackOverflow error. Future access to the segment would all fail with "AssertError" as out of sync.

Suggestion workaround: move away from asMap(), considering deprecating the support as the value reference chain is defected in design.

Example

`public void reproduceStackOverFlow() throws InterruptedException {
        long expireAfterAccess = 10000L;
        Cache<Integer, String> cache = CacheBuilder.newBuilder()
                .expireAfterAccess(Duration.ofMillis(expireAfterAccess))
                .build();

        int key = 100;
        int maxChainLength = 65536; //tested with JVM 2MB stack size, this number may go higher with larger stack size settings
        for (int i = 0; i < maxChainLength; i++) {
            try {
                cache.asMap().computeIfAbsent(key, (k) -> {
                    throw new RuntimeException(); //simulate loading error, can happen with no value for a specific key or a service call fail
                });
            } catch (Exception e) {
                //ok, eat it
            }
        }

        //let the entry expire
        Thread.sleep(expireAfterAccess);

        try {
            cache.asMap().computeIfAbsent(key, (k) -> "foobar");
            Assertions.fail();
        } catch (Error error) {
            error.printStackTrace();
            if (!(error instanceof StackOverflowError)) {
                Assertions.fail();
            }
        }
    }`

Expected Behavior

cache can be managed

Actual Behavior

failed with StackOverflowError

Packages

com.google.common.cache

Platforms

No response

Checklist

  • I agree to follow the code of conduct.

  • I can reproduce the bug with the latest version of Guava available.

@stxmic stxmic added the type=defect Bug, not working as expected label Jan 17, 2025
@ben-manes
Copy link
Contributor

Sounds like maybe this line needs to be wrapped in a try-catch that calls removeLoadingValue if an exception is thrown. It currently retains the failed. I saw something like this in #5438. I would generally avoid the compute methods in Guava's cache since it was not designed for that and made their addition fairly problematic. You might consider using Caffeine instead.

newValue = computingValueReference.compute(key, function);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type=defect Bug, not working as expected
Projects
None yet
Development

No branches or pull requests

2 participants