blob: 34a0f048adc2246ae5cd2ad1dbe6c0f9aa977133 [file] [log] [blame]
= Anotação @AccessTimeout
:index-group: EJB
:jbake-type: page
:jbake-status: published
Antes de darmos uma olhada no `@AccessTimeout`, pode ajudar entender quando uma chamada precisa "esperar".
== Esperando...
=== Stateful Bean
[note]
NOTE: Por padrão, é permitido que clientes façam chamadas concorrentes para um stateful session object e o container é requerido para serializar cada requisição concorrente. O container nunca permite acesso multi-thread a instancia de um componente stateful. Por essa razão, métodos de leitura/escrita bloqueiam metadados assim como componentes que gerenciam a própria concorrência (bean-managed concurrency) não são aplicáveis a um stateful session bean e não devem ser usados.
Isso significa que quando um método `foo()` de uma instancia stateful esta sendo executado e uma chega uma segunda requisição para esse ou outro método, o container EJB serializa a segunda requisição. Isso não permite com que o método seja executado concorrentemente mas espere ate o primeiro método ser processado.
O cliente também tem de esperar quando o bean `@Stateful` esta em uma transação e o cliente o invocar de fora dessa transação.
=== Stateless Bean
Se existir 20 instancias de um bean no pool e todas elas estiverem ocupadas, quando uma nova requisição chegar, o processo *tem de esperar* ate algum bean estar disponível no pool. (Nota: a semântica de pool, se houver, não é coberta pela especificação. O server vendor pode ou não envolver uma condição de espera)
=== Singleton Bean
O container força um acesso single-thread por padrão para um componente singleton. Isso é parecido com o que a anotação `@Lock(WRITE)` faz. Quando um método anotado com `@Lock(WRITE)` é executado, todos os outros métodos `@Lock(WRITE)` e `@Lock(READ)` que são chamados tem de esperar até que ele termine sua execução.
=== Resumindo
- `@Singleton` - Um método `@Lock(WRITE)` esta sendo invocado e o gerenciamento de concorrência pelo container esta sendo usado. Todos os métodos sao `@Lock(WRITE)` por padrão.
- `@Stateful` - Qualquer método de uma instancia pode estar sendo utilizado e uma segunda chamada pode acontecer. Ou o bean `@Stateful` esta em uma transação e o cliente o chama de fora dessa transação.
- `@Stateless` - Sem instancias disponíveis no pool. Como observado, a semântica de pool (se houver) não é coberta pela especificação. Caso exista uma semântica no server vendor que envolva uma condição de espera, a anotação `@AccessTimeout` deveria ser aplicada.
== `@AccessTimeout`
A anotação `@AccessTimeout` é simplesmente uma conveniência em torno da tupla `long` e `TimeUnit` comumente usadas na `java.util.concurrent` API.
[source,java,numbered]
----
import java.util.concurrent.TimeUnit;
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface AccessTimeout {
long value();
TimeUnit unit() default TimeUnit.MILLISECONDS;
}
----
== Uso
Um método ou uma classe pode ser anotada com `@AccessTimeout` para especificar o temo máximo que uma chamada deve esperar para acessar o bean quando acontecer uma condição de espera.
A semântica para o `value` é a seguinte:
- `value` > 0 indica um tempo de espera que é especificado pelo elemento `unit`.
- `value` de 0 significa que acesso concorrente não é permitido.
- `value` de -1 indica que a chamada do cliente vai ficar bloqueada por tempo indeterminado ate que possa proceder.
Simples!
NOTE: Quando acontecer um timeout, qual exceção o cliente recebe?
Citando a especificação: "if a client-invoked business method is in progress on an instance when another client-invoked call, from the same or different client, arrives at the same instance of a stateful session bean, if the second client is a client of the bean's business interface or no-interface view, the concurrent invocation must result in the second client receiving a javax.ejb.ConcurrentAccessException[15]. If the EJB 2.1 client view is used, the container must throw a java.rmi.RemoteException if the second client is a remote client, or a javax.ejb.EJBException if the second client is a local client"
Ou seja pode receber `javax.ejb.ConcurrentAccessException`. Ou no caso de EJB 2.1 estar sendo utilizado pode receber `java.rmi.RemoteException` se for um cliente externo ou `javax.ejb.EJBException` se for local.
=== Sem padrão
Note que o atributo `value` não tem um valor padrão. Isso foi intencional, tendo a intenção de informar que se o `@AccessTimeout` não for explicitamente usado, o comportamento sera o do server vendor.
Alguns servidores vão esperar por um tempo pre determinado e lançar a exceção `javax.ejb.ConcurrentAccessException`, outros podem lançar de imediato.
== Exemplo
Aqui nos temos um simples `@Singleton` bean que possui tres métodos síncronos e um método anotado com `@Asynchronous`. O componente esta anotado com `@Lock(WRITE)` então apenas uma thread pode acessar o `@Singleton` por vez. Este é o comportamento padrão de um componente `@Singleton`, então usar a anotação `@Lock(WRITE)` não é necessário mas é importante para deixar claro que o componente tem um comportamento single-thread.
[source,java,numbered]
----
@Singleton
@Lock(WRITE)
public class BusyBee {
@Asynchronous
public Future stayBusy(CountDownLatch ready) {
ready.countDown();
try {
new CountDownLatch(1).await();
} catch (InterruptedException e) {
Thread.interrupted();
}
return null;
}
@AccessTimeout(0)
public void doItNow() {
// faz alguma coisa
}
@AccessTimeout(value = 5, unit = TimeUnit.SECONDS)
public void doItSoon() {
// faz alguma coisa
}
@AccessTimeout(-1)
public void justDoIt() {
// faz alguma coisa
}
}
----
O método `@Asynchronous` não tem uma relação direta com o `@AccessTimeout`, mas serve como uma forma simple de travar ("lockar") o bean para realizarmos o teste. Ele nos permite testar o comportamento concorrente do componente.
[source,java,numbered]
----
public class BusyBeeTest extends TestCase {
public void test() throws Exception {
final Context context = EJBContainer.createEJBContainer().getContext();
final CountDownLatch ready = new CountDownLatch(1);
final BusyBee busyBee = (BusyBee) context.lookup("java:global/access-timeout/BusyBee");
// Esse método assíncrono nunca termina.
busyBee.stayBusy(ready);
// Você ainda esta trabalhando abelhinha?
ready.await();
// Beleza, a abelha esta ocupada.
{ // Timeout imediato
final long start = System.nanoTime();
try {
busyBee.doItNow();
fail("A abelha continua ocupada");
} catch (Exception e) {
// A abelha continua muito ocupada como esperado.
}
assertEquals(0, seconds(start));
}
{ // Timeout em 5 segundos
final long start = System.nanoTime();
try {
busyBee.doItSoon();
fail("A abelha deve estar ocupada");
} catch (Exception e) {
// A abelha continua ocupada como esperado.
}
assertEquals(5, seconds(start));
}
// Esse método vai te fazer esperar para sempre, apenas teste se estiver com bastante tempo :D
// busyBee.justDoIt();
}
private long seconds(long start) {
return TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start);
}
}
----
== Executando
[source,bash]
----
mvn clean test
----
=== Saida
[source,bash]
----
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running org.superbiz.accesstimeout.BusyBeeTest
Apache OpenEJB 4.0.0-beta-1 build: 20111002-04:06
http://tomee.apache.org/
INFO - openejb.home = /Users/dblevins/examples/access-timeout
INFO - openejb.base = /Users/dblevins/examples/access-timeout
INFO - Using 'javax.ejb.embeddable.EJBContainer=true'
INFO - Configuring Service(id=Default Security Service, type=SecurityService, provider-id=Default Security Service)
INFO - Configuring Service(id=Default Transaction Manager, type=TransactionManager, provider-id=Default Transaction Manager)
INFO - Found EjbModule in classpath: /Users/dblevins/examples/access-timeout/target/classes
INFO - Beginning load: /Users/dblevins/examples/access-timeout/target/classes
INFO - Configuring enterprise application: /Users/dblevins/examples/access-timeout
INFO - Configuring Service(id=Default Singleton Container, type=Container, provider-id=Default Singleton Container)
INFO - Auto-creating a container for bean BusyBee: Container(type=SINGLETON, id=Default Singleton Container)
INFO - Configuring Service(id=Default Managed Container, type=Container, provider-id=Default Managed Container)
INFO - Auto-creating a container for bean org.superbiz.accesstimeout.BusyBeeTest: Container(type=MANAGED, id=Default Managed Container)
INFO - Enterprise application "/Users/dblevins/examples/access-timeout" loaded.
INFO - Assembling app: /Users/dblevins/examples/access-timeout
INFO - Jndi(name="java:global/access-timeout/BusyBee!org.superbiz.accesstimeout.BusyBee")
INFO - Jndi(name="java:global/access-timeout/BusyBee")
INFO - Jndi(name="java:global/EjbModule748454644/org.superbiz.accesstimeout.BusyBeeTest!org.superbiz.accesstimeout.BusyBeeTest")
INFO - Jndi(name="java:global/EjbModule748454644/org.superbiz.accesstimeout.BusyBeeTest")
INFO - Created Ejb(deployment-id=org.superbiz.accesstimeout.BusyBeeTest, ejb-name=org.superbiz.accesstimeout.BusyBeeTest, container=Default Managed Container)
INFO - Created Ejb(deployment-id=BusyBee, ejb-name=BusyBee, container=Default Singleton Container)
INFO - Started Ejb(deployment-id=org.superbiz.accesstimeout.BusyBeeTest, ejb-name=org.superbiz.accesstimeout.BusyBeeTest, container=Default Managed Container)
INFO - Started Ejb(deployment-id=BusyBee, ejb-name=BusyBee, container=Default Singleton Container)
INFO - Deployed Application(path=/Users/dblevins/examples/access-timeout)
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.071 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
----