JobMonitor: mudanças entre as edições
Linha 105: | Linha 105: | ||
* Caso o método lance qualquer Exception o '''step''' é alterado para '''EXCEPTION''' e a Exception lançada é salva no atributo '''exception''' do '''JobStatus'''. | * Caso o método lance qualquer Exception o '''step''' é alterado para '''EXCEPTION''' e a Exception lançada é salva no atributo '''exception''' do '''JobStatus'''. | ||
Ao finalizar o Job, independente do sucesso, o Job notifica o '''JobMonitor''' de que o Job terminou. Ao realizar essa tarefa, o JobMonitor cancela o Timer que ele criou no registro (explicado na sessão anterior). No entanto ele cria um novo timer (atualmente de | Ao finalizar o Job, independente do sucesso, o Job notifica o '''JobMonitor''' de que o Job terminou. Ao realizar essa tarefa, o JobMonitor cancela o Timer que ele criou no registro (explicado na sessão anterior). No entanto ele cria um novo timer (atualmente de 10min) que eliminará por completo qualquer outra referência do '''Job''' de dentro do '''JobMonitor''' para garantir que todos os recursos serão liberados e estarão disponíveis para o GC. | ||
O '''JobMonitor''' não excluí imediatamente os recursos porque essa notificação ocorre no momento em que a tarefa se extinguiu, e outras Thread ainda podem precisar encontrar o '''JobStatus''' tanto para recuperar o retorno do método (ou a Exception) para notificar o usuário. | O '''JobMonitor''' não excluí imediatamente os recursos porque essa notificação ocorre no momento em que a tarefa se extinguiu, e outras Thread ainda podem precisar encontrar o '''JobStatus''' tanto para recuperar o retorno do método (ou a Exception) para notificar o usuário. |
Edição das 16h25min 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..
![]() |
|
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.
- Recomendado lançar a exception:
- 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.
![]() |
|
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.
![]() |
|
Ciclo de Vida do Job
Abaixo os passos do ciclo de vida do Job para exemplificar como tudo funciona:
- Criamos a tarefa instanciando a classe Job: new Job(...);
- Já no construtor, Job se registra no JobMonitor e ganha seu jobUUID.
- Com o jobUUID recebido, Job cria o objeto final JobStatus.
- Se o Job criado estiver uma Thread que tenha uma sessão do SessionManager, a Thread do Job é anexada à mesma Sessão do usuário.
- Não confundir a sessão se segurança com a Transaction do Container. O Job continuará rodando em uma Tread fora da Transaction, a única situação aqui é que ao chamar SessionManager.getSession() teremos uma identificação/autenticação de segurança.
- A partir da instância de Job que fora criado, podemos obter o JobStatus e o jobUUID através dos respectivos métodos: job.getJobStatus() e job.getJobStatus().getJobUUID()
- Iniciamos a tarefa disparando a Thread do job com job.start();
- Neste momento a Thread do Job começa a ser executada.
- O método deve (provavelmente) retornar o JobStatus ou o jobUUID para que o Caller consiga acompanhar a tarefa e solicitar seu cancelamento.
Dentro do JobMonitor
Dentro do JobMonitor, quando a tarefa se registrou, o JobMonitor cria um Timer de segurança que verifica se o Job não foi esquecido dentro do JobMonitor e não teve seu .start() chamado. Passado o tempo do timer (no momento 1h) se o Job não estiver sendo executado, forçaremos a limpeza do Job como um vazamento de recursos. Caso esteja rodando, faremos um Log de que a tarefa está demorando demais, tarefas do JobMonitor não devem ser demoradas desta forma, tarefas de manutenção devem ser executadas pelo Scheduler. JobMonitor se destina mais para tarefas do usuário que possam demorar um pouco mais que o desejado para deixar a tela travada, não para execuções de longo prazo desse jeito.
Dentro do Job
- Quando instanciado o Job tem o seu atributo step definido como IDLE, indicando que a tarefa está inerte por ainda não ter sido iniciada. Ao chamar o .start();
- Antes da chamada do método runJob(...) o step é atualizado para RUNNING, e deve ficar neste passo enquanto a implementação do método é executada.
- Caso este método retorno normalmente, o objeto retornado é salvo dentro do JobStatus, no atributo jobReturn, e o step é alterado para FINISHED indicando que a tarefa terminou sua execução com sucesso.
- Caso o método lance qualquer Exception o step é alterado para EXCEPTION e a Exception lançada é salva no atributo exception do JobStatus.
Ao finalizar o Job, independente do sucesso, o Job notifica o JobMonitor de que o Job terminou. Ao realizar essa tarefa, o JobMonitor cancela o Timer que ele criou no registro (explicado na sessão anterior). No entanto ele cria um novo timer (atualmente de 10min) que eliminará por completo qualquer outra referência do Job de dentro do JobMonitor para garantir que todos os recursos serão liberados e estarão disponíveis para o GC.
O JobMonitor não excluí imediatamente os recursos porque essa notificação ocorre no momento em que a tarefa se extinguiu, e outras Thread ainda podem precisar encontrar o JobStatus tanto para recuperar o retorno do método (ou a Exception) para notificar o usuário.
Uma vez que a Thread cliente recupera o "último" JobStatus, obtendo seu retorno, é possível (e recomendado) acelerar a liberação dos recursos chamando o método JobMonitor.cleanJob(jobUUID). Assim, tanto os recursos utilizados pelo Job, quando o Timer criado serão liberados. E a Thread cliente fará o que for necessário, salvando ou não, o JobStatus e seus retornos.
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 jobUUID 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 jobUUID 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);
Para suprir a necessidade de encontrar o Job em um JobMonitor de outra JVM, é possível criar o JobChecker passando uma implementação do JobStatusSupplier que seja responsável por realizar a ponte até o JobMonitor correto.
![]() |
|
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.
![]() |
|