JobMonitor: mudanças entre as edições

De BIS Wiki
Ir para navegação Ir para pesquisar
Linha 43: Linha 43:
= JobMonitor =
= JobMonitor =


O JobMonitor é uma classe estática que controla e mantém todos os Jobs do Sistema. Ao criar uma instância de '''Job''' ele automaticamente se registra no JobMonitor. Mesmo antes de iniciar a tarefa o JobMonitor já tem a referência da classe e monitora seu status.
O JobMonitor é uma classe estática, como um Singleton, que controla e mantém todos os Jobs do Sistema. Ao criar uma instância de '''Job''' ele automaticamente se registra no '''JobMonitor'''. Mesmo antes de iniciar a tarefa o '''JobMonitor''' já tem a referência da classe e monitora seu status.


Quando o Job é criado, ela cria também sua classe JobStatus, e dentro dela é possível encontrar seu identificador único (UUID). Com este UUID é possível solicitar informações do Job no JobMonitor. Assim, caso o JobMonitor esteja sendo executado em uma VM e o objeto que queira saber o status esteja em outra, ele pode solicitar o objeto JobStatus utilizando dessa UUID. Exemplo:
Quando o '''Job''' é criado, ela cria também uma instância final da '''JobStatus''' que tem um identificador único (UUID). Este UUID é o identificador do '''Job''' e pode ser utilizado para realizar operações no '''JobMonitor'''.


{{java|Exemplo Tarefa em Execução em Background|<syntaxhighlight lang="java">
 
{{nota|JobMonitor por JVM|Uma vez que '''JobMonitor''' é uma classe estática, ela é única por JVM e só será possível encontrar referência do '''Job''' se estivermos procurando dentro da mesma JVM em que o '''Job''' foi criado.
 
Em casos em que o Job é criado em outra JVM, será preciso implementar métodos de acesso ao '''JobMonitor''' remoto para realizar as operações desejadas.}}
 
 
Ao criar um Job, devemos nos preocupar em salvar o JobStatus, ou ao menos o UUID para que seja possível referenciar a tarefa em background a partir do '''JobMonitor'''.
 
 
{{java|Exemplo de Criação do Job e Obtenção do Status a partir do '''JobMonitor'''|<syntaxhighlight lang="java">
//Implementação do Job
//Implementação do Job
Job job = new Job("Título da Tarefa") { ... };  
Job job = new Job("Título da Tarefa") { ... };  
Linha 61: Linha 70:
</syntaxhighlight>}}
</syntaxhighlight>}}


Note que no exemplo acima o '''start()''' do job pode ser dado a qualquer momento. O job não precisa estar sendo executado para que todo o restante funcione. Porém normalmete o job já é inicializado assim que que a instância é criada.


