Remover conexões EM ESPERA ao fazer undeploy no tomcat [RESOLVIDO]

41 respostas
erico_kl

Olá pessoal!

Estamos com um problema aqui na empresa quando efetuamos undeploy de aplicações no servidor. Trabalhamos com sistemas desktop + web service e precisamos remover todas as conexões do pool quando for feito undeploy no tomcat.
Antes a gente tinha o pool configurado via aplicação porém agora estamos com o pool configurado no tomcat e mesmo assim as conexões não são removidas.
Vou dar um exemplo do que acontece: fizemos deploy de um arquivo .war (que no nosso caso é o web service) no servidor e acessamos a aplicação. O pool funciona de forma eficaz, respeitando todas as configurações passadas no context.xml. Aí atualizamos a aplicação e fizemos undeploy da aplicação já existente e fizemos um novo deploy (isso tudo sem reiniciar o tomcat, pois não podemos reiniciá-lo a cada deploy). Todas as conexões do pool que ficaram em espera, continuam lá… ou seja… não são liberadas do pool após o undeploy.

Como faço para resolver esse problema?

Obs.: já tentamos de tudo… e removeAbandoned=“true” não adianta…

Obrigado pela ajuda de todos

41 Respostas

FernandoFranzini

esse pool ai é por war ou global do tomcat? nas minhas app os pools são por war e o pool é destruido no undploy.

erico_kl

é configurado no war… no context.xml e no web.xml… antes era só na aplicação mas de nenhuma das formas as conexões são liberadas

erico_kl

o estranho é que mesmo configurado todo o pool pelo tomcat, ao fazer undeploy as conexões continuam com o estado “IDLE”… e são essas as conexões que teriam que ser removidas… porém não são, e até que o tomcat seja restartado, todas conexões ficarão lá, mesmo que nunca sejam acessadas…

FernandoFranzini

Oi erico…
Estranho isso…vou verificar aqui no meu projeto…

FernandoFranzini

Oi Erico

Acabei de fazer o teste…e realmente o tomcat deixa pendurado sim…ele só remove as conexões do pool quando o servidor é parado.
Mas tenho usado o HOT-DEPLOY ha anos e nunca tive problemas…
No seu próximo deploy da mesma aplicação ele vai usar o pool ja aberto…normal
Qual o problema disso para vc?

erico_kl

aí é que está, no hot-deploy ele não ocupa essas conexões em espera…
digamos que eu tenha um máximo de 5 conexões em espera e todas elas já criadas e aguardando acessos, aí faço o undeploy e o deploy da aplicação e aí quando o sistema é aberto novamente o tomcat cria outras 5 novas conexões (deixando aquelas outras 5 em espera ainda que nunca serão usadas…)

e aí conforme vou fazendo deploys, as conexões vao aumentando sempre, e logo estoura o limite máximo de conexões do banco…

obrigado pela ajuda, Fernando

FernandoFranzini

Vou testar aqui no ambiente de homologação para ver…

erico_kl

ok… muito obrigado pela ajuda!

Fico no aguardo e qualquer avanço que eu tiver eu posto aqui também…

FernandoFranzini

Nâo deu tempo de eu fazer…mas acho que unica forma do pool ficar pendura no undeploy é quando no próprio undeploy acontecer exceptions…
Vc ja verificou se log depois do undeploy?
Normalmente acontece por falta de jar’s, etc…

erico_kl

o log do tomcat informa o seguinte no undeploy:

30/05/2011 17:37:32 org.apache.catalina.loader.WebappClassLoader clearThreadLocalMap GRAVE: A web application created a ThreadLocal with key of type [org.apache.axis.utils.XMLUtils.ThreadLocalDocumentBuilder] (value [org.apache.axis.utils.XMLUtils$ThreadLocalDocumentBuilder@66a96863]) and a value of type [org.apache.xerces.jaxp.DocumentBuilderImpl] (value [org.apache.xerces.jaxp.DocumentBuilderImpl@7c04703c]) but failed to remove it when the web application was stopped. To prevent a memory leak, the ThreadLocal has been forcibly removed. 30/05/2011 17:37:33 org.apache.catalina.startup.HostConfig checkResources INFO: Undeploying context [/Aplicacao]
esta mensagem é a mesma para todos os undeploys feitos…

FernandoFranzini

O log esta ok sim…
Eu to achando estranho ele duplicar o pool a cada novo deploy…
Temos que descobrir o pq disso…
Estou montando um ambiente teste aqui na minha maquina para eu testar esse comportamento no meus war’s aqui.
Depois te dou um retorno… :wink:

erico_kl

bah… muito obrigado pela ajuda e atenção…
fico no aguardo e enquanto isso vou fazendo mais testes aqui…

valeu!

FernandoFranzini

Ola Erico

Consegui descobrir seu problema...vou tentar te explicar rapidamente....
O tomcat realmente não fecha as conexões do datasource por 3 motivos:

1. A especificação do web container não diz que o provedor de web container tem que fechar as conexões do datasouce.
2. O tomcat não pode fechar pq ele não esta amarrado com nenhum provedor de datasource, mesmo ele venha configurado com o commons dbpc da apache, cada um esta livre para usar qualquer outro.
3. O commons dbpc não libera as conexões no undeploy.

Nessa thread aqui https://issues.apache.org/bugzilla/show_bug.cgi?id=37262 é discutido justamente isso...e os administradores do tomcat disseram que não podem ficar colocando código proprietário para fechar o pool do provedor A, B, C, etc...alegando que o produto TOMCAT não esta e não ira ficar amarrado com nenhum provedor de datasouce especifico....ficando então a cargo do desenvolvedor da aplicação se responsabilidar por isso (uma vez que ele é o responsavel por escolher o seu provedor)

Então vc tem 2 caminhos para resolver seu problema...ou colocar um provedor de pool que libere as conexões para vc ou fazer na unha mesmo pq o commons não faz.
A solução é fácil usando o próprio commons mesmo...é so liberar as conexões no metodo destroy de um listener de contexto servlet. Eu ja testei e deu certo:

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.tomcat.dbcp.dbcp.BasicDataSource;

public class ContextoListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent ev) {
    }
    
    public void contextDestroyed(ServletContextEvent ev) {
		try {
			Context ic = new InitialContext();
			Context CONTEXT = (Context) ic.lookup("java:/comp/env");
			BasicDataSource ds = (BasicDataSource) CONTEXT.lookup("<NOME_DO_SEU_JNDI>");
			ds.close();
		} catch (Exception e) {
			e.printStackTrace();
		} 
    }    
}
Qualquer dúvida estou por aqui. T+ :wink:
erico_kl

novamente muito obrigado pela ajuda! Esse é um problema que estamos enfrentando a um tempo aqui e ainda não tínhamos a solução porém agora com tua ajuda estamos perto de resolver.
Só acho que não entendi muito bem… onde ficaria esse ContextoListener? Quando ele seria executado?

Porque hoje tenho um método que retorna o dataSource:

private DataSource createDataSource(){
   try {
      InitialContext context = new InitialContext();
      dataSource = (DataSource)context.lookup("java:comp/env/"+getValue(JNDI_NAME));
      return dataSource;
   } catch (Exception e) {
      e.printStackTrace();
   }
   return null;
}

a destruição do dataSource ocorreria manualmente no método que utiliza este objeto ou na hora de devolver a conexão ao pool depois de utilizada?

códigos auto-explicativos abaixo:

/**
	 * retorna uma conexao do pool
	 */
	public Connection getConnection() {
		try {
			return dataSource.getConnection();
		} catch (SQLException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * devolve a conexao ao pool
	 */
	public void releaseConnection(Connection con) {
		if (con != null) {
			try {
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
				con = null;
			}
		}
	}

mais uma vez muito obrigado! Sua ajuda está sendo fundamental pra nós.

FernandoFranzini

Não…
Estamos falando de fechar todas as conexões do pool (não é devolver uma conexão bloqueada para uso). Devolver a conexão reservada para uso vc continua fazendo o indicado pela especificação…vc pega a conexão via datasouce.getConnection()…usa nos selects, inserts etc e devolve con.close()…isso continua o mesmo
Vc vai adicionar um listener que vai executado no deploy do war e no undeploy…
Siga ai o manual…

  1. Escreve a classe que eu sugeri, colocando seus valores de nomes JNDI
  2. Declara no web.xml o listener o nome da classe totalmente qualificado:

<listener> <listener-class> pacote.pacote.seupacote.ContextoListener </listener-class> </listener>

erico_kl

hmm… claro, pois aí ele vai liberar todas as conexões quando um deploy ou undeploy for realizado… acho que estou entendendo…

vou implementar aqui e aí posto o resultado…

Muito Obrigado, Fernando!

erico_kl

cara eu tentei aqui e não deu certo, as conexões continuam lá…

tive que importar 2 bibliotecas do tomcat pois o projeto padrão não continham as classes importadas nesta classe q vc montou. Porém o log do tomcat apresenta um erro estranho dizendo que um dos jars que eu importei não foi possível carregar :confused:
segue o log:

31/05/2011 17:58:48 org.apache.catalina.startup.HostConfig deployWAR
INFO: Deploying web application archive indumoveis.war
31/05/2011 17:58:49 org.apache.catalina.loader.WebappClassLoader validateJarFile
INFO: validateJarFile(/var/lib/tomcat6/webapps/indumoveis/WEB-INF/lib/servlet-api.jar) - jar not loaded. See Servlet Spec 2.3, section 9.7.2. Offending class: javax/servlet/Servlet.class
31/05/2011 17:58:49 org.apache.axis.configuration.EngineConfigurationFactoryServlet getServerEngineConfig
GRAVE: Unable to find config file.  Creating new servlet engine config file: /WEB-INF/server-config.wsdd
31/05/2011 17:59:19 org.apache.axis.configuration.EngineConfigurationFactoryServlet getServerEngineConfig
GRAVE: Unable to find config file.  Creating new servlet engine config file: /WEB-INF/server-config.wsdd
java.lang.ClassCastException: org.apache.commons.dbcp.BasicDataSource cannot be cast to org.apache.commons.dbcp.BasicDataSource
	at server.database.control.ContextListener.contextDestroyed(ContextListener.java:18)
	at org.apache.catalina.core.StandardContext.listenerStop(StandardContext.java:4011)
	at org.apache.catalina.core.StandardContext.stop(StandardContext.java:4615)
	at org.apache.catalina.manager.ManagerServlet.undeploy(ManagerServlet.java:1365)
	at org.apache.catalina.manager.HTMLManagerServlet.undeploy(HTMLManagerServlet.java:563)
	at org.apache.catalina.manager.HTMLManagerServlet.doGet(HTMLManagerServlet.java:123)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:558)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:859)
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
	at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
	at java.lang.Thread.run(Thread.java:662)
erico_kl

outro detalhe estranho é o CastException que ocorre como mostrei acima também:

java.lang.ClassCastException: org.apache.commons.dbcp.BasicDataSource cannot be cast to org.apache.commons.dbcp.BasicDataSource

Obs.: as libs importadas no projeto são as mesmas libs do próprio tomcat…

FernandoFranzini

Estranho mesmo…mas não essa classe ai…é outra ! Acho que vc importou errado…veja o exemplo que eu te mostrei:
org.apache.tomcat.dbcp.dbcp.BasicDataSource;
Tente denovo

erico_kl

pois é… eu tentei utilizar a mesma só que mudei pq dava o mesmo erro de cast:

algo ainda está sendo vinculado à outra biblioteca…

obs.: O tipo do meu dataSource que está no contexto da aplicação é do pacote javax.sql.DataSource, como eu mostrei antes. Mas acredito que isso não interfira no listener…

estou fazendo vários testes aqui pra ver se da certo… estou tentando de tudo, qualquer coisa eu volto a postar…

muito obrigado pela ajuda, Fernando…

FernandoFranzini

Essa mensagem aqui de cast foi para caba - java.lang.ClassCastException: org.apache.commons.dbcp.BasicDataSource cannot be cast to org.apache.commons.dbcp.BasicDataSource Vc esta com jar’s duplicados de versões diferentes?
Posta ai sua context.xml:
vc por acaso mudou o factory?
vc ta usando commons mesmo ou outro provedor?

erico_kl

segue o context.xml

<Context reloadable="true"> <Resource auth="Container" driverClassName="org.postgresql.Driver" maxActive="50" maxldle="15" minIdle="5" maxWait="20000" name="jdbc/banco" type="javax.sql.DataSource" url="jdbc:postgresql://localhost:5432/banco" username="postgres" password="xxx" validationQuery="select 1" removeAbandoned="true" removeAbandonedTimeout="60"/> </Context>
como eu disse antes, o tipo está expecificado como javax.sql.DataSource mas não sei se interfere na conexão…
não mudei nenhum factory e na lib do tomcat está o commons-dbcp, tentei trocar e dá erro também… :confused:

erico_kl

na lib do tomcat tem o commons-dbcp e o tomcat-dbcp (esta última coloquei no braço…)
se eu tirar a commons-dbcp não consigo nem a conexão com o banco…

FernandoFranzini

Que coisa meu…
Qual versão do tomcat?

erico_kl

é a versão 6, e o SO é ubuntu server 10.04…
to tentando de tudo aqui mas sem sucesso… as conexões continuam lá e no log aquele erro de cast de um BasicDataSource para outro… :confused:

