RunTime do AdvPL

Introdução

No primeiro post do Blog, em apenas um parágrafo foi dada uma definição bem sintética do que é o AdvPL: Trata-se de uma uma linguagem de programação estruturada com suporte a orientação de objetos, que roda em uma máquina virtual com arquitetura client-server e multi-plataforma.

Hoje vamos entrar um pouco mais fundo em algumas características da execução de um código AdvPL, desde a pré-compilação até a execução do Código. Mas antes, vamos com algumas definições.

Código AdvPL

A sintaxe da linguagem AdvPL é uma extensão da sintaxe xBase, mais conhecida pelo seu uso na linguagem Clipper. Um código fonte em AdvPL passa por uma etapa de pré-compilação, onde vários comandos são transformados em chamadas de funções, e “açúcares sintáticos” podem ser implementados, através de #defines, #translates, #command(s), #ifdef(s) e afins. Os comandos da linguagem AdvPL que realmente são “comandos” são as instruções de decisão e iteração, como WHILE, FOR, IF, ELSEIF , ELSE, CASE, END … praticamente todo o resto dos comandos são implementações feitas por #command, #xcommand ou #translate, que permitem você usar por exemplo um “comando” de abertura de tabela, como o comando “USE <cTabela> ALIAS <cAlias> [ SHARED | EXCLUSIVE ] [ VIA <cRdd> ] [READONLY] [NEW]”, que na etapa de pré-compilação vai ser transformando internamente na chamada da função DbUseArea().

Pré-compilação

Tanto o IDE como o TDS (versão anterior e atual do Ambiente de Desenvolvimento de aplicações AdvPL, respectivamente) trabalham com um pré-compilador (appre.exe). A partir do fonte AdvPL e dos respectivos #include(s) utilizados no código, a pré-compilação gera um novo arquivo, chamado de PPO (Pre Processed Output), e este sim é o código enviado ao AppServer para ser compilado.

Compilação

A compilação deste código gera um “ByteCode” em formato proprietário, interpretável somente pela máquina virtual do AppServer. O ByteCode gerado é criptografado e armazenado em um arquivo de repositório de objetos, configurado no AppServer para o ambiente onde os fontes estão sendo compilados. Damos o nome de “ambiente” a um espaço virtual de execução, que engloba um repositório de objetos e um ponto de acesso a uma pasta no disco (local ou remota via rede) para acesso a arquivos sob este identificador de ambiente, entre outras configurações de acesso a dados relacionais e comportamentos desejados para a instância do ambiente.

Repositório de Objetos

Armazena as funções e classes compiladas a partir dos fontes AdvPL, é fornecido pela TOTVS contendo as compilações dos módulos do ERP Microsiga do produto padrão. Contém também as funções do Framework AdvPL, usadas pelos fontes dos demais módulos, e que também são usados em customizações do produto, onde o próprio cliente pode criar um código em AdvPL, usando-se das funções básicas e de Framework AdvPL para customizar o produto padrão através de pontos de entrada (compilação de funções de usuário — USER FUNCTION(s) — com nomes especificos, onde existem pontos de chamada realizados pelo produto padrão do ERP), ou mesmo criando novas funcionalidades.

O repositório possui mecanismos de verificação de integridade e proteção de código, para evitar a engenharia reversa dos fontes compilados. A reversão de código é possível, e às vezes necessária quando um cliente por exemplo “perde” os fontes das customizações da versão em uso do seu ERP, e precisa delas no momento de uma migração de versão. O procedimento de “descompilação” neste caso é solicitado para a TOTVS mediante abertura de chamado, onde cada caso é avaliado.

O repositório de objetos também guarda arquivos de imagem (BMP,PNG,JPG), que podem ser inseridos em um Projeto AdvPL através do IDE/TDS e recuperados posteriormente em tempo de execução e usados diretamente por algumas funções, classes e comandos da linguagem que montam a interface da aplicação.

O Protheus 12 utiliza uma nova engine da máquina virtual do AppServer, com um novo formato de ByteCode, com mais instruções de baixo nível. Isto tornou o ByteCode ligeiramente maior, porém com ganhos de desempenho em alguns processos e funcionalidades.

