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.
