Skip to content
This repository has been archived by the owner on Apr 21, 2023. It is now read-only.

Fixed the synchronization strategy in ExecutorServiceProvider #1470

Merged
merged 1 commit into from
May 8, 2020

Conversation

nbhusare
Copy link
Contributor

@nbhusare nbhusare commented May 4, 2020

PS #1442 (comment) for details regarding this fix.

Signed-off-by: nbhusare [email protected]

@cdietrich cdietrich added this to the Release_2.22 milestone May 4, 2020
@cdietrich cdietrich requested a review from szarnekow May 4, 2020 10:47
@cdietrich
Copy link
Member

this seems to break a lot of tests

@szarnekow
Copy link
Contributor

java.lang.ArrayIndexOutOfBoundsException: 16965
	at com.google.inject.internal.asm.$ClassReader.<init>(Unknown Source)
	at com.google.inject.internal.asm.$ClassReader.<init>(Unknown Source)
	at com.google.inject.internal.asm.$ClassReader.<init>(Unknown Source)
	at com.google.inject.internal.util.$LineNumbers.<init>(LineNumbers.java:62)

Wow. Compiler bug? ASM bug? Both?

@miklossy
Copy link
Contributor

miklossy commented May 4, 2020

Could this failure be related to Guice 3.0 is incompatible with Java 8? See #393

@miklossy
Copy link
Contributor

miklossy commented May 4, 2020

But the stacktrace there is really similar:

Caused by: java.lang.ArrayIndexOutOfBoundsException: 34949
	at com.google.inject.internal.asm.$ClassReader.<init>(Unknown Source)
	at com.google.inject.internal.asm.$ClassReader.<init>(Unknown Source)
	at com.google.inject.internal.asm.$ClassReader.<init>(Unknown Source)
	at com.google.inject.internal.util.$LineNumbers.<init>(LineNumbers.java:62)
	at com.google.inject.internal.util.$StackTraceElements$1.apply(StackTraceElements.java:36)
	at com.google.inject.internal.util.$StackTraceElements$1.apply(StackTraceElements.java:33)
	at com.google.inject.internal.util.$MapMaker$StrategyImpl.compute(MapMaker.java:549)

@cdietrich
Copy link
Member

error message when tested with guice 4

java.util.concurrent.ExecutionException: com.google.inject.ProvisionException: Unable to provision, see the following errors:

1) Error in custom provider, java.lang.NullPointerException
  while locating org.eclipse.xtext.ide.ExecutorServiceProvider
  while locating java.util.concurrent.ExecutorService
    for field at org.eclipse.xtext.ide.server.contentassist.ContentAssistService.executorService(ContentAssistService.java:54)
  at org.eclipse.xtext.ide.server.contentassist.ContentAssistService.class(ContentAssistService.java:54)
  while locating org.eclipse.xtext.ide.server.contentassist.ContentAssistService

1 error
	at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
	at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1908)
	at org.eclipse.xtext.testing.AbstractLanguageServerTest.testCompletion(AbstractLanguageServerTest.java:1105)
	at org.eclipse.xtext.ide.tests.server.CompletionTest.testCompletion_01(CompletionTest.java:72)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:542)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:770)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: com.google.inject.ProvisionException: Unable to provision, see the following errors:

1) Error in custom provider, java.lang.NullPointerException
  while locating org.eclipse.xtext.ide.ExecutorServiceProvider
  while locating java.util.concurrent.ExecutorService
    for field at org.eclipse.xtext.ide.server.contentassist.ContentAssistService.executorService(ContentAssistService.java:54)
  at org.eclipse.xtext.ide.server.contentassist.ContentAssistService.class(ContentAssistService.java:54)
  while locating org.eclipse.xtext.ide.server.contentassist.ContentAssistService

