问题
I faced the issue with the Play framework default cache (EHCache) when working with asynchronous couchdatabase java driver. Play crashes on the hot reload with the error:
Error in custom provider, play.api.cache.EhCacheExistsException: An EhCache instance with name 'play' already exists.
I found this could be not only with the couchdatabase driver but also in some other scenarios, like https://groups.google.com/forum/#!topic/pac4j-dev/2_EUOCrov7M.
回答1:
I figure out a solution - force cache shutdown on the stop hook. It could be done in one of an existent module in your project like:
lifecycle.addStopHook(() -> {
...
CacheManager.getInstance().shutdown();
...
});
Special "fix" module could be created as well:
package fixes;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.inject.AbstractModule;
import net.sf.ehcache.CacheManager;
import play.Logger;
import play.Logger.ALogger;
import play.inject.ApplicationLifecycle;
/**
* Fix for the hot reloading cache issue.
* "Error in custom provider, play.api.cache.EhCacheExistsException: An EhCache instance with name 'play' already exists."
*
*/
public class CacheFix extends AbstractModule{
@Override
protected void configure() {
bind(CacheFixInstance.class).asEagerSingleton();
}
}
/**
* Only stop hook
*/
@Singleton
class CacheFixInstance {
private static ALogger logger = Logger.of(CacheFixInstance.class);
@Inject
public CacheFixInstance(ApplicationLifecycle lifecycle) {
lifecycle.addStopHook(() -> {
// Force cache to stop.
CacheManager.getInstance().shutdown();
logger.debug("Cache has been shutdown");
// Nothing to return.
return CompletableFuture.completedFuture(null);
});
}
}
In application.conf
:
enabled += fixes.CacheFix
回答2:
In case some look to a quick copypaste fix here is the scala version I translated from Andriy Kuba's answer
package utils
import javax.inject.{Inject, Singleton}
import com.google.inject.AbstractModule
import net.sf.ehcache.CacheManager
import play.api.Logger
import play.api.inject.ApplicationLifecycle
import scala.concurrent.{ExecutionContext, Future}
/**
* Fix for the hot reloading cache issue.
* "Error in custom provider, play.api.cache.EhCacheExistsException: An EhCache instance with name 'play' already exists."
*
*/
class CacheHotReloadFix extends AbstractModule {
override protected def configure(): Unit = {
bind(classOf[CacheHotReloadFixInstance]).asEagerSingleton()
}
}
@Singleton
class CacheHotReloadFixInstance @Inject() (lifecycle: ApplicationLifecycle, implicit val executionContext: ExecutionContext) {
private val logger = Logger(this.getClass)
lifecycle.addStopHook { () =>
logger.debug("Forching ehcach to stop before play reloads")
// Force cache to stop.
Future(CacheManager.getInstance().shutdown())
}
}
回答3:
If you are having trouble while running the test cases (where there will be multiple Applicaiton
s), you can have a dummy implementation of the SyncCacheApi
and AsyncCacheApi
and override the bindings while creating the Application
via provideApplication()
@Override
protected Application provideApplication() {
Application application = new GuiceApplicationBuilder().configure(testConfig)
.disable(EhCacheModule.class)
.overrides(bind(SyncCacheApi.class).to(FakeSyncCacheApi.class))
.bindings(new MyModule())
.in(new File(".")).build();
return application;
}
and the sample FakeSyncCacheApi
would be something like
@Singleton
public class FakeSyncCacheApi implements SyncCacheApi {
private LRUMap cache = new LRUMap();
@Override
public <T> T get(String key) {
return (T) cache.get(key);
}
@Override
public <T> T getOrElseUpdate(String key, Callable<T> block, int expiration) {
return getOrElseUpdate(key, block);
}
@Override
public <T> T getOrElseUpdate(String key, Callable<T> block) {
T value = (T) cache.get(key);
if (value == null) {
try {
value = block.call();
} catch (Exception e) {
}
cache.put(key, value);
}
return value;
}
@Override
public void set(String key, Object value, int expiration) {
cache.put(key, value);
}
@Override
public void set(String key, Object value) {
cache.put(key, value);
}
@Override
public void remove(String key) {
cache.remove(key);
}
}
The idea here is to disable the EhCache
module and have our own dummy implementation.
回答4:
Here is my Scala version based on Andriy Kubas version that works with Play 2.5.6. Remember that Play 2.5.3 has a bug, so stophooks doesn't work.
package modules
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import com.google.inject._
import net.sf.ehcache.CacheManager;
import play.api.Logger
import play.api.inject.ApplicationLifecycle
import play.api.{ Configuration, Environment, Mode }
import play.api.inject.{ Module => PlayModule }
class CacheModule extends PlayModule {
def bindings(environment: Environment, configuration: Configuration) = Seq(
bind[CacheFixInstance].toSelf.eagerly
)
}
@Singleton
class CacheFixInstance @Inject()(lifecycle: ApplicationLifecycle) {
val logger = Logger(this.getClass)
lifecycle.addStopHook { () =>
logger.info("CacheInstance stopped")
Future.successful(CacheManager.getInstance().shutdown())
}
}
and ofcourse play.modules.enabled += "modules.CacheModule"
回答5:
For me adding a shutdown hook did not work (maybe not fast enough, I don't know). Instead I'd just shutdown the CacheManager at the end of each test suite and run them sequentially:
override def afterAll(): Unit = {
CacheManager.getInstance().shutdown()
}
回答6:
My solution to the problem for Scala and Play 2.6 based on Andriy Kuba answer.
First it is required to include javaCore
module in build.sbt
to be able to import play.api.inject.ApplicationLifecycle
libraryDependencies += javaCore
Then create a new class CacheFixInstance.scala
:
package util
import javax.inject.Inject
import net.sf.ehcache.CacheManager
import play.api.Logger
import play.api.inject.ApplicationLifecycle
import scala.concurrent.Future
class CacheFixInstance @Inject()(lifecycle: ApplicationLifecycle) {
private val logger = Logger(getClass)
lifecycle.addStopHook { () =>
logger.info("CacheInstance stopped")
Future.successful(CacheManager.getInstance().shutdown())
}
logger.info(s"Hot reload EHCache fix initialized.")
}
and then add it to your module configuration or alternativelly use @singleton
annotation to CacheFixInstance
class:
bind(classOf[CacheFixInstance]).asEagerSingleton()
来源:https://stackoverflow.com/questions/43243293/ehcache-instance-with-name-play-already-exists