detalhe, eu importei essa lib org.apache.tomcat.dbcp.dbcp.BasicDataSource da lib do tomcat7 que eu tinha no meu micro pois nas libs do tomcat6 essa lib não existe, pelo menos no que eu tenho aqui… talvez por isso o erro de cast mas o estranho é que mesmo se eu colocar as libs idênticas no tomcat e na aplicação aquele erro de cast pra DataSources iguais ocorre…

FernandoFranzini

O cast tem que parar de acontecer para o código funcionar e pool todo ser liberado.
No ambiente que eu montei aqui para fazer estes testes…instalei o tomcat 6 cruzão mesmo. Não retirei e não adicionou jar nenhum…
Seu erro de cast - java.lang.ClassCastException: org.apache.commons.dbcp.BasicDataSource cannot be cast to org.apache.commons.dbcp.BasicDataSource mostra que vc deve ter versões duplicadas no seu projeto…
Você provavelmente tem dois .jar do DBCP passeando por aí, um dentro
do tomcat e outro dentro da sua aplicação. …veja ai na sua lib e na lib do tomcat…

erico_kl

certo… mas eu preciso de um lib na aplicação para conseguir importar a classe org.apache.commons.dbcp.BasicDataSource e outra no lib do tomcat (que é a que veio padrão), elas precisam ser as mesmas, correto?
se eu colocar a mesma lib nos 2, continua dando erro de cast
se eu tirar do tomcat, ClassNotFound
e se eu tirar da aplicação vai compilar com erro (pois preciso da lib pra pode importar…)

FernandoFranzini

Não…vc precisa de um jar só dentro do lib do tomcat!!!
Sua IDE deve importar automaticamente para dentro da aplicação todos os jars disponíveis dentro do tomcat.
Outra coisa não é org.apache.commons.dbcp.BasicDataSource !!!
É a essa aquiiiii - org.apache.tomcat.dbcp.dbcp.BasicDataSource

erico_kl

só que o projeto não é um projeto EE, é SE acessando um web service, então nao tenho as libs do tomcat importadas automaticamente…
e eu sei que vc passou a outra lib “org.apache.tomcat.dbcp.dbcp.BasicDataSource” só que nao achei nenhuma lib nativa do tomcat6 que contenha essa classe, apenas a outra que importei (do tomcat7), e mesmo eu importando essa lib tomcat-dbcp (que contém essa classe que vc falou) aí ocorre aquele erro de Cast…

ou seja, eu preciso de alguma lib que contenha esse caminho, pois nas do tomcat6 só contém a org.apache.commons.dbcp.BasicDataSource…

e por isso que terei que colocar a mesma lib na aplicação e no tomcat…

FernandoFranzini

Estamos batendo cabeça a toa…claro que é projeto EE…não ta rodando no tomcat 6? não tem um web.xml? vc não gera um war? é projeto EE sim kkkkkkk
Esquece o consumidor de web services jse…ele não tem nada relacionado aqui…
Me fale do seu ambiente ai e projeto…
Vc não tem um projeto web que gerar um war? que é o projeto que vc expõe o web service?
Vc ta usando eclipse?

erico_kl

sim sim… é que tem um detalhe… é projeto web sim, na parte do web service…
utilizo o eclipse sim, mas não é pra versão EE (faço os xml no braço, e gero o .war com o ant)

por isso não tenho as libs do tomcat importadas automaticamente… pq como a programação em si do sistema (parte do cliente) é JSE, utilizamos o eclipse nessa versão e criamos o pouco que tem de ambiente web puramente no braço…

mas acabei de fazer uns testes aqui com o eclipse EE e não deu o erro de Cast, porém as conexões continuam lá…

detalhe: o web service tem uma thread que fica rodando pra limpar as sessões, e no undeploy informa que não foi possível pará-la, isso interfere em alguma coisa? (pois essa mensagem sempre foi informada no log…)

FernandoFranzini

Dai fica difícil…isso ai é uma gambiarra na verdade…
Aconselho a vc fazer um projeto JEE , configurar um servidor web container…as libs serão importadas, vc pode depurar o listener e ver se a coisa realmente ta fazendo…e gerar o war via WTP mesmo.
Essa thread ai não interfere em nada…mas parece outra gambi sua…vc não precisa fazer isso…é usar o SESSION-TIME-OUT do container no seu web.xml
Eu tb tenho muitos projetos web services rodando JAX-WS dentro do tomat…funciona que é uma maravilha :smiley:
Bom, resolvido ja esta…só depende de vc ai…

