Skip to content

fix: evict failed compilation from cache to allow retry - #470

Open
chenjunwenhao wants to merge 1 commit into
alibaba:mainfrom
chenjunwenhao:fix/compile-cache-failed-eviction
Open

fix: evict failed compilation from cache to allow retry#470
chenjunwenhao wants to merge 1 commit into
alibaba:mainfrom
chenjunwenhao:fix/compile-cache-failed-eviction

Conversation

@chenjunwenhao

Copy link
Copy Markdown
Contributor

Summary

  • Bug: When QLOptions.cache(true) is enabled and a script fails to compile (e.g., syntax error), the failed FutureTask is permanently cached in compileCache. All subsequent calls with the same script will get the same cached failure, even if the root cause has been resolved (e.g., a missing class becomes available).
  • Fix: Evict the failed future from the cache when Future.get() throws, so the next call can attempt fresh compilation.
  • Tests: Added two test cases verifying failed compilations don't leak into subsequent cache operations.

Root Cause

In Express4Runner.getParseFuture(), a FutureTask is inserted into the ConcurrentHashMap via putIfAbsent before parseTask.run() executes. If parseDefinition(script) throws, the FutureTask captures the exception internally and remains in the cache. Every subsequent call finds this poisoned future and re-throws the same ExecutionException forever.

The only existing workaround is clearCompileCache(), which drops all cached scripts (including successfully compiled ones), causing a latency spike.

Fix

public QCompileCache parseToDefinitionWithCache(String script) {
    Future<QCompileCache> future = getParseFuture(script);
    try {
        return future.get();
    }
    catch (Exception e) {
        compileCache.remove(script, future);  // evict failed future
        // ... rethrow as before
    }
}

Uses conditional remove(key, value) to safely evict only the specific failed future, avoiding accidental eviction of a replacement that another thread may have inserted concurrently.

Test Plan

  • compileCacheEvictsFailedEntriesTest: Verifies a failed compilation doesn't prevent subsequent valid scripts from compiling with cache enabled.
  • compileCacheDoesNotLeakFailedEntriesTest: Verifies multiple distinct failing scripts don't leak entries, and valid scripts still work and are cacheable.
  • Existing concurrentCacheTest continues to pass.

When a script fails to compile with QLOptions.cache(true), the failed
FutureTask was permanently cached, preventing any subsequent retry
from ever succeeding. Now the failed future is evicted from the cache
so that the next call can attempt fresh compilation.

Uses conditional remove (ConcurrentHashMap.remove(key, value)) to
avoid accidentally evicting a replacement future inserted by another
thread between the failure and the eviction.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant