Caelum | Ensino e Inovação - Cursos de Java, Scrum, Ruby on Rails


A java.net.SocketException Broken Pipe

Por Paulo Silveira em 19/10/09

Quando começamos a programar com banco de dados, rapidamente aprendemos que devemos sempre usar um pool de conexões para acessa-lo, caso contrário podemos facilmente atrapalhar o bom funcionamento do mesmo, devido o excesso de conexões.

Passamos então a usar um pool de conexões, e ao colocar o sistema em produção, nos deparamos com outro problema: o broken pipe:

java.net.SocketException: Broken pipe
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
at java.net.SocketOutputStream.write(SocketOutputStream.java:136)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:2690)
...

No GUJ, são mais de 100 mensagens a respeito de broken pipes! Recentemente Tomaz Lavieri abriu um detalhado tópico sobre esse mesmo assunto, que me incentivou a escrever esse post, dada sua relevância.

Por que essa exception acontece? São dois motivos principais:

O primeiro é que muitos bancos de dados possuem um timeout para conexões inativas (o padrão do MySQL é de 8 horas, mas em alguns hosts isso pode estar configurado em segundos!). Depois de determinado tempo, o banco de dados mata essa conexão ociosa numa tentativa de economizar recursos, pois conclui que alguém simplesmente a esqueceu aberta. Quando, no lado do cliente, seu pool decide usá-la, a socket rapidamente percebe que a conexão foi fechada do outro lado, foi quebrada (daí o nome broken pipe). Muito comum ao começar a usar um pool!

O segundo motivo é que você pode estar tratando suas transações sem o devido cuidado: esquecendo de fazer o commit ou o rollback em alguns casos. O banco de dados então pode matar essa conexão depois de algum timeout de transação, porém o seu pool não sabe disso, e quando for utilizar essa conexão, ela está quebrada!

Solução rápida? Configurar o seu pool para testar se as conexões continuam válidas. No C3P0, pool de conexões que recomendamos fortemente, quando usado com o Hibernate, basta fazer no seu hibernate.cfg.xml:

<property name="hibernate.connection.provider_class">
  org.hibernate.connection.C3P0ConnectionProvider
</property>
<property name="hibernate.c3p0.min_size">1</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.timeout">30</property>
<property name="hibernate.c3p0.idle_test_period">100</property>

É a configuração hibernate.c3p0.idle_test_period que resolve o broken pipe. Nesse caso o C3P0 fara essa verificação de maneira assíncrona: ele cria threads (3 por padrão) que checam de tanto em tanto tempo (100 segundos nesse caso) se alguma das conexões do pool está inválida (broken pipe é um dos casos). Na existência de uma conexão assim, essa será eliminada do seu pool! Você ainda pode ter um azar muito grande, pois uma conexão pode ter algum problema logo depois que a thread a verificou! Se você quer ter uma confiabilidade de 100% em relação a suas conexões, você pode configurar a variável testConnectionOnCheckout no arquivo c3p0.properties que deve ser colocado no seu classpath. Isso não é muito recomendado, pois toda vez que uma conexão é pega do pool, alguma forma de ping será feito no banco de dados para saber se ela é válida, perdendo um pouco de performance.

Vale lembrar de que isso não é motivo para você se descuidar no tratamento de transações, centralizando isso dentro de um interceptador/filtro que faça o uso correto do try, catch e finally, precavendo-se de qualquer vazamento. Transações, assim como qualquer outro recurso caro (arquivos, conexões, sockets, threads, etc…), deve ter seu ciclo de vida tratado com atenção, de preferência de maneira isolada.

Caso você não use Hibernate, Jerônimo Mozer mostra como usar o C3P0 programaticamente.

Mais detalhes podem ser vistos nas configurações de teste de conexões do C3P0, detalhes do seu funcionamento com o Hibernate e a página do próprio Hibernate sobre esse pool, mas que se encontra um pouco defasada. Também vemos muitos detalhes como esses no capítulo de dia a dia com Hibernate do nosso curso FJ-26.

  • Share/Bookmark

Enfrentando a LazyInitializationException no Hibernate

Por Paulo Silveira em 13/10/09

Sem dúvida o primeiro balde de água fria que levamos ao começar a trabalhar com o Hibernate é a LazyInitializationException. Afinal, quando e por que ela acontece?

Para chegar lá precisamos de um exemplo de relacionamento: uma nota fiscal tem vários itens de compra, um produto tem uma categoria:

@Entity
class NotaFiscal {
  
  @OneToMany
  List<Item> items;
}

Depois de mapeadas nossas entidades, podemos facilmente percorrer esse relacionamento através do acesso a um getter:

NotaFiscal nf = (NotaFiscalsession.load(NotaFiscal.class, 42);
List<Item> items = nf.getItems();

Nesse caso o Hibernate fará dois selects: um para pegar os dados da NotaFiscal com id 42, e outro procurando todos os items where nota_fiscal_id=42. Há também a possibilidade de indicar para o Hibernate de que tudo deva ser feito em um único join, para isso basta configurar que esse relacionamento deve ser pego (fetched) de maneira ansiosa, prontamente (eager):

class NotaFiscal {
  ...
  @OneToMany(fetch=FetchType.EAGER)
  List<Item> items;
}

Vale relembrar os defaults do Hibernate: todos os relacionamentos *ToOne são EAGER, e os *ToMany são LAZY, isso porque relações *toMany são provavelmente mais custosas, trazendo mais objetos para a memória.

É muito interessante ter relacionamentos LAZY: os dados são puxados apenas quando realmente necessários. Ao mesmo tempo deve-se tomar cuidado: pode gerar o problema das n+1 queries, e muitas vezes sabemos que tal relacionamento será tão utilizado, que deve ser feito de maneira EAGER, evitando mais uma query ser disparada ao banco de dados.

Como exatamente funciona o relacionamento lazy? O Hibernate tira proveito de proxies dinâmicas: ele te devolve objetos que fingem ser listas nesse caso, e quando você invoca algum método deles, o Hibernate então faz a respectiva query para carregar o relacionamento. E há um momento em que isso falha: quando a sessão que carregou o objeto já estiver fechada, como no seguinte código caso o relacionamento seja LAZY:

Session session = sessionFactory.openSession();
NotaFiscal nf = (NotaFiscalsession.load(NotaFiscal.class, 42);
session.close();

List<Item> items = nf.getItems();
System.out.println("numero de pedidos dessa nota:" + items.size());

Esse código tem o seguinte resultado:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection - no session or session was closed.

Ao invocar o método size() a proxy dinâmica devolvida pelo getItems() tenta se conectar ao banco para puxar todos os itens dessa nota, porém a sessão já foi fechada! Por que o Hibernate não reabre a sessão? Como eles mesmos afirmam, não faria mais sentido ter demarcação de transação e nem de abertura e fechamento de sessões! E essa responsabilidade seria demais para um framework ORM, pois não é tão óbvio decidir sobre o ciclo de vida de objetos caros, como a Session e a Connection, que poderiam acabar sendo abertas inúmeras vezes para a renderização de uma única página! Outros frameworks de mapeamento objeto relacional usam essa outra abordagem e reconectam a sessão e conexão caso necessário, que eu também considero uma má escolha.

Para resolver este caso acima parece simples: sempre fechar a sessão ao término do trabalho. Mas e quando estamos trabalhando na Web? Após pegarmos os dados necessários no nosso controlador, fechamos a sessão e passamos os objetos ao JSP através de atributos. O JSP, ao acessar um getter do seu objeto para fazer um loop, como ${notaFiscal.items}, recebe LazyInitializationException da mesma forma. Em um primeiro momento o desenvolvedor muda o relacionamento para EAGER, mas isso gera uma enorme sobrecarga, pois provavelmente em muitos lugares não era necessário carregar todos os itens da compra sempre que uma determinada nota fiscal é requisitada.

Como então evitar a LazyInitializationException sem modificar o relacionamento para EAGER?

Open Session In View

A solução é manter a session aberta durante a renderização da camada de visualização, ou como o Hibernate chama esse pequeno padrão: open session in view. A idéia é bastante simples: a sessão deve ser mantida aberta até o fim da renderização do JSP (ou de qualquer outra camada de apresentação). Isso pode ser obtido através da implementação de um Servlet Filter, algum tipo de interceptador do seu framework preferido ou até mesmo aspectos.

O Spring foi sem dúvida um dos primeiros frameworks a já trazer classes para isso embutidas (assim como também foi o primeiro a fazer o wrap da HibernateException dentro de uma exceção unchecked, pois até o Hibernate 2.x essa exceção era checked). Há as classes OpenSessionInViewInterceptor e sua análoga OpenEntityManagerInViewInterceptor. No VRaptor, além de você pode usar os componentes embutidos do Spring, já existem componentes que fazem o mesmo trabalho (ver componentes embutidos).

Aproveite para ler aqui no blog da Caelum a respeito de outras exceptions frequentes no Hibernate, sobre os estados de uma entidade na JPA, hábitos importantes para todo desenvolvedor Hibernate e mapeamento de herança, além de muitos outros artigos relacionados ao framework. O nosso curso FJ-26 trata bastante de Hibernate e JSF com detalhes importantes do dia a dia como esses.

  • Share/Bookmark

VRaptor 3.0 final lançado!

Por Lucas Cavalcanti em 05/10/09

vraptor3 icon

Depois de 8 meses de intenso desenvolvimento, e quase 2 meses depois do primeiro beta público, o framework web MVC VRaptor 3 final está disponível para donwload. O site oficial foi inteiramente reformulado, com uma nova versão da palestra de apresentação do framework e uma extensa documentação.

O princípio básico do VRaptor é que você pode expor os métodos do seu controlador de maneira RESTFul, através de simples anotações. No exemplo a seguir, acessando a URI /usuarios/adiciona por POST, teremos esse método insere invocado e um objeto Usuario populado através dos parâmetros usuario.nome, usuario.endereco, assim por diante:


@Post
@Path("usuarios/adiciona")
void insere(Usuario usuario) {
...
}

Se seu retorno não fosse void, o que é retornado é exposto ao seu view, através de um atributo de request. A partir desse simples modelo temos acesso aos mais variados recursos: a injeção de dependências é feita pelo construtor, e há total integração com Spring, permitindo a criação fácil de testes unitários. E há suporte fácil a Hibernate e JPA, através de ComponentFactories já embutidas no framework, basta você registrá-los e receber Session/EntityManager no construtor. Validação, conversores, redirecionamentos, URIs parametrizadas e todo mais prossegue da mesma maneira elegante.

Diversas empresas já estão usando o VRaptor 3 desde suas versões beta: Wine.com.br, a maior empresa online de vinhos do Brasil, através da Giran.com.br, a Locaweb, a AgenciaClick, a Defferrari, entre outros. Há desenvolvedores rodando o VRaptor 3 em um cluster com 32 máquinas e outras no cloud do Google App Engine!

Como começar já?

Faça o download do projeto vazio (blank project) já preparado para o Eclipse, e siga as instruções do guia de 1 minuto! Você está pronto para tirar suas dúvidas no fórum de discussão do GUJ para frameworks brasileiros!

Agradecemos a todos os desenvolvedores do projeto, e em especial aos usuários, que contribuiram no fórum de maneira surpreendente: são mais de 1000 mensagens sobre o novo VRaptor desde sua versão beta 1!

  • Share/Bookmark

Divisions com Hibernate: uso avançado da Criteria API

Por Lucas Cavalcanti em 11/09/08

Existe uma operação, não muito conhecida, mas muitas vezes necessária, em bancos de dados chamada divisão (division). Essa operação representa o seguinte tipo de consulta: Selecione os alunos que fizeram todos os cursos. Selecione os autores em que todos os seus livros têm mais de 200 páginas. E assim por diante.

Esse tipo de consulta precisa de alguns recursos avançados do SQL, então antes de mostrar como implementá-la vamos ver como implementar consultas um pouco mais simples, usando a Subqueries e a DetachedCriteria, que nos possibilitam consultas bastante poderosas usando a api da Criteria.

Bom, vamos começar com três entidades: Aluno, Curso, e um relacionamento de muitos pra muitos entre eles representado pela entidade Matrícula.

Vamos pensar um pouquinho como fazer a seguinte consulta: “Selecionar todos os alunos que estejam cursando Matemática ou Português“. Pensando em banco de dados, podemos fazer um join entre Alunos e Matrículas, e selecionar as linhas em que o curso é matemática ou é português. Precisamos também evitar que a busca retorne alunos repetidos. Vamos fazer isso com Criteria, recebendo a lista dos cursos que eu quero que o aluno esteja cursando algum deles:


public List<Aluno> alunosCursandoAlgumDessesCursos(List<Curso> cursos) {
  Criteria criteria = session.createCriteria(Aluno.class);
  //join com as matrículas
  criteria.createCriteria("matriculas""m");
  
  //usando a disjunction para fazer um 'ou' entre vários elementos
  Disjunction ou = Restrictions.disjunction();
  for (Curso curso : cursos) {
    ou.add(Restrictions.eq("m.curso", curso);
  }
  criteria.add(ou);

  //eliminando resultados repetidos
  criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
  return criteria.list();
}

Ou podemos fazer algo bem mais interessante, que é usar a restrição in, que retorna verdadeiro se a propriedade dada é igual a algum dos elementos da coleção que passarmos pra ela. Nesse caso trocaríamos o Disjunction por simplesmente:

criteria.add(Restrictions.in("m.curso", cursos));

Bem fácil! Agora vamos mudar só um pouquinho a consulta para: “Selecione todos os alunos que estiverem cursando Português E Matemática“. Poderíamos inocentemente mudar a Disjunction para Conjunction no método anterior. Mas isso não funciona! Por quê? Porque se fizermos isso, estaríamos mudando a consulta para algo do tipo: “Selecione os alunos que tenham uma matrícula que é em Português e em Matemática ao mesmo tempo“. E isso não é possível. Temos que mudar essa consulta para algo do tipo: “Selecione todos os alunos para os quais exista uma matricula no curso Português e exista uma matrícula no curso Matemática“.

Existe uma operação em SQL que faz exatamente isso: o exists. Ela retorna verdadeiro se a subconsulta que estiver depois dela retornar algum resultado. Para fazer isso precisamos então criar subconsultas em Criteria, e o jeito de fazer isso é usando a classe Subqueries, que fabrica Criterions que envolvem a criação de subconsultas.

Para usar qualquer método da Subqueries precisamos de uma DetachedCriteria. Essa DetachedCriteria é um tipo especial de Criteria que não precisa da session do hibernate para ser criada. Dentro dela temos acesso a todos os alias e propriedades da Criteria principal, e o uso é o mesmo que faríamos para Criterias normais.

Já que temos a Subqueries na mão, vamos implementar a consulta, recebendo a lista dos cursos que queremos que o aluno esteja matriculado em todos eles:


public List<Aluno> alunosCursandoTodosEssesCursos(List<Curso> cursos) {
  Criteria criteria = session.createCriteria(Aluno.class, "a");
  Conjunction e = Restrictions.conjunction();
  for (Curso c : cursos) {
    e.add(Subqueries.exists(
      DetachedCriteria.forClass(Matricula.class, "m")
        .setProjection(Projections.id())
        .add(Restrictions.eqProperty("a.id""m.aluno.id"))
        .add(Restrictions.eq("m.curso",c))));
  }
  criteria.add(e);
  return criteria.list();
}

Ou seja, queremos que exista uma matrícula do aluno da Criteria principal para cada curso da lista passada.

Mas vamos pensar no seguinte: Essa lista de cursos provavelmente veio de outra consulta no banco, por que não usar essa consulta, ao invés da lista de cursos?! O jeito de fazer isso é usando o operador division que falamos no começo do post. Ele é meio complicado de implementar, pois você tem que pensar meio ao contrário do normal. Por exemplo, para implementar a consulta “Selecione os alunos que estão matriculados em todos os cursos” precisamos transformá-la para: “Selecione os alunos para os quais não exista nenhum curso para o qual não exista matrícula desse aluno para esse curso“, ou seja: um aluno que não exista nenhum curso em que ele não esteja matriculado. É estranho mas é assim mesmo que é feito. A Subqueries também possui o método notExists, então podemos fazer a seguinte consulta, que traz os alunos que fazem todos os cursos:


public List<Aluno> alunosCursandoTodosOsCursos() {
  Criteria criteria = session.createCriteria(Aluno.class, "a");
  criteria.add(Subqueries.notExists(
      DetachedCriteria.forClass(Curso.class, "c")
        .setProjection(Projections.id())
        .add(Subqueries.notExists(
            DetachedCriteria.forClass(Matricula.class, "m")
              .setProjection(Projections.id())
              .add(Restrictions.eqProperty("m.curso.id""c.id"))
              .add(Restrictions.eqProperty("m.aluno.id""a.id")                
        ))
      ));
  return criteria.list();
}

Não é um bicho de sete cabeças, mas também não é nada trivial. O código fica meio poluído por causa das chamadas estáticas, mas se você fizer o import static dos métodos a coisa melhora um pouquinho.

As restrições que você tinha colocado para buscar a lista de cursos dos métodos anteriores, você pode colocar na DetachedCriteria de Cursos, que vai funcionar do jeito que é esperado. Por exemplo: “Selecione os alunos que estejam matriculados em um curso noturno” vira “Selecione os alunos para os quais não exista algum curso noturno em que ele não esteja matriculado“. Mais ainda: você pode colocar restrições pertinentes na DetachedCriteria da matrícula, que também vai funcionar da forma esperada. Por exemplo: “Selecione os alunos que estejam com a matricula paga em todos os cursos” vira “Selecione os alunos para os quais não existe algum curso em que não exista matrícula paga nesse curso“.

Existem muitos casos em que o operador division salva sua vida então, mesmo que ele seja meio complicadinho, é bom saber que ele existe e ter uma boa referência de como implementá-lo =).

Além da Subqueries, existe outra classe muito útil que fabrica Criterions e Projections relacionados a uma propriedade fixa: a Property. Vale a pena olhar o javadoc do hibernate e ver a quantidade de opções de consultas que temos disponíveis. Existe um bug no hibernate que te obriga a setar uma Projection nas DetachedCriterias quando usadas dentro das Subqueries, se isso não é feito o hibernate nos presenteia com uma NullPointerException.

  • Share/Bookmark

Vazamento de memória e de conexões

Por Guilherme Silveira em 02/09/08

Descrevo aqui a minha aventura junto com diversos desenvolvedores da Caelum: Cauê Guerra, Filipe Sabella, Anderson Leite, Pedro Mariano e o Pedro Matiello para resolver um grande problema que ocorria em um dos nossos projetos.

Assim como quando um avião cai, não é um bug que gera um grande problema, mas sim uma série de fatores que contribuem para a dificuldade na hora de encontrar complicações estruturais de um projeto.

Vamos as fatos, que incluem bugs e vazamentos em bibliotecas famosas, profiling e open call hierarchy de eclipse.

Sintoma: o servidor web parava de responder para qualquer página, exceto a tela de login

Nesse caso, o servidor entrava na tela de login mas ao tentar submete-la ou acessar qualquer outra funcionalidade o browser não recebia resposta alguma do servidor web.

A primeira coisa a pensar era que o processador estaria a 100%, que é o caso comum em laços infinitos ou threads gulosas além da conta. Mas um comando rápido no servidor (top) mostrava que o processador estava a 0%. Claro, se estivesse a 100%, teríamos duas opções:

  1. rodar o profiler e detectar o método que estaria consumindo 100% do cpu
  2. imprimir todas as threads do sistema (com Thread.métodos estáticos) e ver o stack trace de cada uma delas… detectaríamos o mesmo ponto.

Mas como nosso problema não era esse, uma possível causa levantada pelo Rafael Cosentino foi de que poderia haver algum deadlock, travando todas as requisições. Pensando na dificuldade de detectar tal falha, e acreditando que o problema estava relacionado com o consumo de memóriaapesar de não haver OutOfMemoryErrors), decidimos usar o JProfiler.

O primeiro passo foi rodar o profiler no Stella Boleto para verificar se ele estava com algum leak, pois sabíamos que o mesmo consome uma memória razoável durante o processo de geração do boleto, devido a imagem de background de alta resolução ser carregada num BufferedImage. Memória a qual deveria ser liberada. Ao rodar o profiler, o Caue Guerra, executou diversas threads em paralelo com invocações complexas do Stella Boletos. Durante e após as execuções, não houve memory leak algum, e o garbage collector consumia os objetos corretamente.

Sem nenhum ponto especial, teríamos que fazer o profiling da aplicação inteira.

Foram várias tentativas com profiling remoto offline, o código java invocava a api de profiling para depois analisarmos os resultados em nossas máquinas, porém um bug do JProfiler com o Apache Tomcat 6.0.16 inviabilizou tal análise. Fomos então rodar a aplicação no Jetty 7 (apontando para a mesma configuração do tomcat) sem que nenhum usuário da aplicação live notasse a mudança: uma migração transparente de servlet containers executada com êxito, assim como ocorreu no GUJ.

Em paralelo, o Tomcat foi atualizado para 6.0.18, que funciona com a última versão do JProfiler corretamente e levantamos o mesmo com apenas 64 megas de ram (para estressar o garbage collector mais frequentemente). Rodando o Tomcat em outra porta, mas acessando o mesmo banco de produção, permitiu que testássemos junto ao cliente exatamente as funcionalidade que ele julgava “lentas”.

Em algum ponto durante o uso do JProfiler, o mesmo indicou que não havia nenhum deadlock de threads, bastando para ele verificar se existem threads esperando por monitores de maneira cruzadas.

Primeiro sinal de leak: Executamos algumas tarefas no sistema e vimos que o número de objetos crescia de maneira adequada, porém, ao rodar o GC, alguns objetos ficavam para trás… objetos que são entidades do projeto! É comum o número de Strings, arrays de char e de bytes crescerem, mas entidades do nosso próprio sistema? E agora? Por que isso acontece? Alguém continuou mantendo referências para objetos do projeto que não deveriam estar referenciados. Novamente o profiler vai ajudar.

Segundo sinal de leak: Cutucando mais um pouco percebemos que algumas Sessions do hibernate tambem não estavam sendo fechadas nem coletadas! Essa costuma ser uma forte indicação de vazamento de conexão. E no caso do Hibernate, isso poderia vir a gerar mais leak ainda, dado que muitas entidades ficariam presas no cache de primeiro nível, já que a sessão continuava aberta.

Objetos do hibernate apos o gc

Mas olhando a fundo, percebemos que a maior parte dessas sessões, exceto uma, estava injetada em objetos de modelo na sessao web. Isto é, são sessões usadas pelos nossos Repositories, que faz todo o sentido para o projeto e não é um memory leak, pois a sessão que era referenciada deveria estar fechada e com isso os objetos de cache de primeiro nível dele, coletados.

Sendo assim, partimos para testar funcionalidades que acreditavamos ter consumo grande de memória.

Primeiro sinal de consumo excessivo de memória: Encontramos uma funcionalidade que levantava cerca de 40 mil objetos na memória. Com isso, o consumo era de cerca de 40 megas de objetos só nesse select… mas o Tomcat estava configurado para 64 megas? O que acontecia? O consumo de memória aumentava, o garbage collector era invocado de sua maneira parcial, até que o consumo chegava ao topo, quando garbage collector full era rodado, pedindo mais memória para o sistema operacional.

Mas 64 ram não era espaço suficiente, então rodava o garbage collector full novamente, passando por todos os objetos da memória novamente, liberando mais um pouco e deixando um pouco mais de espaço, mas ainda não o suficiente. Cada vez mais, em intervalos de poucos segundos, o garbage collector era rodado e liberava menos espaço novo do que antes, tudo isso ainda na mesma requisição que usaria 40 megas… até que os espaços são tão pequenos que o processador fica a 100% de processamento, até acabar a memória e lançar o OutOfMemoryError. Nesse meio tempo, o servidor fica praticamente sem dar respostas, dado o alto uso de CPU.

Garbage collector rodando apos requisicao que criava diversos objetos

Garbage collector rodando apos requisicao que criava diversos objetos: note o garbage collector full rodado no final.

 Ok, encontramos um problema. Solução: filtrar esses 40 mil objetos para trazer menos objetos para a memória de uma vez só. No nosso caso uma simples busca por esses objetos foi o suficiente. O cliente não queria paginação e já havia recusado a paginação que havia sido feita no Sprint anterior.

Em outros casos, poderiamos usar resultados do hibernate que armazenam uma referencia somente para o objeto atual da lista, podendo garbage coletar os anteriores (uma StatelessSession, conforme discutido nos 7 hábitos dos desenvolvedores Hibernate altamente eficazes).

Esse ainda não era o grande problema, pois o nosso sintoma travava o servidordeixando o processador a 0%, e não a 100%.

Memory leak temporário encontrado: Voltando ao problema de leak anterior: o profiler mostrou que alguns objetos que estavam sobrando e não sendo garbage coletados. Com um pouco mais de procura, um heap dump mostra todas as referencias de um determinado objeto e, procurando quem referenciava aqueles que não iam para o garbage collector, encontramos que a implementação padrão do grupo Apache para a JSTL na tag c:forEach mantinha uma referencia para o último objeto iterado. Oops. Como assim?
 

c:forEach leak

c:forEach leak

A tag c:forEach, para seguir a especificação da JSTL, mantem uma referência para o último objeto iterado, mesmo após a iteração (as tags ficam em um pool do servidor de aplicação e são recicladas). Por isso, a referência era fixa e todo aquele pedaco da árvore de referÊncia dos objetos não poderia ser coletado até o proximo c:forEach usar aquela instância do pool novamente.

E agora o que isso pode causar?

Primeiro, as instâncias da tag só vão manter a referência por um tempo determinado. Quando utilizadas novamente, aquela árvore de objetos poderá ser coletada… o único problema aconteceria com um número muito grande de usuários executando uma query no servidor ao mesmo tempo. Isso não costuma acontecer em projetos de 1000 usuários, portanto não é uma preocupação no momento.

Novamente não resolvemos o problema principal, mas achamos mais um detalhe importante sobre uma biblioteca que utilizamos no dia a dia sem medo. Solução: não se preocupar pois o projeto não terá um número tão grande de usuários simultaneos para causar algum problema. Continuar usando a JSTL.

Por fim, as estatísticas do Hibernate Session Factory mostravam que raramente uma conexão se perdia. Após mais de 5000 conexões estabelecidas, três estavam abertas. Isto é, a conexão atual, e mais duas… As estatísticas do hibernate é uma das maneira mais simples de obter resultados sobre leaks de conexões, queries mal escritas ou possíveis pontos de bom uso do cache de segundo nível. Oolhando as estatísticas, confirmamos o session leak (connection leak):

c:forEach leak enquanto não apontar para outro objeto

session leak

Session leak é um dos erros mais comuns em projetos com o Hibernate. Infelizmente encontramos muitos projetos que acessam conexões em métodos estáticos, sem controle algum de quem invoca esse método e de quem libera essa sessão. Nesse projeto, o acesso a camada ORM era feita através de um Interceptador, como no Open Session in View, que é a maneira que mais gostamos de trabalhar, pois dificulta session leaks e facilita a criação de testes para seu código, já que será usado alguma maneira de inversão de controle.

Causa encontrada: Usando o Open Call Hieranchy do Eclipse, encontramos rapidamente 3 referências para abertura de sessões que não fecham ela de maneira adequada (com try/finally mal escritos)…

Solução: Abrir e fechar sessões corretamente!

Mas o que um connection leak pode causar no meu projeto? Tudo depende da configuração do seu connection pool. No caso do c3p0, o mais famoso e amplamente utilizado, existem algumas configurações que você pode fazer como, por exemplo, o número minimo de conexões no pool, c3p0.minPoolSize.

Porem, ao usar hibernate essa propriedade muda para hibernate.c3p0.min_size. Note que o hibernate
decidiu não seguir o padrão! Em uma conversa com o Diego Plentz, commiter do Hibernate e colaborador da Caelum, não encontramos rapidamente um motivo pelo qual o padrão não foi seguido. Talvez algum problema de conflito de propriedades configuradas em um mesmo arquivo, ou talvez para manter compatibilidade com o Hibernate 2.

O que acontecia nesse caso específico? A aplicação usava algumas configurações corretamente, como o minimo e máximo de conexões (nos testes de integração e de aceite, o máximo era 3). Mas uma outra configuração importantíssima estava escrita de maneira errada dentro de uma String do XML: configurar o tempo máximo que uma conexão que não está sendo usada seja eliminada, justamente para ignorar leaks. Algo como: se a conexão está idle, por, digamos, 1 minuto, considere que foi um leak, e traga ela de volta para o pool.

Causa encontrada: Como exatamente essa configuração estava sendo usada no valor errado, junto com o memory leak da implementação da JSTL, após a funcionalidade com session leak acontecer 3 vezes nos testes de integração, não existia mais nenhuma conexão disponível. O mesmo acontecia em produção com um número maior de vezes. Então as threads ficavam esperando uma conexao do connection pool.

Isso podia ser visto na visualização de threads do profiler: a cada nova requisição com o servidor sem responder, uma nova thread era criada pelo tomcat para atender a requisição, mas essa thread ficava em WAITING, isto é, ela estva esperando uma conexão ser liberada. Pela view de monitor era possível
verificar que ela aguardava algo do c3p0… a conexão… pronto! 

Threads aguardando monitores/sleeping etc

Threads aguardando monitores/sleeping etc

Encontramos o problema… as threads ficavam paradas (0% de processador) esperando por conexões que nunca chegariam (WAITING)! Um problema que veio por causa da configuração do c3p0 com o problema do connection leak. A solução é composta por duas tarefas: corrigir o session/connection leak e corrigir a configuração do c3p0. Encontramos facilmente o erro graças aos testes de integração.

Ufa! Prontos para o próximo desafio…

  • Share/Bookmark



Caelum | Ensino e Inovação
São Paulo: Rua Vergueiro, 3185, cj. 87, próximo ao Metrô Vila Mariana   |   Tel. (11) 5571-2751
Rio de Janeiro: Rua Senador Dantas, 80, cj. 307/308 - Centro   |   Tel. (21) 2220-4156 ou 2297-0033
Brasília: SCS Qd. 8 Bl. B-50, Sala 521 - Ed. Venâncio 2000   |   Tel. (61) 3039-4222