erico_kl

ok… posso até fazer…
não sei se é uma gambiarra pois como eu disse nós só precisávamos do tomcat rodando por ter web service do outro lado (tanto que antes o pool era feito na própria aplicação, independente do web container)… nós não tínhamos nenhuma interface web ou algo do tipo pra trabalhar com JEE pois todas as views eram JSE… na parte web só tinha o web.xml (o context.xml só foi inserido quando tiramos o pool da aplicação e colocamos no tomcat)

enquanto eu escrevia esse post aqui, eu configurei o eclipse EE em outra máquina e vinculei o projeto com o tomcat, não deu mais nenhum erro no log do tomcat, como eu informei antes, porém as conexões não são removidas…

vou continuar tentando mas parece que não tem jeito daquelas conexões serem fechadas… já tentamos de tudo aqui…

novamente muito obrigado pela sua ajuda, Fernando.

FernandoFranzini

erico_kl:
ok… posso até fazer…
não sei se é uma gambiarra pois como eu disse nós só precisávamos do tomcat rodando por ter web service do outro lado (tanto que antes o pool era feito na própria aplicação, independente do web container)… nós não tínhamos nenhuma interface web ou algo do tipo pra trabalhar com JEE pois todas as views eram JSE… na parte web só tinha o web.xml (o context.xml só foi inserido quando tiramos o pool da aplicação e colocamos no tomcat)

Não tem problema de não ter GUI web…um projeto web service é por natureza web!!! veja que é por isso q vc’s precisam do tomcat kkkkk…porque é um projeto web!! Só acho que vc estão fazendo na unha muita coisa que ja esta pronta no web container…
Tem jeito sim…depura o listener e veja se realmente esta invocando…veja as referências polimórficas do datasource…coloque log para garantir a execução…sei-la…eu não faço para vc pq não tenho postgree aqui…meu teste aqui foi feito com SQLServer…
Que tem jeito, tem…

erico_kl

realmente tentei aqui com um tomcat na máquina local, configurado junto com o Eclipse EE e deu certo, as conexões fecharam no undeploy! :smiley:

é só ver um jeito agora de configurar em um tomcat remoto (pois testei e no tomcat remoto as conexões não foram limpadas)

mas já vi que resolve o problema mesmo… vou me adequar aqui com o ambiente web, pq tá mais que na hora kkk…

muito obrigado!

FernandoFranzini

Que bom…demorou mas chegamos la.
Estamos ai para ajudar e servir.
Fica na paz.

erico_kl

só mais uma coisa, o seu ambiente que vc testou era linux? Aqui consegui fazer limpar só as conexões respectivas à ambiente windows. Quando rodo a mesma aplicação em um tomcat configurado num linux as conexões não são limpadas… embora não conste nenhum erro no log…

ah… quanto àquela Thread que limpa as sessões, ela é referente à autenticação do usuário no sistema, e não da própria sessão do tomcat (pois esta está configurada com o timeout sim…). Ou seja, o usuário se autentica via desktop e é criado uma thread pro seu web service e esta a cada acesso verifica a sua autenticidade e se o tempo dele (usuário no db) não se esgotou para então permitir ou não o consumo total do WS…

FernandoFranzini

Pode não funcionar se seu ambiente de produção for diferente do desenvolvimento…outra versão do tomcat, versão do commons…
Aqui deu certo sim…

erico_kl

pois é… a versão do tomcat é a 6 para os 2 ambientes… só se eu copiasse as libs do tomcat do servidor de produção e substituisse pelas libs do tomcat de desenvolvimento… mas nunca antes precisei fazer isso pq tanto o servidor de produção quanto o de desenvolvimento (ambos remotos) rodam o mesmo tomcat… (sendo que tive que instalar um terceiro, na máquina local, pra poder adicionar as libs, como vc falou…)

erico_kl

cara, consegui resolver totalmente agora!
o que eu fiz: peguei as libs do tomcat que eu tinha na máquina de desenvolvimento (Windows) e substitui pelas libs do tomcat do servidor (Linux). Agora como as libs são as mesmas e o sistema está identificando o tomcat da máquina local (que por sua vez tem as mesmas bibliotecas do servidor), o ContextListener é chamado e as conexões são liberadas no undeploy! :smiley:

mais uma vez muito obrigado!

problema 100% solucionado… graças à sua ajuda!

valeu, Fernando!

FernandoFranzini

:wink:
T+

Criado 28 de maio de 2011
Ultima resposta 2 de jun. de 2011
Respostas 41
Participantes 2