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