Bug 336575 - DLTK 2.0 MixinModel lacks thread safety where it seems intended
Summary: DLTK 2.0 MixinModel lacks thread safety where it seems intended
Status: NEW
Alias: None
Product: DLTK
Classification: Technology
Component: Common (show other bugs)
Version: 2.0   Edit
Hardware: PC Windows 7
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: dltk.common-inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-02-07 20:37 EST by Carl Knight CLA
Modified: 2011-02-14 06:16 EST (History)
1 user (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Carl Knight CLA 2011-02-07 20:37:20 EST
Build Identifier: 3.6.1 M20100909-0800

org.eclipse.dltk.core.mixin.MixinModel is only partially synchronized.

We have multiple threads attempting to use the index and they get into strife calling get(String) on MixinModel. The reason is simple: 

[code]
	public IMixinElement get(String key) {
		if (DLTKCore.VERBOSE) {
			System.out.println("MixinModel.get(" + key + ')'); //$NON-NLS-1$
		}
		if (notExistKeysCache.contains(key)) {
			return null;
		}
		if (removes == 0) {
			if (cache.get(key) == null) {
				return null;
			}
		}
		MixinElement element = getCreateEmpty(key);
		if (DLTKCore.VERBOSE) {
			System.out.println("Filling ratio:" + this.cache.fillingRatio()); //$NON-NLS-1$
			this.cache.printStats();
		}
		buildElementTree(element);
		if (element.isFinal() && element.sourceModules.size() > 0) {
			existKeysCache.add(key);
			return element;
		}
		notExistKeysCache.add(key);
		synchronized (this.cache) {
			cache.remove(element.key);
			cache.resetSpaceLimit(CACHE_LIMIT, element);
		}
		return null;
	}
[/code]

The class members existKeysCache and notExistKeysCache are not synchronized. Multiple threads can access them. The calls to add(key) on the HashMap result in two threads trying to modify the map simultaneously. Specifically a rehash(). This seems to cause the HashMap to either blow up or infinitely loop inside AnalyzeMap().

The solution would be to synchronize these other members as well as the cache.

- Carl.

Thread 1 stack:

Thread [Worker-6] (Suspended)	
	HashMap<K,V>.rehash(int) line: not available	
	HashMap<K,V>.rehash() line: not available	
	HashMap<K,V>.putImpl(K, V) line: not available	
	HashMap<K,V>.put(K, V) line: not available	
	HashSet<E>.add(E) line: not available	
	MixinModel.get(String) line: 147	
	UMixinModelUtils.safeGet(MixinModel, String, String) line: 34	
	MethodNameAndArgsMatchSearchPredicate(AbstractNameMatchSearchPredicate).search(ISourceModule) line: 180	
	UValidationEngine.validate(ISourceModule) line: 106	
	UValidationEngine.processIncludes(List<ISourceModule>, List<ISourceModule>, List<String>) line: 119	
	UValidationEngine.fullyValidate(ISourceModule) line: 89	
	USourceElementValidator.endvisit(ModuleDeclaration) line: 214	
	UModuleDeclaration(ModuleDeclaration).traverse(ASTVisitor) line: 72	
	UValidationBuildParticipant$UBuildParticipant.build(IBuildContext) line: 45	
	StandardScriptBuilder.buildModule(IBuildContext) line: 219	
	StandardScriptBuilder.buildNatureModules(IScriptProject, int, List<ISourceModule>, IProgressMonitor) line: 167	
	StandardScriptBuilder.buildModules(IScriptProject, List<ISourceModule>, int, IProgressMonitor) line: 136	
	StandardScriptBuilder.buildModelElements(IScriptProject, List<ISourceModule>, IProgressMonitor, int) line: 52	
	ScriptBuilder.buildElements(ScriptBuilder$BuilderState[], IProgressMonitor, int) line: 858	
	ScriptBuilder.incrementalBuild(IResourceDelta, IProgressMonitor) line: 697	
	ScriptBuilder.build(int, Map, IProgressMonitor) line: 274	
	BuildManager$2.run() line: 629	
	SafeRunner.run(ISafeRunnable) line: 42	
	BuildManager.basicBuild(int, IncrementalProjectBuilder, Map, MultiStatus, IProgressMonitor) line: 172	
	BuildManager.basicBuild(IProject, int, ICommand[], MultiStatus, IProgressMonitor) line: 203	
	BuildManager$1.run() line: 255	
	SafeRunner.run(ISafeRunnable) line: 42	
	BuildManager.basicBuild(IProject, int, MultiStatus, IProgressMonitor) line: 258	
	BuildManager.basicBuildLoop(IProject[], IProject[], int, MultiStatus, IProgressMonitor) line: 311	
	BuildManager.build(int, IProgressMonitor) line: 343	
	AutoBuildJob.doBuild(IProgressMonitor) line: 144	
	AutoBuildJob.run(IProgressMonitor) line: 242	
	Worker.run() line: 54	

Thread 2 stack:

Daemon Thread [org.eclipse.dltk.internal.ui.text.ScriptReconciler] (Suspended)	
	HashMap<K,V>.analyzeMap() line: not available	
	HashMap<K,V>.rehash(int) line: not available	
	HashMap<K,V>.rehash() line: not available	
	HashMap<K,V>.putImpl(K, V) line: not available	
	HashMap<K,V>.put(K, V) line: not available	
	HashSet<E>.add(E) line: not available	
	MixinModel.get(String) line: 147	
	UMixinModelUtils.safeGet(MixinModel, String, String) line: 34	
	MethodNameAndArgsMatchSearchPredicate(AbstractNameMatchSearchPredicate).search(ISourceModule) line: 180	
	UValidationEngine.validate(ISourceModule) line: 106	
	UValidationEngine.processIncludes(List<ISourceModule>, List<ISourceModule>, List<String>) line: 119	
	UValidationEngine.fullyValidate(ISourceModule) line: 89	
	USourceElementValidator.endvisit(ModuleDeclaration) line: 214	
	UModuleDeclaration(ModuleDeclaration).traverse(ASTVisitor) line: 72	
	UValidationBuildParticipant$UBuildParticipant.build(IBuildContext) line: 45	
	StructureBuilder.build(String, ISourceModule, AccumulatingProblemReporter) line: 73	
	ReconcileWorkingCopyOperation.makeConsistent(SourceModule, IProblemRequestor) line: 90	
	ReconcileWorkingCopyOperation.executeOperation() line: 58	
	ReconcileWorkingCopyOperation(ModelOperation).run(IProgressMonitor) line: 698	
	ReconcileWorkingCopyOperation(ModelOperation).runOperation(IProgressMonitor) line: 764	
	SourceModule.reconcile(boolean, WorkingCopyOwner, IProgressMonitor) line: 309	
	ScriptReconcilingStrategy.reconcile(ISourceModule, boolean) line: 164	
	ScriptReconcilingStrategy.access$0(ScriptReconcilingStrategy, ISourceModule, boolean) line: 153	
	ScriptReconcilingStrategy$1.run() line: 125	
	SafeRunner.run(ISafeRunnable) line: 42	
	ScriptReconcilingStrategy.reconcile(boolean) line: 120	
	ScriptReconcilingStrategy.reconcile(IRegion) line: 192	
	ScriptCompositeReconcilingStrategy(CompositeReconcilingStrategy).reconcile(IRegion) line: 97	
	ScriptCompositeReconcilingStrategy.reconcile(IRegion) line: 74	
	ScriptReconciler(MonoReconciler).process(DirtyRegion) line: 77	
	AbstractReconciler$BackgroundThread.run() line: 206	


Reproducible: Sometimes

Steps to Reproduce:
1. In more than one thread, call MixinModel.get(String).
2. If the timing is just right, they both attempt to modify existKeysCache or notExistKeysCache simultaneously.
3. If the two attemp a rehash of the map, neither thread completes.
Comment 1 Vladislav Kuzkokov CLA 2011-02-14 06:16:31 EST
After some research I noticed that cache and requestCache are not synchronized either. Maybe it's worth to make synchronization strategy as simple as possible.

As far as I understand, the only long-running operation called from MixinModel is SearchEngine.searchMixinSources, everything else can use one lock. (Because simple things work, and have less overhead)