quarta-feira, 30 de setembro de 2020

Memory leak inesperado

Essa é uma séria de posts que estava na minha mira há bastante tempo, sendo que hoje tomei vergonha na cara para começar. Nesta série tentarei compartilhar as coisas que me acontecem, surpreendem, e aprendo no dia a dia de desenvolvedor. Então hoje começo a série "hoje eu aprendi...".

Já há alguns vários dias eu fui surpreendido por um vazamento de memória (memory leak) em um sistema da Agro1. O vazamento de memória estava em um código semelhante a este:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var  
  [weak] lAlgumaCoisa: IAlgumaCoisa;  
begin  
  If Supports(pAlgumObjeto, IAlgumaCoisa, lAlgumaCoisa) then
  begin  
    RegistrarMetodoQueSeraExecutadoDepois(  
      procedure  
      begin  
        lAlgumaCoisa.ExecutarAlgumaCoisa;  
      end);  
  end;  
end;


Pois bem, uma das bases do novo framework em que estamos trabalhando na Agro1 é o uso massivo de interfaces, e felizmente o Delphi Berlin trouxe a opção de declarar variáveis do tipo interface (ou fields, parâmetros, etc.) como sendo referências fracas ou não seguras, e um dos efeitos destes atributos é fazer com que estas variáveis não passem pelos métodos _AddRef e _Release de IInterface durante sua manipulação (você pode ler um pouco mais sobre estes atributos aqui).

Claro que o método acima é um resumo do que precisava ser feito e pode parecer confuso, mas trouxe para este exemplo somente o básico para mostrar o problema.

O que aprendi neste dia foi que os metadados da variável passada como terceiro parâmetro do método Supports não são considerados no momento de fazer a atribuição da referência para a variável, logo, a variável estar marcada ou não com o atributo weak não faz a menor diferença. Isto tudo porque o método Suppoprts chama diretamente o método QueryInterface, que por sua vez chama o método GetInterface, que chama explicitamente o _AddRef. Como a implementação do _AddRef, neste caso, fazia uso de contagem de referência, a variável entrou na contagem, fechou-se um circuito fechado entre as variáveis envolvidas no processo, e consequentemente geramos um vazamento de memória.

Solução: tivemos que declarar uma segunda variável usada somente com escopo local, sem o atributo weak, e outra variável dedicada ao closure, esta sim com o marcador weak. Resumindo, ficou algo parecido com isto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var  
  lAlgumaCoisaClosure: IAlgumaCoisa;  
  [weak] lAlgumaCoisaClosure: IAlgumaCoisa;  
begin
If Supports(pAlgumObjeto, IAlgumaCoisa, lAlgumaCoisa) then begin
    lAlgumaCoisaClosure := lAlgumaCoisa;
    RegistrarMetodoQueSeraExecutadoDepois(  
      procedure  
      begin  
        lAlgumaCoisaClosure.ExecutarAlgumaCoisa;  
      end);  
  end;  
end;


Bom, se você passar por situação parecida, já sabe, está aí o problema e possível solução, espero que este post lhe ajude.

Grande abraço e até o próximo!

Nenhum comentário:

Postar um comentário

Você está fazendo isso errado! Tópico #2

 Fala galera! Como estão todos? Sejam bem-vindos ao primeiro post de 2021. Continuando o tema "Você está fazendo isso errado", va...