Execução do AdvPL

Partindo de uma execução de um programa AdvPL, iniciado a partir do SmartClient, o SmartClient faz um HandShake com o AppServer via conexão TCP (ou SSL caso configurado), e solicita a execução de uma função. Ela pode ser uma “Main Function” — Função de entrada de módulo ou ferramenta do ERP — ou uma “User Function” — aplicações ou customizações criadas em AdvPL pelo próprio cliente.

Como o AdvPL é essencialmente dinâmico, os fontes do repositório são carregados na memória sob demanda, conforme as funções vão sendo chamadas. Quando o Menu do ERP é apresentado, todos os programas já carregados e executados até então, essencialmente fontes do Framework do AdvPL são mantidos na memória. Ao chamar uma opção de menu, o fonte e suas dependências são carregados sob demanda e executados, e quando o usuário encerra a opção e volta ao Menu do ERP, todos os fontes carregados desde o início daquela opção são descarregados da memória.

Quando a excecução é a chamada de menu da interface MDI (SIGAMDI), cada opção de menu cria um contexto e uma conexão separada com o AppServer, permitindo a execução de múltiplos programas sob a mesma interface, onde cada execução possui seu contexto e processo independentes. Cada opção de menu finalizada encerra o seu próprio conexto e descarrega todos os prograas da memória. Ao usar o mecanismo de Balanceamento de Carga do AdvPL, cada nova opção de Menu do MDI pode ser direcionada para o AppServer com menos processos em execução.

Para jobs, não existe balanceamento de carga nativo. Cada Job é executado na instância do serviço onde o AppServer está sendo executado. Hoje podemos emular um balanceamento realizando um RPC para um determinado serviço e subindo o job via RPC ( Nativo do AdvPL).

Atualmente o contexto de uma execução em AdvPL está implicitamente amarrado com uma Thread do Sistema Operacional. A arquitetura multi-thread do AppServer não estabelece afinidade entre as CPUS físicas do equipamento, o Scheduler de tarefas do sistema operacional e encarregado de distribuir as Threads e realocá-las por CPU a seu critério. Devido ao cenário extremamente variável de peso de execução das Threads, tentar estabelecer uma afinidade tende a ser pior do que deixar o sistema operacional escolher a CPU em uso.

AdvPL com interface – SmartClient

A execução de processos de interface AdvPL com o SmartClient é síncrona, a aplicação SmartClient é a resposável por renderizar a interface (Janela ou diálogo) construída pela aplicação AdvPL, e uma vez que a interface seja “ativada”, o AppServer passa a esperar por uma interação do usuário no SmartClient, como pressionar um botão ou preencher um campo. Entre ambos existe um mecanismo de verificação de conexão, chamado “pulso”. Quando o AppServer está aguardando por uma interação do usuário com a interface, mesmo que o usuário não dispare nenhum evento, a cada 60 segundos o SmartClient dispara um evento interno de “pulso” para o AppServer , para indicar que o SmartClient ainda está aberto. Caso o AppServer nao receba nenhum evento ou nenhum pulso do SmartClient em 3 minutos, ele assume que o SmartClient não está mais conseguindo se comunicar com o AppServer , e derruba/finaliza o processo em execução no AppServer , liberando os recursos utilizados ( arquivos e registros bloqueados, licenças, conexão com DBAccess,etc ).

Depuração de Código AdvPL

Através do IDE e/ou TDS, podemos depurar (ou “debugar”) um código AdvPL, colocando pontos de parada, inspecionando variáveis de memória, e determinando as etapas de execução (step into, step over, step out, run…). Deve-se tomar uma atenção especial no uso dos “Watches” de execução, pois a chamada de funções dentro de um “Watch” podem mudar o comportamento da aplicação, como por exemplo reposicionar o ponteiro de registro atual de uma tabela de dados, mudando o comportamento da aplicação enquanto ela está sendo depurada. Normalmente usamos os Watches para variáveis de memória. Através da Janela de Comandos do IDE/TDS, também podemos executar expressões, e até mudar conteúdos de variáveis durante a depuração através de instruções de atribuição. Isto pode ser útil em muitas situações.

Uso de memória

