Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
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
https://support.microsoft.com/kb/289531/en-us
How To Hand Code an ADO Data Connection in ASP
https://support.microsoft.com/kb/299980/en-us
Open and Close Methods Example (VBScript)
25+ ASP Tips to Improve Performance and Style
https://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnasp/html/asptips.asp
INFO: Reusing ADO Recordsets Maintains Properties