Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions src/prerna/semoss/web/services/local/SessionResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import prerna.util.Constants;
import prerna.util.insight.InsightUtility;
import prerna.web.conf.DBLoader;
import prerna.web.conf.UserSessionLoader;
import prerna.web.services.util.WebUtility;

@Path("/session")
Expand Down Expand Up @@ -183,22 +184,14 @@ public Response cleanSession(@Context HttpServletRequest request) {
synchronized (lock) {
cancelTime = (Date) session.getAttribute(CANCEL_INVALIDATION);
}
if (cancelTime == null) {
if (cancelTime != null && execTime.before(cancelTime)) {
classLogger.info("Cancelled invalidating session");
output = "cancelled";
} else {
// kill the entire session
classLogger.info("Invalidating session");
session.invalidate();
endSession(session);
output = "invalidated";
} else {
boolean isCancelled = execTime.before(cancelTime);
if (isCancelled) {
classLogger.info("Cancelled invalidating session");
output = "cancelled";
} else {
// kill the entire session
classLogger.info("Invalidating session");
session.invalidate();
output = "invalidated";
}
}
} else {
// in case during the time this was called
Expand Down Expand Up @@ -291,10 +284,19 @@ public void invalidateSession(@Context HttpServletRequest request, @Context Http

if (session != null) {
classLogger.info("Invalidating session");
session.invalidate();
endSession(session);
}
}

/**
* Invalidate a live session on purpose, so session cleanup treats it as over (releasing
* the user's sandbox lease) rather than as a server stop.
*/
private static void endSession(HttpSession session) {
session.setAttribute(UserSessionLoader.IS_SESSION_ENDED, true);
session.invalidate();
}

@GET
@Path("/insights")
@Produces("application/json;charset=utf-8")
Expand Down
13 changes: 13 additions & 0 deletions src/prerna/web/conf/NoUserInSessionFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@
import prerna.semoss.web.services.local.ResourceUtility;
import prerna.util.Constants;
import prerna.util.SocialPropertiesUtil;
import prerna.util.openshell.OpenShellUserSandboxes;
import prerna.web.services.util.WebUtility;

public class NoUserInSessionFilter implements Filter {

private static final String NO_USER_HTML = "/noUserFail/";
private static final String OPENSHELL_LEASE_ATTR = "OPENSHELL_SANDBOX_LEASE";

@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
Expand All @@ -76,6 +78,17 @@ public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2
// don't know what i am redirecting
// send back an error and have the client remake the request
setInvalidEntryRedirect(context, arg0, arg1);
} else if (session != null && OpenShellUserSandboxes.appliesTo(user)) {
// any activity, not only python, keeps the user's sandbox alive for the
// session timeout; refreshed at most once a minute per session
String marker = OpenShellUserSandboxes.touch(user, session.getId(),
(String) session.getAttribute(OPENSHELL_LEASE_ATTR), session.getMaxInactiveInterval());
if (marker != null) {
session.setAttribute(OPENSHELL_LEASE_ATTR, marker);
// kept for logout: dropping a provider can leave the user with no login
// to derive the owner from by the time the session is destroyed
session.setAttribute(OpenShellUserSandboxes.SESSION_OWNER_ATTR, OpenShellUserSandboxes.ownerOf(user));
}
}
}

Expand Down
71 changes: 61 additions & 10 deletions src/prerna/web/conf/UserSessionLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,17 @@
import prerna.util.SymlinkHelper;
import prerna.util.Utility;
import prerna.util.insight.InsightUtility;
import prerna.util.openshell.OpenShellUserSandboxes;

@WebListener
public class UserSessionLoader implements HttpSessionListener {

/** How far short of its timeout an idle session still counts as timed out. */
private static final long SESSION_EXPIRY_SLACK_MS = 5000L;

public static final String IS_USER_LOGOUT = "IS_USER_LOGOUT";
// set when the app invalidates a live session on purpose (not a logout, not a server stop)
public static final String IS_SESSION_ENDED = "IS_SESSION_ENDED";
public static final String USER_LOGOUT_IP_ADDR = "USER_LOGOUT_IP_ADDR";

private static final Logger classLogger = LogManager.getLogger(UserSessionLoader.class);
Expand Down Expand Up @@ -98,6 +104,11 @@ public void sessionDestroyed(HttpSessionEvent sessionEvent) {
String sessionId = session.getId();

User thisUser = (User) session.getAttribute(Constants.SESSION_USER);
// A user sandbox outlives a server stop, which also destroys every session: only a
// logout or a real timeout ends the user's claim on it (see OpenShellUserSandboxes).
String openshellOwner = OpenShellUserSandboxes.ownerForSession(thisUser,
session.getAttribute(OpenShellUserSandboxes.SESSION_OWNER_ATTR));
boolean sessionOver = openshellOwner == null || sessionIsOver(session);
if (thisUser == null) {
// no need to log a new session that is auto dropped
// this just keeps writing to the log
Expand Down Expand Up @@ -134,6 +145,10 @@ public void sessionDestroyed(HttpSessionEvent sessionEvent) {
}
classLogger.info("Trying to drop insight {}", insightId);
try {
if (!sessionOver) {
// the user's python outlives this server; keep the insight's variables
insight.setDeletePythonGlobalsOnDropInsight(false);
}
InsightUtility.dropInsight(insight);
classLogger.info("Dropped insight {}", insightId);
} catch (Exception e) {
Expand Down Expand Up @@ -200,11 +215,12 @@ public Void call() throws Exception {
}

// stop netty/python wrappers and chroot mounts for session user
cleanupUserProcessAndChroot(thisUser, "session user");
cleanupUserProcessAndChroot(thisUser, openshellOwner, sessionId, sessionOver, "session user");

// remove agent users
if (subAgent != thisUser) {
cleanupUserProcessAndChroot(subAgent, "agent user");
cleanupUserProcessAndChroot(subAgent, OpenShellUserSandboxes.ownerForSession(subAgent, null), sessionId,
sessionOver, "agent user");
}

// if cloud sync enabled, push and clear the rooms
Expand Down Expand Up @@ -236,19 +252,31 @@ public Void call() throws Exception {
/**
* Cleans up user-owned client process and optional chroot resources.
*
* @param user the user to clean up
* @param userType descriptive label used in logs (for example, session user)
* @param user the user to clean up
* @param openshellOwner owner of the user's OpenShell sandbox, or null outside that mode
* @param sessionId the session that ended
* @param over true for a logout or timeout, false when the server is stopping
* @param userType descriptive label used in logs (for example, session user)
*/
private void cleanupUserProcessAndChroot(User user, String userType) {
private void cleanupUserProcessAndChroot(User user, String openshellOwner, String sessionId, boolean over,
String userType) {
if (user == null) {
return;
}

boolean openshellUserSandbox = openshellOwner != null;
try {
// stop the netty thread if used for either r or python
ClientProcessWrapper cpw = user.getPythonClientProcessWrapper();
if (cpw != null) {
cpw.shutdown(true);
if (openshellUserSandbox) {
// The python worker and chroot belong to the user, not this session: they
// go when the user's last session here ends, and the sandbox when their
// last session anywhere does.
OpenShellUserSandboxes.sessionEnded(openshellOwner, sessionId, over);
} else {
// stop the netty thread if used for either r or python
ClientProcessWrapper cpw = user.getPythonClientProcessWrapper();
if (cpw != null) {
cpw.shutdown(true);
}
}
} catch (Exception e) {
classLogger.error("Failed to shut down client process wrapper during {} cleanup", userType, e);
Expand All @@ -266,7 +294,7 @@ private void cleanupUserProcessAndChroot(User user, String userType) {

// remove mounts if chroot is enabled
try {
if (Boolean.parseBoolean(Utility.getDIHelperProperty(Constants.CHROOT_ENABLE))) {
if (!openshellUserSandbox && Boolean.parseBoolean(Utility.getDIHelperProperty(Constants.CHROOT_ENABLE))) {
SymlinkHelper chrootHelper = user.getUserSymlinkHelper();
if (chrootHelper != null) {
chrootHelper.removeChrootFolder();
Expand Down Expand Up @@ -387,4 +415,27 @@ private void cleanupPlaywrightSessions(User thisUser) {
}
}

/**
* Whether the user has really left: an explicit logout, a session the app ended on
* purpose, or idle past the session timeout. Anything else that destroys a session --
* a server stop above all -- is not the user leaving.
*
* @param session the session being destroyed
* @return true for a logout, an app-ended session or a timeout
*/
private static boolean sessionIsOver(HttpSession session) {
try {
if (Boolean.parseBoolean(String.valueOf(session.getAttribute(UserSessionLoader.IS_USER_LOGOUT)))
|| Boolean.parseBoolean(String.valueOf(session.getAttribute(UserSessionLoader.IS_SESSION_ENDED)))) {
return true;
}
int maxInactive = session.getMaxInactiveInterval();
long idleMs = System.currentTimeMillis() - session.getLastAccessedTime();
// the container expires a session a little after its interval, never before
return maxInactive > 0 && idleMs >= maxInactive * 1000L - SESSION_EXPIRY_SLACK_MS;
} catch (IllegalStateException e) {
// unreadable: keep the lease; the sweeper deletes it once it lapses
return false;
}
}
}
Loading