Resposta ao Desafio da Semana #6 [Memory Leak em ASP]




Por: Roberto Alexis Farah


 


Olá!


Eis o link do Desafio da Semana #6


 


Agora vamos a resposta!


 


PROBLEMA


 


No fragmento de código abaixo há um discreto (e comum) bug: o objeto oRsNivelAccess nunca é fechado nem liberado da memória.


E, pior, a cada nova interação do loop mais memória reservada para o recordset é alocada e a memória antiga nunca é liberada!!! Imagine se o loop tiver 200 iterações!


O bug é discreto e passa despercebido porque a primeira vista parece que quando o objeto é recriado a memória é sobreescrita, logo, ainda que o recordset não seja explicitamente fechado e liberado da memória o leak não ocorreria! Errado! Primeiro, se o fato de recriar o objeto limpasse a memória previamente alocada no heap para o recordset então ainda assim haveria um memory leak, pois quando oRsNivel.eof for FALSE o loop encerra, logo o último recordset não seria liberado!


Segundo, se essa liberação de recursos implícita ocorresse então não precisaríamos ter, como Best Practice the ASP e Visual Basic, o dever de explicitamente fechar objetos e liberá-los da memória. A recomendação existe porque fazendo isso liberamos os recursos de modo determinístico, logo após usá-los.


Terceiro, imagine a seguinte analogia com linguagem C para visualizar a indireção que há no código abaixo e você vai notar o que ocorre.


 


for(int i = 0; i < 200; i++)


{


        int* pnHeap = (int*) malloc(500);     ß Endereço do ponteiro será o mesmo na pilha…


                                                            ß Mas a cada nova alocação dinâmica um novo bloco de memória será alocado num endereço de memória virtual diferente, no heap, e o bloco antigo continuará na memória!


       


       


       


        memset(pnHeap, 8, 500);


}


 


 


do while not oRsNivel.eof
Set oRsNivelAcess = Server.CreateObject(“ADODB.RecordSet”)   
ß Cria objeto a cada iteração.

if Request.QueryString(“seguranca”) = “Nivel de Informacoes” then
strSQLAcess =…           
ß Aqui há uma query qualquer.
else
strSQLAcess =…     
ß Aqui há uma query qualquer.
end if
oRsNivelAcess.Open strSQL, Conn, 3, 3    
ß Recordset exige memória alocada dinamicamente, afinal, não sabemos quantos registros serão retornados.
Response.WriteBlock(56)
Response.Write(oRsNivel(“nivel”))
Response.WriteBlock(57)


utiliza oRsNivelAcess



Response.WriteBlock(59)
oRsNivel.movenext
loop                                                     
ß Nova iteração, e a memória e recursos previamente alocados?


      ß Fecha oRsNivel e libera-o da memória. Mesmo com a conexão.




 


Alguém poderia notar que o objeto, ao sair de escopo, deveria automaticamente liberar recursos, mas isso não ocorre, razão pela qual explicitamente fazemos isso.


 


 


 


SOLUÇÃO #1


 


Supondo que strSQL seja construído a cada interação do loop, a solução proposta é:


 


Set oRsNivelAcess = Server.CreateObject(“ADODB.RecordSet”)   ß Cria objeto de recordset uma única vez.


do while not oRsNivel.eof
if Request.QueryString(“seguranca”) = “Nivel de Informacoes” then
strSQLAcess =…           
ß Aqui há uma query qualquer.

else
strSQLAcess =…     
ß Aqui há uma query qualquer.
end if
oRsNivelAcess.Open strSQL, Conn, 3, 3   


Response.WriteBlock(56)
Response.Write(oRsNivel(“nivel”))
Response.WriteBlock(57)


utiliza oRsNivelAcess



Response.WriteBlock(59)


oRsNivelAcess.Close                    ß Fecha o objeto e libera recursos associados ao objeto.
oRsNivel.movenext
loop


set oRsNivelAccess = nothing          ß Libera objeto recordset da memória.                                                   


      ß Fecha oRsNivel e libera-o da memória. Mesmo com a conexão.




 


 


SOLUÇÃO #2


 


Supondo que strSQL nunca mude durante o loop podemos usar um Disconnected Recordset:


 


 


Set oRsNivelAcess = Server.CreateObject(“ADODB.RecordSet”)   ß Cria objeto de recordset uma única vez.


oRsNivelAcess.CursorLocation = adUseClient         ß Client side cursor, necessário para disconnected recordsets.


oRsNivelAcess.Open strSQL, Conn, adOpenStatic, adLockOptimistic


oRsNivelAcess.ActiveConnection = nothing                       ß Desconecta o recordset.   


do while not oRsNivel.eof
if Request.QueryString(“seguranca”) = “Nivel de Informacoes” then
strSQLAcess =…           
ß Aqui há uma query qualquer.

else
strSQLAcess =…          
ß Aqui há uma query qualquer.
end if
Response.WriteBlock(56)
Response.Write(oRsNivel(“nivel”))
Response.WriteBlock(57)


utiliza oRsNivelAcess



Response.WriteBlock(59)


oRsNivel.movenext
loop


oRsNivelAcess.Close                       ß Fecha o recordset disconectado e libera recursos associados ao objeto.


set oRsNivelAccess = nothing          ß Libera objeto recordset disconectado da memória.                                                    


      ß Fecha oRsNivel e libera-o da memória. Mesmo com a conexão.




 


 


 


 


Alguns problemas de memory leak e CPU a 100% são comuns em código ASP e difíceis de serem detectados. Alguns serão tópicos dos próximos desafios.


O exemplo acima é bastante típico e poderia passar despercebido numa extensa página ASP onde todos os outros recordsets são corretamente fechados e liberados da memória.


 


Eis algumas regrinhas válidas para ASP e Visual Basic 6:


 


– Sempre que usar Open use Close ao final.


– Sempre que usar New ou CreateObject use set object = nothing no final.


 


Como referência coloco alguns artigos sobre o assunto:


 


How To Create ADO Disconnected Recordsets in ASP Using VBScript and Jscript


http://support.microsoft.com/kb/289531/en-us


 


How To Hand Code an ADO Data Connection in ASP


http://support.microsoft.com/kb/299980/en-us


 


Open and Close Methods Example (VBScript)


http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbaac11/ADO210/htm/mdmthopenclosevbscriptx.asp


 


25+ ASP Tips to Improve Performance and Style


http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnasp/html/asptips.asp


 


INFO: Reusing ADO Recordsets Maintains Properties


http://support.microsoft.com/kb/195512/en-us


 


Comments (0)