Hi, I have a customer support chat webapp where when the attendant session expires, I need to end all his active conversations and change his status to offline, so I implemented a HttpSessionListener.
Since some attendants log in the system multiple times, they where being disconnect incorrectly if the second session ended before the first... To solve this, I started to track the sessions. First I though about something application scoped, but as I'm using parallel deployment, I needed something that would be common to all the versions of the webapp, so I chose the database(MySQL 5.6). I have a table with the following columns: recordId, userId, sessionId, appVersion, sessionStart and sessionEnd. When the attendant log in, a new record is created, leaving sessionEnd with null. On HttpSessionListener.sessionDestroyed(), I set sessionEnd with the current timestamp, then I query the database to see if there are additional sessions active for him. If not, I end all his active conversations and change his status to offline. Now the problem is the opposite, some attendants are not disconnected automatically. Looking at the session tracking table, I see some records with sessionEnd == null, but the sessionId of the record is not listed on the server's current active session list of any version of the webapp. The first thing I do on my sessionDestroyed() method is to log the sessionId that is destroyed. So in case my business logic failed, at least this line I should see on the log before any possible exceptions. But there is nothing there, what makes me think that the method was not called at all. This webapp uses Apache Shiro 1.2.3. Server is restarted daily at dawn to reset PermGen... I thought it could be related to this issue(56657 <https://bz.apache.org/bugzilla/show_bug.cgi?id=56657>), since I was using version 7.0.47. But I upgraded to 7.0.62 and it didn't solved, so I tried to disable session persistence. Still no luck. Any ideas? Thanks in advance *Session listener code:* @WebListener public class SessionListener implements HttpSessionListener { @PersistenceUnit(unitName = MySqlListener.PERSISTENCE_UNIT) private EntityManagerFactory emf; @Override public void sessionCreated(final HttpSessionEvent sessionEvent) { Logger.getLogger(getClass().getName()).info("create " + sessionEvent.getSession().getId()); } @Override public void sessionDestroyed(final HttpSessionEvent sessionEvent) { EntityManager em = null; try { Logger.getLogger(getClass().getName()).info("destroy " + sessionEvent.getSession().getId()); final HttpSession session = sessionEvent.getSession(); /** * Nesse caso não há requisição nem FacesContext, portanto o EntityManager será passado * por referência para que a mesma instância seja usada em todas as operações */ em = emf.createEntityManager(); TbTecnicoService.destroySession(session.getId(), em); final TBTecnico tecnico = (TBTecnico) session.getAttribute(TbTecnicoService.TECNICO_LOGADO); if(tecnico != null) { final Long totalSessoesAtivas = new SessaoColaboradorService(em).countSessoesAtivasByColaborador(tecnico.getId()); if(totalSessoesAtivas < 1) { try { final Atendente atendente = new AtendenteService(em).findByTecnico(tecnico); final AtendenteDisponivel atendenteDisponivel = new AtendenteDisponivelService(em).findAtendenteOnline(atendente); // Caso o atendente tenha clicado em "Sair" no portal considera como logout manual final boolean manualLogout = Converter.toBoolean(session.getAttribute(Constants.MANUAL_LOGOUT)); new AtendimentoService(em).desconectarAtendente(atendente, manualLogout, atendenteDisponivel.isPush()); } catch(final NoResultException ex) { // Técnico não é um atendente ou o atendente não estava online } if(tecnico.getSetor().getId() == TbSetor.DESENVOLVIMENTO) { final ChamadoService chamadoService = new ChamadoService(em); chamadoService.pause(tecnico); } } } } catch(final Exception e) { GestaoErroClient.persist(IdAplicacao.COLABORADOR, JsfUtil.getVersion(), getClass().getName(), e); } finally { EntityManagerHandler.close(em); } } }