Несколько таймеров в @Singleton EJB вызывают исключение javax.ejb.ConcurrentAccessTimeoutException

#jakarta-ee #timer #ejb

#джакарта-ee #таймер #ejb

Вопрос:

У меня есть одноэлементное планирование, в котором я пытаюсь управлять несколькими календарными таймерами. У меня есть служба, которую я вызываю каждые 30 минут. Однако один раз в день (например, в 3 часа ночи) мне нужно вызвать эту же службу, чтобы сделать что-то другое.

Главный вопрос — может ли у вас быть несколько таймеров в одном и том же синглтоне?

Когда я смотрю на код, мне кажется, что в какой-то момент (3 часа ночи) два таймера столкнутся, и я получу ConcurrentAccessTimeoutException, и один из них не будет запущен. Например:

 Caused by: javax.ejb.ConcurrentAccessTimeoutException: WFLYEJB0241: EJB 3.1 PFD2 4.8.5.5.1 concurrent access timeout on TestBean- could not obtain lock within 5000MILLISECONDS
  

Может быть, если я сначала создам таймер 3AM в PostConstruct, будет больше вероятности, что он запустится и заблокирует таймер every30? Но даже если это возможно, это кажется а) небольшим трюком б) оставляя все на волю случая. Я могу смириться с тем, что таймер every30 не запускается в 3 часа ночи, но один запуск нельзя пропустить, поскольку это единственный раз, когда он запускается. Другим вариантом было бы установить таймер every30 на что-то, где он не будет запускаться одновременно с таймером один раз в день, но опять же, обманчиво.

Вот мой код:

 @Singleton
@Startup
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class SomeScheduler
{

   @Resource
   private TimerService timerService;   
   
   @Inject
   @ConfiguredProperty(key="scheduleExpression1", mandatory=false, defaultValue="minute=*/30; hour=*")
   private ScheduleExpression scheduleExpression1;   
   
   @Inject
   @ConfiguredProperty(key="scheduleExpression2", mandatory=false, defaultValue="hour=3; dayOfWeek=*")
   private ScheduleExpression scheduleExpression2;      
   
   private Timer timer1;
   private Timer timer2;
   
   
   @PostConstruct
   private void postConstruct()
   {
     timer1 = timerService.createCalendarTimer(scheduleExpression1, new TimerConfig("every30", false));
     timer2 = timerService.createCalendarTimer(scheduleExpression2, new TimerConfig("at3AM", false));
   }
      
   @Timeout
   public void handler (Timer timer) 
   { 
     if (Objects.equals(timer.getInfo(), "every30"))            
     {
        // Call Something
     }
     if (Objects.equals(timer.getInfo(), "at3AM"))
     {
        // Call Something else
     }
   } 

}
  

Комментарии:

1. Являются ли задачи конфликтующими и не могут быть запущены одновременно? Вы могли бы ослабить ограничения параллелизма, или вы также могли бы установить больший тайм-аут, поэтому, если две задачи сталкиваются, это может подождать

2. Они сталкиваются. В качестве эксперимента установите TIMER1 для запуска каждые две минуты, а TIMER2 — каждые 10. Через 10 минут TIMER2 запустился, и я получил несколько сообщений ConcurrentAccessTimeoutException, относящихся к TIMER1. Я попробую установить AccessTimeout на что-то большее, чтобы посмотреть, поможет ли это. Часть моего вопроса заключается в том, считается ли наличие нескольких таймеров в одном классе, подобном этому, плохой практикой?

3. Если вы используете управление параллелизмом по умолчанию для одиночных элементов, тайм-ауты не могут обрабатываться параллельно. Поэтому вам нужно установить тайм-аут, достаточный для завершения запущенной задачи, или использовать «-1». Если ваши задачи могут выполняться в parelel, вы могли бы использовать @Lock(READ)

Ответ №1:

Да, у вас может быть несколько таймеров в одном и том же синглтоне.
Но вы должны отключить «Управляемый контейнером параллельный доступ», пометив свой синглтон с помощью:

 @ConcurrencyManagement(ConcurrencyManagementType.BEAN)
  

Ответ №2:

По умолчанию общедоступные методы в @Singleton компонентах имеют @Lock(WRITE) . Если вам нужно запустить один и тот же метод несколько раз одновременно, и вы в безопасности с условиями гонки, тогда вы можете добавить аннотацию @Lock(READ) поверх метода:

https://docs.oracle.com/cd/E19798-01/821-1841/gipsz/index.html

Если вы НЕ уверены в состоянии гонки в этом конкретном методе, вы можете увеличить время ожидания доступа с помощью аннотации: @AccessTimeout

Это решит ConcurrentAccessTimeoutException .