4. gRPC: tratamento de erros e métodos remotos bloqueantes
Objetivos desta aula:
- Aprender a enviar e receber erros com gRPC
- Aprender a implementar métodos remotos bloqueantes
Materiais de apoio à aula
- Tratamento de erros com gRPC Concorrência e Sincronização em Java
Exercício
O ponto de partida será a solução construída pelo seu grupo na aula anterior para o Jogo do Galo em gRPC.
O objetivo deste novo exercício é estender essa solução de modo a ser devolvido um erro caso um pedido de jogada leve argumentos inválidos, assim como adicionar-lhe alguns testes unitários.
Vamos então começar!
Enviar informação de erro do servidor para o cliente
Vamos agora adicionar um retorno de erro ao servidor caso a mensagem do pedido seja com uma jogada fora do tabuleiro. Relembramos que a operação play recebe o nome do jogador, e a coluna e a linha em que o mesmo pretende fazer umas jogada.
-
Comece por aprender sobre ler os materiais sobre o tratamento de erros com gRPC. TODO: passar estes materiais para aqui
TODO: rever para dar menos código pronto
-
Vamos agora estender a sua solução. No servidor, comece por importar a definição de um estado de erro para argumentos inválidos:
import static io.grpc.Status.INVALID_ARGUMENT; ...
Verifique se a jogada está fora do tabuleiro e, em caso afirmativo, devolver o erro.
... PlayResult result = ttt.play(row, column, player); if (result == PlayResult.OUT_OF_BOUNDS){ responseObserver.onError(INVALID_ARGUMENT.withDescription("Input has to be a valid position").asRuntimeException()); } else{ // Send a single response through the stream. PlayResponse response = PlayResponse.newBuilder().setPlay(result).build(); responseObserver.onNext(response); // Notify the client that the operation has been completed. responseObserver.onCompleted(); } ...
-
Do lado do cliente, deve apanhar uma exceção e imprimir a mensagem de erro:
play_res = null; ... try{ play_res = stub.play(PlayRequest.newBuilder().setRow(row).setColumn(column).setPlayer(player).build()).getPlay(); if (play_res != PlayResult.SUCCESS) { displayResult(play_res); } } catch (StatusRuntimeException e) { System.out.println("Caught exception with description: " + e.getStatus().getDescription()); }
Implementar um método bloqueante
Vamos agora adicionar uma variante bloqueante da operação checkWinner.
- No ficheiro
.proto
, acrescente uma nova operação chamadawaitForWinner
, cujas mensagens de pedido e respostas são idênticas às da operaçãocheckWinner
. A grande diferença é que awaitForWinner
deve bloquear-se enquanto o jogo não tiver terminado. - Depois de gerar os novos stubs, acrescente o novo método à classe do servidor.
- Relembre as primitivas para programação concorrente em Java.
- No novo método, use a primitiva
wait()
para, enquanto o jogo não tenha ainda terminado, a thread que executa esse método se bloquear. Lembre-se que, para chamarwait()
, precisa estar dentro de um método (ou bloco) synchronized. - Precisa também chamar
notifyAll()
sempre que o estado do jogo muda com uma nova jogada. - Finalmente, estenda o cliente para também invocar esta nova operação.
- Experimente! Lance um cliente que fará as jogadas. Em paralelo, lance outro cliente que simplesmente invoca
waitForWinner
.
Já resolveram?
Podem conferir a nossa proposta de resolução.
Nota: esta solução resolve o conjunto dos exercícios deste guião e do anterior.