{{nota|Retorno do UUID|Normalmente os métodos que executam um trabalho pesado já são implementados dentro do JobMonitor e o real retorno do método só ocorre quando o job termina.  
Note que no exemplo acima o '''.start()''' do job pode ser dado a qualquer momento. O job não precisa estar sendo executado para que todo o restante funcione.
 
 
{{stop|JobStatus Referência em Memória|Note que o JobStatus é um objeto final dentro do Job, e que não é criada uma nova instância para cada alteração. Isto quer dizer que o objeto pode ser manipulado dentro do Job e as alterações serem lidas concorrentemente em outra Thread. De certa forma o JobStatus funciona apenas como "ponteiros de memória" indicando onde as informações estão sendo escritas.
 


Por isso o método chamado para iniciar o job em geral retorna o UUID da tarefa. O retorno real do método quando existir é retornado dentro do objeto JobStatus, bem como a exceção caso exista (melhor explicado a seguir).
Um ponto a se observar aqui é que, embora o JobStatus possa ser recuperado e ser único na memória quando estamos dentro da mesma JVM, é preciso observar que ao devolver o JobStatus através de uma fachada utilize o [[SessionManager#SMInterceptor]] ele será clonado justamente para prevenir esse tipo de apontamento e modificação "no objeto original". Assim, não mais representará o mesmo objeto na memória. Ao retornar o objeto pela fachada, o outro lado deve utilizar o jobUUID para recuperar o objeto correto do JobMonitor para receber atualizações diretamente.}}
}}


= JobChecker =
= JobChecker =

Edição das 14h42min de 31 de outubro de 2020

O JobMonitor é o serviço do BISFW que permite que tarefas sejam executar em uma Thread paralela para que a thread principal não fique bloqueada esperando. Esse recurso é extremamente útil para tarefas demoradas, uma vez que permite retornar status sobre a execução da tarefa enquanto ela acontece, mesmo que seja pela interface WEB.

Criando o Job

Para colocar uma tarefa em background basta implementar a classe Job e iniciar a execução dentro do método runJob().

Exemplo Tarefa em Execução em Background
Job job = new Job("Título da Tarefa") { //O título da tarefa é colocado no título da thread, o que ajuda no DEV.
  @Override
  public Object runJob(Job job, JobStatus jobStatus) throws Throwable {
    //Código para execução da tarefa...
  }
};
job.start(); //inicia a tarefa


Quando a tarefa iniciar, o método runJob() receberá dois parâmetros:

  • job - referência da própria instância do Job criado.
  • jobStatus - Instância do objeto de status do job. Dentro desse objeto a tarefa deve atualizar as propriedades como "mensagens", percentual de tarefa realizada, etc..


JobStatus é passado por referência
Note que este objeto é passado como referência de memória, isto é, ele não precisa ser retornado o conteúdo interno dele é "atualizado" automaticamente para todos que tiverem a mesma referência. O objeto JobStatus é um objeto final dentro da classe Job.

Este objeto é tanto o que é passado para dentro dos métodos, quanto para o métodos que solicitam informação sobre o andamento do Job.

Deve-se tomar cuidado quando esse objeto for enviado para outras partes do sistema, como por exemplo atravessar uma fachada, ou qualquer outra situação em que o objeto é clonado e não mais usada a mesma instância da memória. Nestes casos alterações no novo objeto não refletirão no objeto original.


Interrompendo o Job

O cancelamento do Job em segundo plano pode ser solicitado pelo usuário. Para que isso ocorra a interface deve chamar o método interrupt() do JobStatus. Ao chamar esse método o JobStatus ficará marcado que a tarefa obteve a solicitação de cancelamento de sua execução.


Note que depende do Job Implementado verificar se essa solicitação foi realizada e parar a execução da tarefa tomando as providências necessárias (como dar rollback em tarefas pela metade ou concluí-las e cancelar depois, etc.). Há duas maneiras do Job verificar esse cancelamento:

  • Verificação Manual - Na verificação manual o desenvolvedor pode utilizar o método getInterruptResquested() e obter o valor do atributo. Caso true, indica que a solicitação de cancelamento foi realizada e deste ponto em diante direcionar o método para os procedimentos de cancelamento, e lançar a exception.
    Recomendado lançar a exception:
    throw new BISValidationException("FW_ERROR_000004");
    Este Código de exception é utilizado para identificar que a operação foi cancelada pelo usuário.
  • Verificação Automática - Neste modo basta o desenvolvedor incluir a chamada do método checkInterrupt() do JonMonitor. Este método verifica o status do atributo e se tiver em true já lança a exception como citada acima (Validation com o código 'FW_ERROR_000004'). Só é preciso tomar cuidado para que o próprio código do Job não trate a exception e deixe que ela vaze para fora do método do Job.
    Note que neste método a exception é lançada diretamente, assim, o método que verifica o status deve ser bem posicionado. Ou realizar as tratativas no catch/finally.

JobMonitor

O JobMonitor é uma classe estática, como um Singleton, que controla e mantém todos os Jobs do Sistema. Ao criar uma instância de Job ele automaticamente se registra no JobMonitor. Mesmo antes de iniciar a tarefa o JobMonitor já tem a referência da classe e monitora seu status.

Quando o Job é criado, ela cria também uma instância final da JobStatus que tem um identificador único (UUID). Este UUID é o identificador do Job e pode ser utilizado para realizar operações no JobMonitor.


JobMonitor por JVM
Uma vez que JobMonitor é uma classe estática, ela é única por JVM e só será possível encontrar referência do Job se estivermos procurando dentro da mesma JVM em que o Job foi criado.

Em casos em que o Job é criado em outra JVM, será preciso implementar métodos de acesso ao JobMonitor remoto para realizar as operações desejadas.


Ao criar um Job, devemos nos preocupar em salvar o JobStatus, ou ao menos o UUID para que seja possível referenciar a tarefa em background a partir do JobMonitor.


Exemplo de Criação do Job e Obtenção do Status a partir do JobMonitor
//Implementação do Job
Job job = new Job("Título da Tarefa") { ... }; 

//Recuepra o objeto que mantém o status de progresso diretamente da tarefa
JobStatus status = job.getStatus();

//Obtemos e salvamos para referência futura o UUID do Job
String uuid = status.getUuid();

//Obtendo o JobStatus a partir do JobMonitor com o UUID do job.
JobStatus status2 = JobMonitor.getJobStatus(uuid);


Note que no exemplo acima o .start() do job pode ser dado a qualquer momento. O job não precisa estar sendo executado para que todo o restante funcione.


JobStatus Referência em Memória
Note que o JobStatus é um objeto final dentro do Job, e que não é criada uma nova instância para cada alteração. Isto quer dizer que o objeto pode ser manipulado dentro do Job e as alterações serem lidas concorrentemente em outra Thread. De certa forma o JobStatus funciona apenas como "ponteiros de memória" indicando onde as informações estão sendo escritas.


Um ponto a se observar aqui é que, embora o JobStatus possa ser recuperado e ser único na memória quando estamos dentro da mesma JVM, é preciso observar que ao devolver o JobStatus através de uma fachada utilize o SessionManager#SMInterceptor ele será clonado justamente para prevenir esse tipo de apontamento e modificação "no objeto original". Assim, não mais representará o mesmo objeto na memória. Ao retornar o objeto pela fachada, o outro lado deve utilizar o jobUUID para recuperar o objeto correto do JobMonitor para receber atualizações diretamente.

JobChecker

JobChecker é a implementação de uma Thread que verifica periodicamente a tarefa sem segundo plano e notifica conforme atualização do objeto de status.

Para criar o JobChecker basta passar o UUID do job na sua construção:

JobChecker verificando o progresso da tarefa
JobChecker jobChecker = new JobChecker(uuid, true) {
  @Override
  public void updateStatus(JobStatus jobStatus) {
    // Código para lêr o conteúdo do JobStatus e atualizar qualquer sistema

    // Abaixo um código exemplo para atualizar o conteúdo da tela do Vaadin.
    // É necessário obter a UI do usuário e criar uma classe de acesso primeiro
    ui.access(new Runnable() {
      @Override
      public void run() {
        // Código do Vaadin para atualizar a UI do usuário
      }
    });
  }
};

Note que no construtor foi passado além da UUID um booleano 'true'. Esse boleano indica se quando o JobChecker detectar que a tarefa finalizou ele mesmo se encarrega de avisar o JobMonitor que ele pode descartar a tarefa e seu status.

Isso é importante porque mesmo que o Job termine, o JobMonitor não sabe até quando deve guardar a referência do JobStatus para ser consultado, assim é importante que o descarte seja autorizado para economizar recursos.

Para forçar esse descarte manualmente, caso não se use o JobChecker ou se passe o atributo como false, deve ser chamado o método:

JobMonitor.cleanJob(uuid);

Caso esse método não seja chamado, no momento em que este documento é escrito, o JobMonitor tem um tempo limite de 6h para manter o Job em memória desde o momento em que a instância do Job é criado. Quando o job é removido de forma forçada, um logImprovement() é chamado com o título do Job.

Job & SessionManager

Ao criar um novo objeto Job, ele mesmo tenta detectar se estamos em um ambiente com uma Session associada a Thread atual. Se encontrar ele automaticamente transfere a sessão de autenticação atual para a nova Thread do Job. A vantagem dessa operação é que o método SessionManager.getSession() passará a funcionar dentro do método run() do Job, bem como permitirá que outros serviços como FWLogger encontre a sessão do usuário.

Transferir Session não Mantém a Transação CMT
Note que o SessionVO passa a fazer parte da nova Thread, mas caso a Thread principal retorne e "volte a fachada de acesso" o container que controla a transação CMT fechará a transação dando commit(). Ou seja, mesmo que o Job tenha acesso ao SessionVO, não indica que ele poderá executar acessos à banco de dados ou outros serviços que dependem de uma Transaction. Esses tipos de chamadas precisam que o Job faça outra chamada através da fachada, mas podem obter o UUID ou Token da sessão recebida para realizar nova autenticação na fachada.