Normalmente a memória alocada por um programa é limpa quando o programa é finalizado. Durante a execução de código, existem algumas limpezas realizadas nos destrutores de componentes de interface, e no retorno ao stack (pilha) anterior após a execução de funções do Repositório de Objetos. Normalmente objetos são limpos quando não são mais referenciados, porém caso seja feita uma referência circular entre objetos, a limpeza somente ocorre quando a aplicação termina, ou quando for realizada explicitamente por uma função chamada “FreeObj()”.

Existem diversos recursos de monitoramento de memória do AppServer, um dos mais utilizados é a configuração DebugThreadUsedMemory, que pode ser habilitada no appserver.ini, na seção [GENERAL]. Esta configuração faz com que o AppServer mostre no Monitor de Processos do AppServer a quantidade de memória aproximada em uso por cada processo em execução.

Conclusão

Boa parte dos recursos mencionados neste tópico estão documentados no site oficial de documentação da TOTVS, o TDN (Totvs Development Network), que pode ser acessado no link http://tdn.totvs.com. O Post de hoje é só pra “molhar” o bico, os próximos posts em momento oportuno vão entrar em mais detalhes de cada uma destas operações 😀

Agradeço a audiência de todos, e desejo a vocês TERABYTES de sucesso !!! 

Abraços 😉

Anúncios

