import java.util.concurrent.TimeUnit;
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface AccessTimeout {
long value();
TimeUnit unit() default TimeUnit.MILLISECONDS;
}
Anotação @AccessTimeout
Antes de darmos uma olhada no @AccessTimeout
, pode ajudar entender quando uma chamada precisa "esperar".
Esperando…
Stateful Bean
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.
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 elementounit
. -
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!
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 jakarta.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 jakarta.ejb.EJBException if the second client is a local client"
Ou seja pode receber jakarta.ejb.ConcurrentAccessException . Ou no caso de EJB 2.1 estar sendo utilizado pode receber java.rmi.RemoteException se for um cliente externo ou jakarta.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 jakarta.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.
@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.
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
mvn clean test
Saida
-------------------------------------------------------
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 'jakarta.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