EhCache instance with name 'play' already exists

时光总嘲笑我的痴心妄想 提交于 2020-01-23 12:34:10

问题


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 Applicaitons), 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!