Jetty 6: Pseudo-Continuations
Este blog deixou de ser mantido, mas o autor continua escrevendo aqui. Não deixe de assinar o novo feed!
Espero que essa foto ao lado - da Jéssica - (não clique no link se estiver no trabalho!) não te faça a fechar o browser. Garanto que, ao final do post, você entenderá a minha intenção em colocar a foto de uma boneca inflável (ou uma pseudo-mulher, enfim).
Baboseiras a parte, hoje, durante a minha leitura diária (e obrigatória) de feeds, encontrei em meio aos artigos do DZone um artigo que parecia ser bastante interessante. Tratava-se de um artigo entitulado Ajax for Java developers: Write scalable Comet applications with Jetty and Direct Web Remoting, publicado no DeveloperWorks por Philip McCarthy.
Como gostei da introdução, achei que seria legal ler esse artigo, até porque trata-se de um assunto que será cada vez mais abordado: aplicações Comet. Para minha surpresa, este artigo se revelou mais interessante do que eu imaginava (não exatamente no bom sentido), visto que ele explica algumas particularidades do Jetty 6, focando bastante na implementação de Continuations deste.
Não sei se você leu tal artigo (ou sabe como funciona a implementação de Continuations do Jetty), mas eu particularmente não via uma gambiarra tão… gambiarra há um bom tempo.
Você pode conferir a tradução de alguns trechos do referido artigo no decorrer deste post. E tire suas próprias conclusões.
Trechos traduzidos do artigo
[...] Explicarei o código do servlet abaixo mais adiante.
ContinuationServlet
-
-
public class ContinuationServlet extends HttpServlet {
-
-
public void service(HttpServletRequest req, HttpServletResponse res)
-
-
-
Continuation cc = ContinuationSupport.getContinuation(req,null);
-
-
res.setContentType("text/plain");
-
res.getWriter().flush();
-
-
cc.suspend(2000);
-
-
}
-
}
-
A listagem abaixo mostra a saída de cinco requisições simultâneas ao ContinuationServlet:
$ for i in 'seq 1 5' ; do lynx -dump localhost:8080/continuation?id=$i & done Request: 1 start: Sun Jul 01 13:37:37 BST 2007 Request: 1 start: Sun Jul 01 13:37:39 BST 2007 Request: 1 end: Sun Jul 01 13:37:39 BST 2007 Request: 3 start: Sun Jul 01 13:37:37 BST 2007 Request: 3 start: Sun Jul 01 13:37:39 BST 2007 Request: 3 end: Sun Jul 01 13:37:39 BST 2007 Request: 2 start: Sun Jul 01 13:37:37 BST 2007 Request: 2 start: Sun Jul 01 13:37:39 BST 2007 Request: 2 end: Sun Jul 01 13:37:39 BST 2007 Request: 5 start: Sun Jul 01 13:37:37 BST 2007 Request: 5 start: Sun Jul 01 13:37:39 BST 2007 Request: 5 end: Sun Jul 01 13:37:39 BST 2007 Request: 4 start: Sun Jul 01 13:37:37 BST 2007 Request: 4 start: Sun Jul 01 13:37:39 BST 2007 Request: 4 end: Sun Jul 01 13:37:39 BST 2007
Existem duas coisas importantes que podem ser percebidas nessa listagem. Primeiro, cada mensagem de início aparece duas vezes; não se preocupe com isso agora. Segundo, e mais importante, as requisições são tratadas concorrentemente, sem enfileiramento (nota: em um passo anterior, o autor configurou o Jetty para que este só usasse uma thread para atender às requisições). Veja que os timestamps de todas as mensagens de início e fim são as mesmas. Conseqüentemente, nenhuma requisição demora mais que dois segundos para completar, mesmo que apenas uma thread seja dedicada a tratar as requisições para o Servlet.
Conhecendo o mecanismo de Continuations do Jetty
Um entendimento de como o mecanismo de Continuations do Jetty é implementado irá explicar os efeitos que você vê na listagem. Para usar Continuations, Jetty deve ser configurado para tratar requisições com seu SelectChannelConnector. Este connector é feito sobre as APIs java.nio, permitindo que o mesmo mantenha conexões abertas sem consumir uma thread para cada uma destas conexões. Quando o SelectChannelConnector é usado, o método ContinuationSupport.getContinuation() fornece uma instância de SelectChannelConnector.RetryContinuation. [...] Quando o método suspend() é chamado no RetryContinuation, ele lança uma runtime exception especial - RetryRequest - que se propaga para fora do servlet através do filter chain e é tratada em SelectChannelSelector. Mas em vez de enviar qualquer resposta ao cliente como resultado da exception, a requisição é mantida em uma fila de Continuations pendentes, e a conexão HTTP é mantida aberta. Neste ponto, a thread que foi usada para servir a requisição é retornada à ThreadPool, onde ela pode ser usada para servir a outra requisição.
A requisição suspensa é mantida nessa fila até que o período de timeout expire ou o método resume() seja chamado nesta Continuation. Quando uma destas condições ocorrerem, a requisição é resubmetida para o servlet (através do filter chain). O efeito disso é que toda a requisição é “refeita” até o ponto onde o método suspend() foi chamado inicialmente. Então, quando o código alcança o método suspend() pela segunda vez, a exceção RetryRequest não é lançada e a execução continua normalmente.
Tornando as Continuations úteis
[...] O método resume() forma um par com o método suspend(). Você pode pensar neles como sendo as equivalentes em Continuation dos métodos wait() e notify() da classe Object padrão. Isto é, suspend() coloca uma Continuation em espera até que o timeout expire ou o método resume() seja invocado por outra thread [...].
Entretanto, diferentemente de Continuations implementadas em nível de linguagem como em Scheme, ou mesmo o paradigma dos métodos wait() / notify() de Java, chamar resume() não implica que a execução do código continue do ponto exato onde ele parou anteriormente. Como você viu, o que acontece é que a request associada à Continuation é “refeita”. Isto resulta em dois problemas: re-execução de código não desejável (como as mensagens de início repetidas, na listagem mostrada anteriormente) e perda de estado: qualquer coisa no escopo é perdida quando uma chamada a suspend() é realizada.
A solução para o primeiro problema é o método isPending(). Se o retorno de isPending() for true, isso significa que suspend() foi chamado anteriormente, e essa nova execução da requisição ainda não atingiu o método suspend() pela segunda vez. Em outras palavras, fazendo o código anterior ao suspend() usar o método isPending() assegura que o trecho em questão será executado uma única vez. Continuation também oferece um mecanismo simples para preservar estado: os métodos putObject(Object) e getObject(). Use estes métodos para armazenar um objeto de contexto com qualquer estado que você precise preservar quando a Continuation é suspensa.
WTF?!
No decorrer da tradução eu tentei destacar alguns trechos non-sense. Percebeu como funciona o esquema de Continuations no Jetty? O negócio interrompe a requisição atual com o lançamento de uma unchecked exception e simula o re-envio da mesma quando a Continuation for resumida. Para começo de conversa, desde quando eu era gurizinho lá em Barbacena, Continuations não tem nada a ver com isso. Por esse motivo, a palavra Continuation no contexto desse post deveria estar entre aspas, pois isso não é uma Continuation. É uma “Continuation”.
Seria equivalente criarmos um toca-fitas cujo botão pause rebobinasse a fita e a tocasse novamente (com a diferença de que esta ainda armazena o estado).
Abaixo segue um trecho retirado do Wikipedia que tenta definir uma Continuation. Mesmo sendo uma definição um tanto quanto superficial, creio que ela já nos ajuda aqui:
Em computação, uma Continuation é uma representação de algum estado de execução de um programa (frequentemente a pilha de chamadas e o ponteiro para a instrução atual) em um dado momento. Muitas linguagens possuem construções que possibilitam um programador a salvar tal estado em objetos, de modo restaurá-lo em um momento futuro.
Pois bem, como dito pelo próprio Philip McCarthy, essa implementação de Continuations do Jetty não armazena estado. O que ele faz é simular um replay da requisição interrompida. Deste modo, Jetty não implementa Continuations! Ponto.
Apesar disso, devo dizer que o esforço do pessoal do Jetty em criar algo nesse sentido é louvável. Pena que esforços estão sendo depositados no lugar errado, uma vez que faz muito mais sentido fornecer uma implementação Java nativa de Continuations em vez de workarounds como este.
Infelizemente, essa situação toda me faz lembrar da chatisse que é o processo de se adicionar melhorias na linguagem Java (como acontece hoje com closures). Já sabemos muito bem como tudo funciona: 239 propostas diferentes de Continuations são submetidas até que, após vários meses de discussões e flames, uma delas é escolhida para ser implementada na segunda release após a atual (mais ou menos uns dois anos, imagino). Então eu pergunto: qual será aproximadamente o número de gambiarras, digo, implementações de Continuations teremos até lá?