13 comentários sobre “RunTime do AdvPL

  1. Ótimo artigo! É muito importante como funciona o RunTime do AdvPL. Creio que o mesmo comportamento é aplicado ao produto Logix, que também utiliza TOTVS Tec e AdvPL.

    Curtido por 1 pessoa

    • Olá Rubinho,

      Sim, o Logix têm algumas diferenças pertinentes a algumas funcionalidades do 4JS, mas por baixo do capô eles usam versões muito próximas do mesmo “motor” 😀

      Abraços

      Curtir

  2. Parabéns pelo texto, muito esclarecedor! Me tirou algumas dúvidas que durante os treinamentos que tive na Totvs não souberam me responder, principalmente sobre o repositório(.rpo). Para quem vinha do Delphi, que quando compilado sempre é gerado um executável(.exe) ou .dll, era difícil imaginar como era o funcionamento do RPO

    Curtido por 1 pessoa

  3. Júlio,

    Gostei da parte sobre limpeza de memória. Comecei a brincar com MVC e estava tentando utilizá-lo em job, sem interface, carregando e valorizando o modelo através do objeto FWLoadModel(). Como haviam muitos registros a serem inseridos, acabei me deparando com o seguinte erro: “Reference counter overflow ( over 32600 )”. Isso mesmo quando a variável, que recebia o objeto, era local e toda a atribuição do modelo ocorria em uma User Function separada do processamento principal. (A user era chamada para cada linha do arquivo de importação).

    A única maneira de limpar o objeto e impedir que o erro ocorresse foi utilizando a função FreeObj(). Ai fica uma dúvida: Quando uma função termina, seja ela main, user ou static, ela não deveria liberar a memória ocupada por suas variáveis? Ou os objetos e arrays são alocados de forma diferente?

    Att’

    Curtido por 1 pessoa

    • Opa … vejamos … existe um controle de referências complexo dentro do APPServer para manter os comportamentos esperados da linguagem, principalmente ligados a objetos e CodeBlocks, onde algumas referências a conteúdos alocados durante a execução são eliminados somente na finalização do programa. Com o aumento significativo do tamanho e complexidade do código executado, uma limpeza mais profunda de memoria em cada retorno de função do stack teria mais custo de CPU e custo de realocação de memória, além de favorecer a fragmentação de memória após sucessivas execuções. E, existem cenários comuns de uso de CodeBlocks que implicitamente referenciam o escopo local da função, impedindo a limpeza do escopo até a referência ser quebrada. Na prática, o “Garbage” do AdvPL é bem leve, para favorecer o desempenho, porém a aplicação acaba gastando um pouco mais de memória, pois a limpeza desta memória exigiria um mecanismo mais complexo, e naturalmente mais oneroso. Dadas estas características, as boas práticas do desenvolvimento em AdvPL incentivam a limpeza (destruição explícita) de objetos quando estes não forem mais utilizados pela aplicação. A limpeza completa de todo o conteúdo gerado durante a execução dos códigos é realizada apenas no término / retorno do programa que iniciou a thread. 😀

      Esse assunto é muito interessante, e eu acho que algumas destas boas práticas (ainda) não estão sendo abordadas em profundidade. Isso abre caminho para mais um post sobre esse assunto 😉

      Abraços

      Curtido por 1 pessoa

  4. Boa tarde

    Primeiro muito obrigado por compartilhar conhecimentos Júlio.

    Uma dúvida por favor, vi no tópico que depois de 3minutos ele derruba a conexão do smartclient (quando não há retorno de pulso) liberando registros,dbaccess(top rsrsrsrs), mas mesmo derrubando a conexão pelo monitor, seja do tds ou monitor da pasta smartclient, a conexão do usuário fica presa no top, assim continuando por exemplo consumindo licença, o usuário tenta acessar as rotinas e apresenta a mensagem limite de de conexões excedido (liberamos apenas 3 conexões por usuário por exemplo), tem uma saída para não.ter que derrubar o usuário no top, após.derrubar do.monitor?

    Obrigado Júlio

    Curtido por 1 pessoa

    • Olá Paulo, beleza ? Então, vejamos … Eu acho que essa questão merece um post inteiro a respeito …rs… Não é simples explicar este comportamento com poucas palavras, mesmo assim vou tentar fazer uma sintese …. Dependendo do que está sendo executado no AdvPL, e onde está sendo executado, o runtime da linguagem pode estar aguardando um retorno de uma chamada externa ao AdvPl, e enquanto este retorno não chegar, o processo não vai saber que ele foi notificado para ser finalizado. Mesmo quando você usa a opção “Finalizar Imediatamente” do Protheus Monitor, esta opção derruba a conexão TCP entre o SmartClient e o APPServer, e remove o processo da lista de monitoramento — ele não aparece mais no monitor — , mas de qualquer forma o processo é “marcado” para ser finalizado, e somente vai terminar e sair da memória quanto ele verificar este flag o que normalmente o runtime do Advpl faz entre um bloco e outro de instruções.

      Por exemplo, SE um programa AdvPL inicia a execução de uma Stored Procedure no Banco de Dados, através do DBAccess, o Runtime do AdvPL fica esperando a API do DBAccess retornar um status de execução, e o DBAccess fica esperando o Banco de Dados retornar esse status. Se o Banco de dados demorar meia hora pra rodar a Procedure e retornar alguma coisa, mesmo que você derrube o processo AdvPL no Protheus Server, usando o Protheus Monitor, o processo somente vai “saber” que você pediu pra ele sair, quando ele receber um retorno do DBAccess.😀

      Em breve vou abordar este tema com mais detalhes em um post dedicado a este assunto, ok ?

      Grande abraço fera !!!

      Curtir

  5. Muito obrigado por estar compartilhando seu conhecimento com todos. =)

    Vamos a minha dúvida… Muitas vezes estou depurando um fonte AdvPl pelo TDS e acontece um erro de sincronismo, mesmo todo o ambiente estando local, exceto o servidor de licença. Isso ocorre por conta desse “pulso” que você citou? Se sim, existe alguma forma de aumentar o tempo dele? Se não ocorre por conta do pulso ou não existe alguma forma de aumentar esse tempo do pulso, existe algum contorno para essa situação? Pois normalmente quando ocorre, parece que a thread continua presa no AppServer e só reiniciando o mesmo consigo por exemplo voltar a compilar nesse ambiente.

    Mais uma vez, muito obrigado! 🙂

    Curtido por 1 pessoa

    • Olá Daniel, beleza ? Muito grato por sua audiência e comentários !!

      Vejamos … A camada do TDS mantem uma conexão de Debug com o APPServer, e o Smartclient também mantém uma … Infelizmente a mensagem de erro (ainda) não informa qual das conexões que deu “crepe” ,,, Mas se isso acontece em ambiente “localhost”, a causa desses eventos pode ser outra … Se você conseguir identificar uma situação especifica que isso se reproduza “sempre”, por favor compartilhe 😀

      Abraços 😀

      Curtir

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s