Disposable identityHashCode Collision causing wrong behavior
One of our users bumped into that error:
Text:
com.intellij.util.IncorrectOperationException: Sorry but parent: com.tabnine.inline.CompletionPreview@3e2d7098 (class com.tabnine.inline.CompletionPreview) has already been disposed (see the cause for stacktrace) so the child: com.tabnine.inline.render.DefaultTabnineInlay@215e257b (class com.tabnine.inline.render.DefaultTabnineInlay) will never be disposed at com.intellij.openapi.util.ObjectTree.register(ObjectTree.java:53) at com.intellij.openapi.util.Disposer.register(Disposer.java:118) at com.tabnine.inline.render.DefaultTabnineInlay.<init>(DefaultTabnineInlay.kt:20) at com.tabnine.inline.render.TabnineInlay$Companion.create(TabnineInlay.kt:18) at com.tabnine.inline.render.TabnineInlay.create(TabnineInlay.kt) at com.tabnine.inline.CompletionPreview.<init>(CompletionPreview.java:52) at com.tabnine.inline.CompletionPreview.createInstance(CompletionPreview.java:65) at com.tabnine.inline.InlineCompletionHandler.showInlineCompletion(InlineCompletionHandler.java:95) at com.tabnine.inline.InlineCompletionHandler.lambda$rerenderCompletion$3(InlineCompletionHandler.java:66) at com.intellij.openapi.application.TransactionGuardImpl.runWithWritingAllowed(TransactionGuardImpl.java:215) at com.intellij.openapi.application.TransactionGuardImpl.access$100(TransactionGuardImpl.java:22) at com.intellij.openapi.application.TransactionGuardImpl$1.run(TransactionGuardImpl.java:197) at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:873) at com.intellij.openapi.application.impl.ApplicationImpl$3.run(ApplicationImpl.java:511) at com.intellij.openapi.application.impl.FlushQueue.doRun(FlushQueue.java:69) at com.intellij.openapi.application.impl.FlushQueue.runNextEvent(FlushQueue.java:112) at com.intellij.openapi.application.impl.FlushQueue.flushNow(FlushQueue.java:42) at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313) at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:776) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:727) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721) at java.base/java.security.AccessController.doPrivileged(Native Method) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:746) at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:898) at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:746) at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$6(IdeEventQueue.java:439) at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:803) at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$7(IdeEventQueue.java:438) at com.intellij.openapi.application.TransactionGuardImpl.performActivity(TransactionGuardImpl.java:106) at com.intellij.ide.IdeEventQueue.performActivity(IdeEventQueue.java:604) at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:436) at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:873) at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:484) at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:207) at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:128) at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:117) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:113) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:105) at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:92)
As you can see from the code: https://github.com/codota/tabnine-intellij/blob/master/src/main/java/com/tabnine/inline/CompletionPreview.java#L52
The object is regarded as disposed of, although the creation of TabnineInlay was called from the constructor of CompletionPreview... This means that it is not really feasible that the CompletionPreview instance was disposed of before it was even created.
That led me to look at what is throwing the exception:
Object wasDisposed = getDisposalInfo(parent);
if (wasDisposed != null) {
throw new IncorrectOperationException("Sorry but parent: " + parent + " has already been disposed " +
"(see the cause for stacktrace) so the child: "+child+" will never be disposed",
wasDisposed instanceof Throwable ? (Throwable)wasDisposed : null);
}
From which I found that you use the following identity strategy:
TObjectIdentityHashingStrategy
Which computes hashcode as such:
public final int computeHashCode(T object) {
return System.identityHashCode(object);
}
This is the cause of the bug. As this is what the docs are stating: ״As much as is reasonably practical, the hashCode
method defined by class Object
does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)״.
This means that it is guaranteed that an object will have the same identityHashCode for its entire lifetime, but it is not guaranteed to be unique throughout the program life. Since we are creating and disposing of a big amount of CompletionPreviews, it seems that a collision had occurred, which caused the exception.
Please sign in to leave a comment.
Boaz Thank you for the analysis. I think things are a bit more complicated though. Even if you managed to bump into an identityHash collision (not a small feat btw, you should definitely apply to https://en.wikipedia.org/wiki/NIST_hash_function_competition :) ),
it's not enough to cause the bug, because this identityHashCode is used for the Map implementation. And maps are written specifically to survive their key hash collisions (in this case, using an open addressing strategy).
So the truth I think is out there somewhere else. Maybe TabnineInlay.create(this) somehow disposes its argument? Or it's the other thread who disposes it?