Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 342506 - Race conditions in ASTProvider.prepareForUsingCache method
Summary: Race conditions in ASTProvider.prepareForUsingCache method
Status: RESOLVED INVALID
Alias: None
Product: CDT
Classification: Tools
Component: cdt-editor (show other bugs)
Version: 8.0   Edit
Hardware: PC Linux
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: Project Inbox CLA
QA Contact: Anton Leherbauer CLA
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-04-11 22:43 EDT by Sergey Prigogin CLA
Modified: 2011-04-14 14:23 EDT (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Sergey Prigogin CLA 2011-04-11 22:43:17 EDT
The method is defined as:

 1  public IStatus runOnAST(ICElement cElement, WAIT_FLAG waitFlag,
 2          IProgressMonitor monitor, ASTCache.ASTRunnable astRunnable) {
 3      Assert.isTrue(cElement instanceof ITranslationUnit);
 4      final ITranslationUnit tu = (ITranslationUnit) cElement;
 5      if (!tu.isOpen())
 6          return Status.CANCEL_STATUS;
 7
 8      final boolean isActive= fCache.isActiveElement(tu);
 9      if (waitFlag == WAIT_ACTIVE_ONLY && !isActive) {
10          return Status.CANCEL_STATUS;
11      }
12      if (isActive && updateModificationStamp()) {
13          fCache.disposeAST();
14      }
15      return fCache.runOnAST(tu, waitFlag != WAIT_NO, monitor, astRunnable);
16}

There seems to be a race condition around isActive, which may no longer be valid as soon as fCache.isActiveElement(tu) returns.

Another race condition is around the value returned by the updateModificationStamp() call. fCache.disposeAST() may not be called if activeEditorChanged is called between updateModificationStamp() and fCache.runOnAST. It looks like lines 8-14 have to be put inside a synchronized(this) block, but it still does not prevent the second race condition.
Comment 1 Anton Leherbauer CLA 2011-04-13 03:13:26 EDT
I saw you already changed the synchronization.  Please make sure that bug 233976 does not reoccur.
Comment 2 Sergey Prigogin CLA 2011-04-13 03:27:19 EDT
(In reply to comment #1)
> I saw you already changed the synchronization.  Please make sure that bug
> 233976 does not reoccur.

Toni, do you mind reviewing the fix? I'm particularly interested in your assessment of the second race condition, which haven't been addressed by the fix.
Comment 3 Anton Leherbauer CLA 2011-04-13 06:03:49 EDT
I can easily create a deadlock:

Thread [main] (Suspended)	
	owns: Object  (id=89)	
	waiting for: Object  (id=90)	
		owned by: Thread [Worker-4] (Suspended)	
	ASTCache.aboutToBeReconciled(ITranslationUnit) line: 386	
	ASTProvider.aboutToBeReconciled(ICElement) line: 274	
	CEditor.aboutToBeReconciled() line: 3039	
	CReconcilingStrategy.aboutToBeReconciled() line: 133	
	CCompositeReconcilingStrategy.aboutToBeReconciled() line: 121	
	CReconciler.aboutToBeReconciled() line: 386	
	AbstractReconciler$Listener.documentChanged(DocumentEvent) line: 241	
	SynchronizableDocument(AbstractDocument).doFireDocumentChanged2(DocumentEvent) line: 769	
	SynchronizableDocument(AbstractDocument).doFireDocumentChanged(DocumentEvent, boolean, IRegion) line: 736	
	SynchronizableDocument(AbstractDocument).doFireDocumentChanged(DocumentEvent) line: 721	
	SynchronizableDocument(AbstractDocument).fireDocumentChanged(DocumentEvent) line: 796	
	SynchronizableDocument(AbstractDocument).replace(int, int, String, long) line: 1191	
	SynchronizableDocument.replace(int, int, String, long) line: 194	
	SynchronizableDocument(AbstractDocument).replace(int, int, String) line: 1210	
	SynchronizableDocument.replace(int, int, String) line: 180	
	DefaultDocumentAdapter.replaceTextRange(int, int, String) line: 248	
	StyledText.modifyContent(Event, boolean) line: 7178	
	StyledText.sendKeyEvent(Event) line: 7992	
	StyledText.doContent(char) line: 2456	
	StyledText.handleKey(Event) line: 5909	
	StyledText.handleKeyDown(Event) line: 5939	
	StyledText$7.handleEvent(Event) line: 5633	
	EventTable.sendEvent(Event) line: 84	
	StyledText(Widget).sendEvent(Event) line: 1053	
	StyledText(Widget).sendEvent(int, Event, boolean) line: 1077	
	StyledText(Widget).sendEvent(int, Event) line: 1062	
	StyledText(Widget).sendKeyEvent(int, int, int, int, Event) line: 1104	
	StyledText(Widget).sendKeyEvent(int, int, int, int) line: 1100	
	StyledText(Widget).wmChar(int, int, int) line: 1509	
	StyledText(Control).WM_CHAR(int, int) line: 4623	
	StyledText(Canvas).WM_CHAR(int, int) line: 345	
	StyledText(Control).windowProc(int, int, int, int) line: 4511	
	StyledText(Canvas).windowProc(int, int, int, int) line: 341	
	Display.windowProc(int, int, int, int) line: 4957	
	OS.DispatchMessageW(MSG) line: not available [native method]	
	OS.DispatchMessage(MSG) line: 2525	
	Display.readAndDispatch() line: 3737	
	Workbench.runEventLoop(Window$IExceptionHandler, Display) line: 2696	
	Workbench.runUI() line: 2660	
	Workbench.access$4(Workbench) line: 2494	
	Workbench$7.run() line: 674	
	Realm.runWithDefault(Realm, Runnable) line: 332	
	Workbench.createAndRunWorkbench(Display, WorkbenchAdvisor) line: 667	
	PlatformUI.createAndRunWorkbench(Display, WorkbenchAdvisor) line: 149	
	IDEApplication.start(IApplicationContext) line: 123	
	EclipseAppHandle.run(Object) line: 196	
	EclipseAppLauncher.runApplication(Object) line: 110	
	EclipseAppLauncher.start(Object) line: 79	
	EclipseStarter.run(Object) line: 344	
	EclipseStarter.run(String[], Runnable) line: 179	
	NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]	
	NativeMethodAccessorImpl.invoke(Object, Object[]) line: not available	
	DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: not available	
	Method.invoke(Object, Object...) line: not available	
	Main.invokeFramework(String[], URL[]) line: 622	
	Main.basicRun(String[]) line: 577	
	Main.run(String[]) line: 1410	
	Main.main(String[]) line: 1386	

Thread [Worker-4] (Suspended)	
	owns: Object  (id=90)	
		waited by: Thread [Worker-5] (Running)	
		waited by: Thread [main] (Suspended)	
	Unsafe.park(boolean, long) line: not available [native method]	
	LockSupport.park(Object) line: not available	
	Semaphore$NonfairSync(AbstractQueuedSynchronizer).parkAndCheckInterrupt() line: not available	
	Semaphore$NonfairSync(AbstractQueuedSynchronizer).doAcquireSharedInterruptibly(int) line: not available	
	Semaphore$NonfairSync(AbstractQueuedSynchronizer).acquireSharedInterruptibly(int) line: not available	
	Semaphore.acquire() line: not available	
	ASTCache.acquireSharedAST(ITranslationUnit, IIndex, boolean, IProgressMonitor) line: 248	
	ASTCache.runOnAST(ITranslationUnit, boolean, IProgressMonitor, ASTCache$ASTRunnable) line: 223	
	ASTProvider.runOnAST(ICElement, ASTProvider$WAIT_FLAG, IProgressMonitor, ASTCache$ASTRunnable) line: 334	
	SelectionListenerWithASTManager$PartListenerGroup.calculateASTandInform(IWorkingCopy, ITextSelection, IProgressMonitor) line: 168	
	SelectionListenerWithASTManager$PartListenerGroup$3.run(IProgressMonitor) line: 142	
	Worker.run() line: 54	

I don't understand yet where you see two race conditions and esp. what the real negative impact is.
Comment 4 Sergey Prigogin CLA 2011-04-13 18:07:22 EDT
(In reply to comment #3)
The deadlock that you reported was introduced by my other changes and not by synchronization around the following block of code that is now located inside prepareForUsingCache method.

    final boolean isActive= fCache.isActiveElement(tu);
    if (waitFlag == WAIT_ACTIVE_ONLY && !isActive) {
        return false;
    }
    if (isActive && updateModificationStamp()) {
        fCache.disposeAST();
    }

Nevertheless, I removed the synchronization around this block since I realized how little I understand this code. The potential race scenarios are:

1. tu was inactive when fCache.isActiveElement(tu) was called but became active immediately after that.
2. tu was active when fCache.isActiveElement(tu) was called but became inactive immediately after that.
3. updateModificationStamp() returned false, but the document changed immediately after that.

#1 is benign since we simply say that the shared AST is not available instead of trying harder to get it.

I'm not sure what would happen in scenarios #2 and #3.
Comment 5 Anton Leherbauer CLA 2011-04-14 04:51:40 EDT
(In reply to comment #4)
> (In reply to comment #3)
> The deadlock that you reported was introduced by my other changes and not by
> synchronization around the following block of code that is now located inside
> prepareForUsingCache method.

Right, I did not look in detail at the stack trace, but the synchronization of the method prepreForUsingCache() introduces a potential deadlock as well:

Thread [main] (Suspended)	
	owns: Object  (id=121)	
		waited by: Thread [Worker-16] (Suspended)	
			owns: ASTProvider  (id=126)	
				waited by: Thread [main] (Suspended)	
	waiting for: ASTProvider  (id=126)	
		owned by: Thread [Worker-16] (Suspended)	
			waiting for: Object  (id=121)	
	ASTProvider.updateModificationStamp() line: 284	
	ASTProvider.aboutToBeReconciled(ICElement) line: 277	
	CEditor.aboutToBeReconciled() line: 3039	
	CReconcilingStrategy.aboutToBeReconciled() line: 133	
	CCompositeReconcilingStrategy.aboutToBeReconciled() line: 121	
	CReconciler.aboutToBeReconciled() line: 386	
	AbstractReconciler$Listener.documentChanged(DocumentEvent) line: 241	


Thread [Worker-16] (Suspended)	
	owns: ASTProvider  (id=126)	
	waiting for: Object  (id=121)	
		owned by: Thread [main] (Suspended)	
	SynchronizableDocument.getModificationStamp() line: 166	
	ASTProvider.updateModificationStamp() line: 294	
	ASTProvider.prepareForUsingCache(ITranslationUnit, ASTProvider$WAIT_FLAG) line: 401	
	ASTProvider.runOnAST(ICElement, ASTProvider$WAIT_FLAG, IProgressMonitor, ASTCache$ASTRunnable) line: 345	
	SelectionListenerWithASTManager$PartListenerGroup.calculateASTandInform(IWorkingCopy, ITextSelection, IProgressMonitor) line: 168	
	SelectionListenerWithASTManager$PartListenerGroup$3.run(IProgressMonitor) line: 142	
	Worker.run() line: 54	

> Nevertheless, I removed the synchronization around this block since I realized
> how little I understand this code. The potential race scenarios are:
> 
> 1. tu was inactive when fCache.isActiveElement(tu) was called but became active
> immediately after that.
> 2. tu was active when fCache.isActiveElement(tu) was called but became inactive
> immediately after that.
> 3. updateModificationStamp() returned false, but the document changed
> immediately after that.
> 

#1 and #2 have no impact from my POV.  That's just bad luck, but completely valid.  Otherwise we would need to block the UI thread changing the active editor during the whole of runOnAST.

#3 is no problem as well.  updateModificationStamp() is there to protect against using a stale AST if in scalability mode when no reconciler runs.
Of course it does not protect against document changes while the AST is in use, but for that we would need to block the UI thread for the whole time.  We don't want that, right?
Comment 6 Sergey Prigogin CLA 2011-04-14 13:39:29 EDT
This was a red herring.
Comment 7 CDT Genie CLA 2011-04-14 14:23:11 EDT
*** cdt cvs genie on behalf of sprigogin ***
Bug 342506. Added a comment with a link.

[*] ASTProvider.java 1.22 http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.cdt-core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/ASTProvider.java?root=Tools_Project&r1=1.21&r2=1.22