Tencent/matrix · error · IllegalStateException
Call wait() and check its result
Error message
Call wait() and check its result
What it means
FutureResult.get returns the computed result but requires wait() to have completed first; it checks latch.getCount() and throws IllegalStateException if the latch hasn't counted down to zero. This guards against reading resultHolder before the producer thread has set it, which would return null/garbage silently.
Solutions
- Always call wait(timeout, TimeUnit) and check its boolean result before calling get().
- If wait() returned false (timeout), handle the timeout instead of calling get().
- Use the same thread flow as the library's dumpHeap: wait, then get.
- If you need async semantics, wrap wait+get in your own future/callback rather than splitting them across threads.
Example fix
// before
FutureResult<HeapDump> fr = dumpHeapAsync();
HeapDump dump = fr.get();
// after
FutureResult<HeapDump> fr = dumpHeapAsync();
if (fr.wait(10, TimeUnit.SECONDS)) {
HeapDump dump = fr.get();
} Defensive patterns
Strategy: validation
Validate before calling
if (!futureResult.wait(10, TimeUnit.SECONDS)) {
throw new TimeoutException("heap dump did not finish");
}
T result = futureResult.get(); Try / catch
try {
T result = futureResult.get();
} catch (IllegalStateException e) {
// wait() was skipped or timed out; redo wait or abort
} Prevention
- Always pair get() with a preceding successful wait() call
- Check wait()'s boolean return before get()
- Keep wait+get on the same call path/thread
When it happens
Trigger: Calling get() on a FutureResult without first calling wait(timeout, unit) and confirming it returned true, e.g. calling get() immediately after enqueueing the heap dump task.
Common situations: Misusing FutureResult as a plain holder; forgetting the wait step after copying sample code; calling get() from a different thread that skipped the wait timeout.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Closed during initialization
- Did not expect thread to be interrupted
- getIssueStorage db not ready
- getDatabase called recursively
- NOT allow to observe with DESTROYED lifecycle owner
AI-assisted analysis of Tencent/matrix@3b8293bd65 (2026-09-08).
Data as JSON: /api/errors/f7d372be7eccb1df.
Report an issue: GitHub.
Appendix: source
Thrown at matrix/matrix-android/matrix-resource-canary/matrix-resource-canary-android/src/main/java/com/tencent/matrix/resource/leakcanary/internal/FutureResult.java:42
private final AtomicReference<T> resultHolder;
private final CountDownLatch latch;
public FutureResult() {
resultHolder = new AtomicReference<>();
latch = new CountDownLatch(1);
}
public boolean wait(long timeout, TimeUnit unit) {
try {
return latch.await(timeout, unit);
} catch (InterruptedException e) {
throw new RuntimeException("Did not expect thread to be interrupted", e);
}
}
public T get() {
if (latch.getCount() > 0) {
throw new IllegalStateException("Call wait() and check its result");
}
return resultHolder.get();
}
public void set(T result) {
resultHolder.set(result);
latch.countDown();
}
}
View on GitHub (pinned to 3b8293bd65)