Por que o GnuCOBOL continua executando a versão antiga de um subprograma?

Durante o desenvolvimento de uma aplicação modular em GnuCOBOL, podemos encontrar uma situação curiosa: alteramos um subprograma, recompilamos o código, fazemos um novo CALL e… o programa continua executando a versão anterior.

O problema pode ficar ainda mais intrigante quando o programa chamador executa explicitamente um CANCEL depois que o subprograma retorna.

Por que isso acontece?

O comando CANCEL não necessariamente descarrega o programa

Considere uma aplicação com um menu que chama dinamicamente diferentes programas:

CALL WS-NOME-PROGRAMA

O GnuCOBOL precisa localizar e carregar o módulo correspondente. Em Linux, quando trabalhamos com programas compilados como módulos, normalmente estamos falando de uma biblioteca compartilhada com extensão .so.

Depois que o subprograma termina, podemos executar:

CANCEL WS-NOME-PROGRAMA

É fácil imaginar que o CANCEL elimina completamente o programa da memória. Mas esse não é o comportamento padrão do GnuCOBOL. O runtime pode efetuar um cancelamento lógico do módulo sem descarregar fisicamente da memória a biblioteca compartilhada. Isso é interessante para desempenho: se o programa for chamado novamente, não será necessário repetir todo o processo de carga.

Durante o desenvolvimento, entretanto, isso pode produzir um efeito indesejado.

Imagine que o menu de opções do sistema deu call em um programa chamado oft0101. O programador detecta um problema e altera oft0101.cob em outro terminal e gera um novo oft0101.so.

O arquivo em disco mudou, mas o processo que está executando o menu ainda possui o módulo anteriormente carregado. Um novo CALL pode, portanto, continuar executando a versão antiga. Quando encerramos o menu e o executamos novamente, um novo processo é iniciado e o novo .so é carregado. A alteração finalmente aparece.

COB_PHYSICAL_CANCEL

O GnuCOBOL possui uma configuração específica para mudar esse comportamento:

export COB_PHYSICAL_CANCEL=TRUE

Com o cancelamento físico habilitado, um CANCEL “OFT0101” faz com que o runtime possa efetivamente descarregar o módulo dinâmico. Consequentemente, no próximo CALL “OFT0101” o módulo precisa ser carregado novamente. Se oft0101.so tiver sido recompilado nesse intervalo, será a nova versão que estará disponível para carga.

Para testar isso em uma sessão Linux, podemos fazer:

export COB_PHYSICAL_CANCEL=TRUE
./meumenu

Essa solução é bastante conveniente durante o desenvolvimento, mas exige que a variável esteja configurada no ambiente antes da execução da aplicação.

Configurando pelo runtime

Outra possibilidade é definir o comportamento na configuração do runtime do GnuCOBOL, sem depender de um export executado pelo usuário. No arquivo de configuração do runtime, podemos habilitar:

physical_cancel = yes

A configuração correspondente controla o mesmo comportamento associado a COB_PHYSICAL_CANCEL. Podemos consultar as opções e a configuração do runtime da instalação com comandos como

cobcrun --runtime-config

A vantagem dessa abordagem é tornar o comportamento parte da configuração do ambiente GnuCOBOL, em vez de depender da sessão de shell de cada usuário.

Quando usar?

O cancelamento físico tem um custo. Se um módulo for descarregado, uma chamada posterior precisará carregá-lo novamente. Por isso, manter módulos carregados pode ser mais eficiente em uma aplicação estável.

Em desenvolvimento, a situação é diferente.

Se estamos trabalhando em uma aplicação formada por um programa principal que permanece executando e chama dinamicamente vários módulos COBOL, COB_PHYSICAL_CANCEL pode facilitar bastante o ciclo de alteração, compilação e teste.

E o problema ilustra algo interessante sobre programação COBOL no Linux: entender apenas o CALL e o CANCEL não é suficiente. Quando começamos a construir aplicações modulares, também precisamos entender como o runtime, o sistema operacional e as bibliotecas compartilhadas participam da execução dos nossos programas.