26 de julho de 2007 às 11:26 am
Olá Daniel,
eu estava escrevendo um post esses dias sobre Continuation, mas acabou ficando na lista de rascunhos e ainda não terminei. Então queria fazer uma contribuição aqui pro seu post.
Uma boa definição, e de fácil entendimento eu achei no projeto Cocoon da Apache. Na documentação de pageflow eles dizem o seguinte:
“…Think of a continuation as an object that, for a given point in your program, contains a snapshot of the stack trace, including all the local variables, and the program counter. You can not only store these things in the continuation object, but also restore the execution of the program from a continuation object. This means that the stack trace and the program counter of the running program become the ones stored in a continuation.”
Valeu
26 de julho de 2007 às 11:36 am
Tá aí uma boa definição! E, interessante, pois isso é tudo o que o Jetty não faz na sua implementação de Continuations.
Aliás, nem sei se isso é possível de ser implementado em Java hoje…
26 de julho de 2007 às 4:11 pm
Pois é… segundo vc descreveu ai, o Jetty não tem nada do que é realmente uma Continuation.
Em Java não sei como ficaria. Justamente pelo Grafo de objetos e tudo mais. Eu penso que ele teria que começar a guardar valores e não referências para isso, pq no momento em que voltar para a continuation, o objeto pode não existir… É complexo.
Eu estava lendo sobre Cocoon com Continuation utilizando uma versão especial do Rhino.
Não li a fundo, mas me parece mesmo bem bacaninha. E ele guarda as referencias, e não os valores. Não tenho conhecimento para dizer se isso é bom, ou não. E nem qual a melhor maneira de se fazer. Mas no mínimo interessante, este link é bacaninha, vale a pena ler.