1 error
	at com.google.inject.internal.InternalProvisionException.toProvisionException(InternalProvisionException.java:226)
	at com.google.inject.internal.InjectorImpl$1.get(InjectorImpl.java:1097)
	at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1131)
	at org.eclipse.xtext.resource.impl.DefaultResourceServiceProvider.get(DefaultResourceServiceProvider.java:75)
	at org.eclipse.xtext.ide.server.LanguageServerImpl.getService(LanguageServerImpl.java:938)
	at org.eclipse.xtext.ide.server.LanguageServerImpl.getService(LanguageServerImpl.java:921)
	at org.eclipse.xtext.ide.server.LanguageServerImpl.completion(LanguageServerImpl.java:545)
	at org.eclipse.xtext.ide.server.LanguageServerImpl.lambda$24(LanguageServerImpl.java:536)
	at org.eclipse.xtext.testing.AbstractLanguageServerTest$DirectRequestManager.runRead(AbstractLanguageServerTest.java:161)
	at org.eclipse.xtext.ide.server.LanguageServerImpl.completion(LanguageServerImpl.java:536)
	at org.eclipse.xtext.testing.AbstractLanguageServerTest.testCompletion(AbstractLanguageServerTest.java:1104)
	... 26 more
Caused by: java.lang.NullPointerException
	at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936)
	at org.eclipse.xtext.ide.ExecutorServiceProvider.get(ExecutorServiceProvider.java:50)
	at org.eclipse.xtext.ide.ExecutorServiceProvider.get(ExecutorServiceProvider.java:46)
	at org.eclipse.xtext.ide.ExecutorServiceProvider.get(ExecutorServiceProvider.java:1)
	at com.google.inject.internal.ProviderInternalFactory.provision(ProviderInternalFactory.java:85)
	at com.google.inject.internal.BoundProviderFactory.provision(BoundProviderFactory.java:77)
	at com.google.inject.internal.ProviderInternalFactory.circularGet(ProviderInternalFactory.java:59)
	at com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:61)
	at com.google.inject.internal.SingleFieldInjector.inject(SingleFieldInjector.java:52)
	at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:147)
	at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:124)
	at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:91)
	at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:306)
	at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
	at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:168)
	at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:39)
	at com.google.inject.internal.InjectorImpl$1.get(InjectorImpl.java:1094)
	... 35 more

@cdietrich
Copy link
Member

ConcurrentHashMap does not allow null as keys

*/
@Singleton
public class ExecutorServiceProvider implements Provider<ExecutorService>, IDisposable {

private final Map<String, ExecutorService> instanceCache = new ConcurrentHashMap<>(3);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that ConcurrentHashMap does not allow null-keys or null-values, it may also be ok to use Collections.synchronizedMap here, since I don't expect a lot of pressure on the lookup of an ExecutorService.

get(String key) should be rewritten to use computeIfAbsent(..).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dispose must sync on the map, too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get(String key) should be rewritten to use computeIfAbsent(..).

I can think of two ways; however, I am not convinced by either. In the first one, calling computeIfAbsent(...) inside the if (result == null) doesn't make much sense. We already know that the value is null before calling computeIfAbsent(...). The second one would block threads even if the map has a non-null value for the key requested by the second thread.
@szarnekow Please suggest if there is any other way to rewrite this method.

ExecutorService result = instanceCache.get(key);                            
if (result == null) {                                                       
	synchronized (instanceCache) {                                          
		result = instanceCache.computeIfAbsent(key, k -> createInstance(k));
	}                                                                       
}                                                                           
return result; 
synchronized (instanceCache) {                                         
	return instanceCache.computeIfAbsent(key, k -> createInstance(k)); 
} 

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Steps:

  1. Use a Collections.synchronizedMap(new HashMap<>()) for the field initializer
  2. Rewrite and simplify get(String) towards public ExecutorService get(String key) { return instanceCache.computeIfAbsent(key, this::createInstance)); }
  3. fix dispose to sync on the instanceCache, too: public void dipose() { synchronized(instanceCache) { .. } }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@szarnekow computeIfAbsent(...) is not atomic. Don't we have to sync on the map?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SynchronizedMap.computeIfAbsent(K, Function<? super K, ? extends V>) is safe.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, tx for clarification.

Copy link
Contributor

@szarnekow szarnekow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@szarnekow szarnekow merged commit 75e24b1 into eclipse:master May 8, 2020
@szarnekow
Copy link
Contributor

Thank you, @nbhusare!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants