Deadlock no cache do toplink

5 respostas
TiagoW

Saudações,

Estou enfrentando um problema com uma aplicação web que utiliza o toplink como provedor de persistência.
Gostaria de saber se alguém já enfrentou este problema e qual a melhor solução.

Descrição da aplicação:
Aplicação Web, acessada por diversos escritórios de contabilidade, rodando no servidor Glassfish 2.1, com cerca de 350 usuários.
Não utilizo EJB, cada thread instancia seu proprio entitymanager a partir de um entitymanagerfactory fornecido por um método estático. Tudo gerenciado pela aplicação, sem injeção de dependencia, como é possível no EJB.
A biblioteca JSF utilizada é o RichFaces, com algumas telas ainda usando o apache Tomahawk.

O problema é o seguinte:
Alguns entity possuem instâncias únicas que são acessadas por todos os usuarios da aplicação.
Por exemplo, tenho a classe MesCompetencia, contém diversos parâmetros de cálculo para o mês atual e é o mesmo objeto acessado por todos os usuários.
Aparentemente isto está gerando problemas de acesso concorrente ao cache do toplink.

O servidor não chega a travar, porém, as threads que estão processando as requisições dos usuários simplesmente deixam de responder, aguardando a liberação do cache conforme pode-se notar no Thread Dump obtido no momento do travamento:

"httpSSLWorkerThread-8181-1" - Thread t@80
   java.lang.Thread.State: WAITING
	at java.lang.Object.wait(Native Method)
	- waiting on <5220e2c2> (a oracle.toplink.essentials.internal.helper.ConcurrencyManager)
	at java.lang.Object.wait(Object.java:485)
	at oracle.toplink.essentials.internal.helper.WriteLockManager.acquireLocksForClone(WriteLockManager.java:99)
	at oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl.cloneAndRegisterObject(UnitOfWorkImpl.java:669)
	at oracle.toplink.essentials.internal.sessions.UnitOfWorkIdentityMapAccessor.getAndCloneCacheKeyFromParent(UnitOfWorkIdentityMapAccessor.java:167)
	at oracle.toplink.essentials.internal.sessions.UnitOfWorkIdentityMapAccessor.getFromIdentityMap(UnitOfWorkIdentityMapAccessor.java:105)
	at oracle.toplink.essentials.internal.sessions.IdentityMapAccessor.getFromIdentityMap(IdentityMapAccessor.java:310)
	at oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl.registerExistingObject(UnitOfWorkImpl.java:3087)
	- locked <9569727> (a oracle.toplink.essentials.internal.ejb.cmp3.base.RepeatableWriteUnitOfWork)
	at oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl.registerExistingObject(UnitOfWorkImpl.java:3049)
	- locked <9569727> (a oracle.toplink.essentials.internal.ejb.cmp3.base.RepeatableWriteUnitOfWork)
...

Existem várias threads com lock no mesmo local, daí aparentemente o problema ser de acesso concorrente ao cache do toplink.

Já atualizei os .jar do toplink essentials para a versão mais recente, mas o problema continua.

Alguém já passou por isto e conseguiu resolver este problema?
Outra coisa: Quem já usou o eclipselink pode dizer se ocorre o mesmo tipo de problema?
E por ultimo: Aplicações WEB obrigatoriamente têm de usar EJB para funcionarem bem?

5 Respostas

Flavio_Almeida

Tenho interesse em seu problema.

Tenho dúvida em parte do que você disse:

Se entendi corretamente, MesCompetencia não tem um @Entity, logo não é uma entidade, é um serviço certo?
Se for isso, confirme para mim, esta classe possui um atributo EntityManager ou você cria esse entityManager dentro do método que você evoca?
Quando você diz o CACHE DO TOPLINK, você está se referindo ao cache de primeiro ou segundo nível? Se for de segundo, qual a implementação que você está usando? Alguma do próprio toplink?

TiagoW

Olá,

Se entendi corretamente, MesCompetencia não tem um @Entity, logo não é uma entidade, é um serviço certo?
Se for isso, confirme para mim, esta classe possui um atributo EntityManager ou você cria esse entityManager dentro do método que você evoca?
Quando você diz o CACHE DO TOPLINK, você está se referindo ao cache de primeiro ou segundo nível? Se for de segundo, qual a implementação que você está usando? Alguma do próprio toplink?

Desculpe, mas acho que não me expressei bem.
MesCompetencia é um Entity e tem um annotation @Entity.
Mencionei o EntityManager, apenas para ficar claro que a propria aplicação instanciava um EntityManager sempre que necessário, ao invés de usar injeção de dependencia ( e portanto seria um entitymanager gerenciado pelo container) que é possível quando se usa EJB.

Eis a definição da classe EntityManager, e alguns de seus atributos.

@Entity
public class MesCompetencia implements Serializable {
	private static final long serialVersionUID = -3012120036014653360L;

	@Id
	@SequenceGenerator(name = "MES_COMP_SEQ", allocationSize = 1, sequenceName = "mescompetencia_id_seq")
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MES_COMP_SEQ")
	private long id;
	@Temporal(TemporalType.DATE)
	private Date dataLimiteFechamento;

	@Temporal(TemporalType.DATE)
	private Date inicioPeriodoApuracao;

	@Temporal(TemporalType.DATE)
	private Date finalPeriodoApuracao;

	private int mes;

	private int ano;

Eu não sei se o problema está no cache nivel 1 ou 2. Tudo o que eu tenho para analisar é a thread dump tirada no momento em que o sistema pára. E sempre que isto acontece, existe um lock requisitado pela classe “oracle.toplink.essentials.internal.helper.ConcurrencyManager”, que por dedução eu imagino ser o gerenciamento de acesso ao cache do toplink, onde estão os objetos das entity que já foram carregados do banco.
A referência a classe MesCompetencia é que o sistema sempre trava em operações que envolvem esta entity por isto e pelo fato desta classe ter poucas instancias, compartilhadas por todas as sessões ativas, que eu imagino que o problema é acesso concorrente a instâncias de entity dentro do cache do toplink.

Duas observações que eu esqueci de mencionar antes:

  1. Já tentei desabilitar o cache do toplink e o sistema ficou absurdamente lento.
  2. Existia um bug no toplink essentials que parecia se relacionar com o problema que eu estava tendo, porém este bug está marcado como “resolvido” e mesmo após atualizar os arquivos .jar do toplink o problema continua:
    https://glassfish.dev.java.net/issues/show_bug.cgi?id=3747
Flavio_Almeida

O que eu preciso saber é o seguinte:
Você não usa um EntityManager gerenciado pelo container certo? Você tem um EntityManagerFactory na amplicação e pede EntityManager a esta factory.
Eu preciso olhar o código no qual você obtém o EntityManager e que inclui, atualiza, deleta, etc. sua entidade MesdeCompetencia.

Abraço

TiagoW

Começando do começo então:

Eu tenho uma classe JPAHelper que é responsável por criar e gerenciar o EntityManagerFactory:

public class JPAHelper implements ServletContextListener {

    private static EntityManagerFactory emf;

    public void contextInitialized(ServletContextEvent sce) {
        emf = Persistence.createEntityManagerFactory("persistenceWeb");
    }

    public void contextDestroyed(ServletContextEvent sce) {
        if (emf != null) {
            System.out.println("Fechando EntityManagerFactory (" + getClass().getName() + ".contextDestroyed() )");
            emf.close();
        }
    }

    public static EntityManagerFactory getEntityManagerFactory() {
       if (emf == null) {
            if (JSFHelper.isJSFContext()) {
                emf = Persistence.createEntityManagerFactory("persistenceWeb");
            } else {
                emf = Persistence.createEntityManagerFactory("persistenceSE");
            }
        }
        return emf;

    }
}

Esta classe está registrada como um listener no web.xml.

Todas as regras de acesso a este entity estão de uma classe chamada ArrecadacaoManager:

public class ArrecadacaoManager {
    private EntityManagerFactory emf;
    private EntityManager em;

    ...

    public ArrecadacaoManager() {
        this.emf = JPAHelper.getEntityManagerFactory();
        this.em = getEM();
    }

    public ArrecadacaoManager(EntityManagerFactory emf) {
        this.emf = emf;
        this.getEM();
    }

    public ArrecadacaoManager(EntityManagerFactory emf, EntityManager em) {
        this.emf = emf;
        this.em = em;
    }

    public EntityManager getEM() {
        if (em == null) {
            em = emf.createEntityManager();
        }
        return em;
    }

    public void closeEM() {
        if ((em != null) && (em.isOpen())) {
            em.clear();
            em.close();
        }
        em = null;
    }

    @Override
    protected void finalize() throws Throwable {
        closeEM();
    }

    ...
    public MesCompetenciaPessoa find(int mes, int ano, Pessoa pessoa) {
        StringBuffer sql = new StringBuffer();
        sql.append("select mp ");
        sql.append("  from MesCompetenciaPessoa mp");
        sql.append(" where mp.pessoa = :pPessoa ");
        sql.append("   and mp.mesCompetencia.mes = :pMes ");
        sql.append("   and mp.mesCompetencia.ano = :pAno");

        Query qry = em.createQuery(sql.toString());
        qry.setParameter("pPessoa", pessoa);
        qry.setParameter("pAno", ano);
        qry.setParameter("pMes", mes);
        List result = qry.getResultList();


        if (result.size() > 0) {
            return (MesCompetenciaPessoa) result.get(0);
        } else {
            return null;
        }
    }
    ...
}

O método find() que eu copiei acima é um dos que eu identifiquei onde ocorre o lock.

Este método retorna um objeto MesCompetenciaPessoa, que por sua vez contém um MesCompetencia como atributo:

@Entity
public class MesCompetenciaPessoa implements Serializable {

    private static final long serialVersionUID = -3012120036014653363L;
    @Id
    @SequenceGenerator(name = "MES_COMP_PES_SEQ", allocationSize = 1, sequenceName = "mescompetenciapessoa_id_seq")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MES_COMP_PES_SEQ")
    private long id;

    @Temporal(TemporalType.TIMESTAMP)
    private Date dataHoraEncerramento;


    @ManyToOne
    private MesCompetencia mesCompetencia;


