IDEMPIERE-5049 Zk Session and Desktop object not destroy immediately after logout (#994)

- Remove 5 second delay for remove of desktop after logout
- added X-PING custom http header for "ping" to index.zul
- added watch for disconnected desktop
This commit is contained in:
hengsin 2021-11-21 20:37:29 +08:00 committed by GitHub
parent 07202f7b61
commit 06698688ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 209 additions and 30 deletions

View File

@ -301,6 +301,14 @@ public class AtmosphereServerPush implements ServerPush {
startClientPush(desktop.get());
}
/**
*
* @return true if it is holding an atmosphere resource
*/
public boolean hasAtmosphereResource() {
return this.resource.get() != null;
}
private class Schedule<T extends Event> {
private EventListener<T> task;
private T event;

View File

@ -44,6 +44,7 @@ import org.adempiere.webui.session.SessionManager;
import org.adempiere.webui.theme.ITheme;
import org.adempiere.webui.theme.ThemeManager;
import org.adempiere.webui.util.BrowserToken;
import org.adempiere.webui.util.DesktopWatchDog;
import org.adempiere.webui.util.UserPreference;
import org.compiere.Adempiere;
import org.compiere.model.MRole;
@ -140,12 +141,20 @@ public class AdempiereWebUI extends Window implements EventListener<Event>, IWeb
public void onCreate()
{
this.getPage().setTitle(ThemeManager.getBrowserTitle());
String ping = Executions.getCurrent().getHeader("X-PING");
if (!Util.isEmpty(ping, true))
{
cleanupForPing();
return;
}
this.getPage().setTitle(ThemeManager.getBrowserTitle());
Executions.getCurrent().getDesktop().enableServerPush(true);
DesktopWatchDog.addDesktop(Executions.getCurrent().getDesktop());
SessionManager.setSessionApplication(this);
Session session = Executions.getCurrent().getDesktop().getSession();
final Session session = Executions.getCurrent().getDesktop().getSession();
Properties ctx = Env.getCtx();
langSession = Env.getContext(ctx, Env.LANGUAGE);
@ -171,9 +180,28 @@ public class AdempiereWebUI extends Window implements EventListener<Event>, IWeb
Executions.getCurrent().getDesktop().addListener(new TokenCommand());
Executions.getCurrent().getDesktop().addListener(new ZoomCommand());
eventThreadEnabled = Executions.getCurrent().getDesktop().getWebApp().getConfiguration().isEventThreadEnabled();
eventThreadEnabled = Executions.getCurrent().getDesktop().getWebApp().getConfiguration().isEventThreadEnabled();
}
private void cleanupForPing() {
final Desktop desktop = Executions.getCurrent().getDesktop();
final WebApp wapp = desktop.getWebApp();
final DesktopCache desktopCache = ((WebAppCtrl) wapp).getDesktopCache(desktop.getSession());
final Session session = desktop.getSession();
//clear context, invalidate session
Env.getCtx().clear();
destroySession(session);
desktop.setAttribute(DESKTOP_SESSION_INVALIDATED_ATTR, Boolean.TRUE);
Adempiere.getThreadPoolExecutor().schedule(() -> {
try {
desktopCache.removeDesktop(desktop);
} catch (Throwable t) {
t.printStackTrace();
}
}, 1, TimeUnit.SECONDS);
}
public void onOk()
{
}
@ -386,14 +414,6 @@ public class AdempiereWebUI extends Window implements EventListener<Event>, IWeb
final WebApp wapp = desktop.getWebApp();
final DesktopCache desktopCache = ((WebAppCtrl) wapp).getDesktopCache(desktop.getSession());
Adempiere.getThreadPoolExecutor().schedule(() -> {
try {
desktopCache.removeDesktop(desktop);
} catch (Throwable t) {
t.printStackTrace();
}
}, 5, TimeUnit.SECONDS);
final Session session = logout0();
//clear context, invalidate session
@ -402,7 +422,14 @@ public class AdempiereWebUI extends Window implements EventListener<Event>, IWeb
desktop.setAttribute(DESKTOP_SESSION_INVALIDATED_ATTR, Boolean.TRUE);
//redirect to login page
Executions.sendRedirect("index.zul");
Executions.sendRedirect("index.zul");
try {
desktopCache.removeDesktop(desktop);
DesktopWatchDog.removeDesktop(desktop);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void destroySession(final Session session) {
@ -414,10 +441,14 @@ public class AdempiereWebUI extends Window implements EventListener<Event>, IWeb
session.invalidate();
}
/**
* Perform logout after user close a browser tab without first logging out
*/
public void logoutAfterTabDestroyed(){
Desktop desktop = Executions.getCurrent().getDesktop();
if (desktop.isServerPushEnabled())
desktop.enableServerPush(false);
DesktopWatchDog.removeDesktop(desktop);
Session session = logout0();

View File

@ -63,23 +63,6 @@ public class WLogin extends AbstractUIPart
loginWindow = (LoginWindow) loginPage.getFellow("loginWindow");
loginWindow.init(app);
/* IDEMPIERE-1022 - deprecated message
if (!AEnv.isBrowserSupported())
{
//TODO: localization
String msg = "You might experience slow performance and user interface anomalies using your current browser to access the application. We recommend the use of Firefox, Google Chrome or Apple Safari.";
browserWarningWindow = new Window();
Div div = new Div();
div.setStyle("font-size: 9pt");
div.appendChild(new Text(msg));
browserWarningWindow.appendChild(div);
browserWarningWindow.setPosition("top,right");
ZKUpdateUtil.setWidth(browserWarningWindow, "550px");
browserWarningWindow.setPage(page);
browserWarningWindow.doOverlapped();
}
*/
boolean mobile = false;
if (Executions.getCurrent().getBrowser("mobile") !=null) {
mobile = true;

View File

@ -84,7 +84,11 @@ public class SessionManager
if (app != null)
app.logout();
}
/**
* Perform logout after user close a browser tab without first logging out.
* Usually this is invoke from {@link SessionContextListener} and developer shouldn't call this directly.
*/
public static void logoutSessionAfterBrowserDestroyed()
{
IWebClient app = getSessionApplication();
@ -92,6 +96,10 @@ public class SessionManager
app.logoutAfterTabDestroyed();
}
/**
*
* @param user
*/
public static void changeRole(MUser user){
IWebClient app = getSessionApplication();
if (app != null)

View File

@ -0,0 +1,149 @@
/***********************************************************************
* This file is part of iDempiere ERP Open Source *
* http://www.idempiere.org *
* *
* Copyright (C) Contributors *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
* MA 02110-1301, USA. *
* *
* Contributors: *
* - hengsin *
**********************************************************************/
package org.adempiere.webui.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.TimeUnit;
import org.compiere.Adempiere;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Session;
import org.zkoss.zk.ui.WebApp;
import org.zkoss.zk.ui.sys.DesktopCache;
import org.zkoss.zk.ui.sys.DesktopCtrl;
import org.zkoss.zk.ui.sys.ServerPush;
import org.zkoss.zk.ui.sys.SessionCtrl;
import org.zkoss.zk.ui.sys.WebAppCtrl;
import fi.jawsy.jawwa.zk.atmosphere.AtmosphereServerPush;
/**
* watch for disconnected desktop and destroy it
* @author hengsin
*
*/
public class DesktopWatchDog {
private final static DesktopWatchDog INSTANCE = new DesktopWatchDog();
private final ConcurrentLinkedDeque<DesktopEntry> desktops = new ConcurrentLinkedDeque<DesktopWatchDog.DesktopEntry>();
private DesktopWatchDog() {
Adempiere.getThreadPoolExecutor().scheduleWithFixedDelay(() -> {
doMonitoring();
}, 60, 40, TimeUnit.SECONDS);
}
private void doMonitoring() {
List<Session> toDestroy = new ArrayList<Session>();
List<Session> actives = new ArrayList<Session>();
Iterator<DesktopEntry> iterator = desktops.iterator();
while (iterator.hasNext()) {
DesktopEntry entry = iterator.next();
if (!entry.desktop.isAlive()) {
iterator.remove();
continue;
}
if (entry.desktop.isServerPushEnabled() == false) {
entry.noAtmosphereResourceCount++;
}
ServerPush spush = ((DesktopCtrl)entry.desktop).getServerPush();
if (spush == null) {
entry.noAtmosphereResourceCount++;
} else if (spush instanceof AtmosphereServerPush) {
AtmosphereServerPush asp = (AtmosphereServerPush) spush;
if (!asp.hasAtmosphereResource())
entry.noAtmosphereResourceCount++;
else
entry.noAtmosphereResourceCount=0;
}
if (entry.noAtmosphereResourceCount >= 3) {
iterator.remove();
try {
final WebApp wapp = entry.desktop.getWebApp();
final Session session = entry.desktop.getSession();
final DesktopCache desktopCache = ((WebAppCtrl) wapp).getDesktopCache(session);
desktopCache.removeDesktop(entry.desktop);
if (!actives.contains(session) && !toDestroy.contains(session))
toDestroy.add(session);
} catch (Throwable t) {
t.printStackTrace();
}
} else {
final Session session = entry.desktop.getSession();
if (!actives.contains(session))
actives.add(session);
int index = toDestroy.indexOf(session);
if (index >= 0)
toDestroy.remove(index);
}
}
if (!toDestroy.isEmpty()) {
for(Session session : toDestroy) {
try {
((SessionCtrl)session).onDestroyed();
} catch (Throwable t) {
t.printStackTrace();
}
session.invalidate();
}
}
}
private final static class DesktopEntry {
Desktop desktop;
int noAtmosphereResourceCount = 0;
private DesktopEntry(Desktop desktop) {
this.desktop = desktop;
}
}
/**
* add desktop to watch list
* @param desktop
*/
public static void addDesktop(Desktop desktop) {
INSTANCE.desktops.add(new DesktopEntry(desktop));
}
/**
* remove desktop from watch list
* @param desktop
*/
public static void removeDesktop(Desktop desktop) {
Iterator<DesktopEntry> iterator = INSTANCE.desktops.iterator();
while (iterator.hasNext()) {
DesktopEntry entry = iterator.next();
if (entry.desktop == desktop) {
iterator.remove();
break;
}
}
}
}