   ...
}

Por que eu acho que o lock está no MesCompetencia e não no MesCompetenciaPessoa?
Por que o objeto MesCompetenciaPessoa é um para cada usuário, enquanto que o MesCompetencia é único para todos os usuários, então é mais provavel que o lock esteja ocorrendo na instância do MesCompetencia do que na instância do MesCompetenciaPessoa.

Por fim, a classe ArrecadacaoManager é usada diretamente nos ManagedBeans do JSF, por exemplo:

public class ArrecadacaoMB {
...
    public String loadLancamentosDoMes() {
        ResumoGeralArrecadacao linha = (ResumoGeralArrecadacao) listaConsulta.getRowData();
        ArrecadacaoManager am = new ArrecadacaoManager();

        setMesPessoaConsulta(am.find(linha.getMes(), linha.getAno(), contribuinte));
        verificarSeMesAberto(am, linha.getMes(), linha.getAno());
        return FORM_LANCAMENTOS;
    }
...

Tudo isso funciona bem, rodando localmente, mas quando vai para produção e começa a ter varios usuários usando ao mesmo tempo, o sistema simplesmente para de responder em algumas operações (sempre as que envolvem esta entity). E nestas situações, ao tirar um dump, verifica-se que existem várias threads paradas mais ou menos no mesmo ponto:

"httpSSLWorkerThread-8181-24" - Thread t@113161
   java.lang.Thread.State: WAITING
	at java.lang.Object.wait(Native Method)
	- waiting on <5220e2c2> (a oracle.toplink.essentials.internal.helper.ConcurrencyManager)
	at java.lang.Object.wait(Object.java:485)
	at oracle.toplink.essentials.internal.helper.WriteLockManager.acquireLocksForClone(WriteLockManager.java:99)
	at oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl.cloneAndRegisterObject(UnitOfWorkImpl.java:669)
	at oracle.toplink.essentials.internal.sessions.UnitOfWorkIdentityMapAccessor.getAndCloneCacheKeyFromParent(UnitOfWorkIdentityMapAccessor.java:167)
	at oracle.toplink.essentials.internal.sessions.UnitOfWorkIdentityMapAccessor.getFromIdentityMap(UnitOfWorkIdentityMapAccessor.java:105)
	at oracle.toplink.essentials.internal.sessions.IdentityMapAccessor.getFromIdentityMap(IdentityMapAccessor.java:310)
	at oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl.registerExistingObject(UnitOfWorkImpl.java:3087)
	- locked <3eeeb41d> (a oracle.toplink.essentials.internal.ejb.cmp3.base.RepeatableWriteUnitOfWork)
	at oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl.registerExistingObject(UnitOfWorkImpl.java:3049)
	- locked <3eeeb41d> (a oracle.toplink.essentials.internal.ejb.cmp3.base.RepeatableWriteUnitOfWork)
	at oracle.toplink.essentials.queryframework.ObjectBuildingQuery.registerIndividualResult(ObjectBuildingQuery.java:339)
	at oracle.toplink.essentials.internal.descriptors.ObjectBuilder.buildWorkingCopyCloneNormally(ObjectBuilder.java:456)
	at oracle.toplink.essentials.internal.descriptors.ObjectBuilder.buildObjectInUnitOfWork(ObjectBuilder.java:421)
	at oracle.toplink.essentials.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:387)
	at oracle.toplink.essentials.queryframework.ReportQueryResult.processItem(ReportQueryResult.java:220)
	at oracle.toplink.essentials.queryframework.ReportQueryResult.buildResult(ReportQueryResult.java:182)
	at oracle.toplink.essentials.queryframework.ReportQueryResult.<init>(ReportQueryResult.java:98)
	at oracle.toplink.essentials.queryframework.ReportQuery.buildObject(ReportQuery.java:594)
	at oracle.toplink.essentials.queryframework.ReportQuery.buildObjects(ReportQuery.java:643)
	at oracle.toplink.essentials.queryframework.ReportQuery.executeDatabaseQuery(ReportQuery.java:804)
	at oracle.toplink.essentials.queryframework.DatabaseQuery.execute(DatabaseQuery.java:628)
	at oracle.toplink.essentials.queryframework.ObjectLevelReadQuery.execute(ObjectLevelReadQuery.java:692)
	at oracle.toplink.essentials.queryframework.ObjectLevelReadQuery.executeInUnitOfWork(ObjectLevelReadQuery.java:746)
	at oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2244)
	at oracle.toplink.essentials.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:952)
	at oracle.toplink.essentials.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:924)
	at oracle.toplink.essentials.internal.ejb.cmp3.base.EJBQueryImpl.executeReadQuery(EJBQueryImpl.java:367)
	at oracle.toplink.essentials.internal.ejb.cmp3.base.EJBQueryImpl.getResultList(EJBQueryImpl.java:478)
Flavio_Almeida

Não terei tempo de olhar seu código agora à tarde, mas verifique se você trabalha como EntityManager por unidade de trabalho.
O que isso significa: que para cada uso do seu EntityManager, você deve fechá-lo e descartá-lo logo em seguida, sempre obtendo outro quando necessário.

Um EntityManager só não precisa ser fechado se você estiver trabalhando em um contexto de conversação ou em outra situação específica que você identificar.

Abraço

Criado 13 de outubro de 2010
Ultima resposta 13 de out. de 2010
Respostas 5
Participantes 2