In an ASP.NET Web Forms application I have a parent form, which contains another content page within an IFrame. When user clicks on a link within the content page, a long running process (> 30 min) is started. Upon completion a popup is displayed to the user indicating number of records processed.
I need to prevent session timeout programatically, without changing the default 20 min in Web.config.
I have been trying to implement the Heartbeat example posted here (and all over the web, so I know it should work)
Keeping ASP.NET Session Open / Alive
, but it appears that it's used mostly for idle sessions.
In my case, once the content page request goes to server side and long running process is initiated, the HTTP Handler is not called. When the process completes, all the calls are made immediately one after another like they have been "queued".
Here's my HTTP Handler:
<%# WebHandler Language="VB" Class="KeepSessionAliveHandler" %>
Imports System
Imports System.Web
Public Class KeepSessionAliveHandler
Implements IHttpHandler, SessionState.IRequiresSessionState
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
context.Session("heartbeat") = DateTime.Now
context.Response.AddHeader("Content-Length", "0")
End Sub
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class
Javascript function in Head element for parent page. Create interval calling the handler every 8 seconds (to be increased to 10 min in production).
function KeepSessionAlive()
{
if (intervalKeepAliveID)
clearTimeout(intervalKeepAliveID);
intervalKeepAliveID = setInterval(function()
{
$.post("KeepSessionAliveHandler.ashx", null, function()
{
// Empty function
});
}, 8000);
}
intervalKeepAliveID is declared in a main Javascript file included in all pages of the application.
This is the code for my onclick event in the content page Head
$(document).ready(function()
{
// Ensuring my code is executed before ASP.NET generated script
$("#oGroup_lnkSubmit_lnkButton").attr("onclick", null).removeAttr("onclick").click(function()
{
// Prevent the browser from running away
// e.preventDefault();
window.parent.KeepSessionAlive();
// Wave goodbye
//window.location.href = $(this).attr('href');
WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions($(this).attr("name"), "", true, "", "", false, false));
});
});
Somewhere I read that Javascript runs in a single thread, but given that fact that my repeating interval is outside the content page, I do not believe this should apply here...
It's not an issue with JS being single-threaded - The A of AJAX stands for Asynchronous - eg it doesn't block (even if you tell it to block, it really just preserves state until a response is received)
From this MSDN article...
Access to ASP.NET session state is exclusive per session, which means that if two different users make concurrent requests, access to each separate session is granted concurrently. However, if two concurrent requests are made for the same session (by using the same SessionID value), the first request gets exclusive access to the session information. The second request executes only after the first request is finished. (The second session can also get access if the exclusive lock on the information is freed because the first request exceeds the lock time-out.) If the EnableSessionState value in the # Page directive is set to ReadOnly, a request for the read-only session information does not result in an exclusive lock on the session data. However, read-only requests for session data might still have to wait for a lock set by a read-write request for session data to clear.
See this page for a more detailed explanation of the problem and a workaround that gives you greater control of how the blocking is implemented and a potential workaround.
I think you've got a couple of moving parts here that are blocking each other from behaving correctly.
Because your process is running in the server thread, this blocks other requests from being processed.
Because the keepalive depends on getting a response from the server, it doesn't complete.
I'd suggest that you look into a solution like ASP.NET SignalR, along with spawning the long-running process as a separate thread so that your server can continue to service incoming requests.
Related
I have an ember application where users are stored in a MySQL database. When a user exits (ie. closes their browser window), they need to be deleted from the database. I have the following code in one of my route files:
setupController: function () {
$(window).on('beforeunload', () => {
this.get('currentUser').delete();
});
},
In my testing this only seems to delete the user from the database maybe 70-80% of the time, and somehow it seems to be random whether it works or not. I'm guessing this is because sometimes the function isn't run in time before the browser has closed the window. How can I ensure the code to delete a user is executed every time a user exits?
It wouldn't work this way. Reason: browser interrupts any requests (even ajax) to backend when user closes window/tab.
I suggest to implement cleanup on backend side. What you need is store last time when user performed some action and delete those who did not make any requests in some period of time (for example, if there was no requests in 1 hour, you can be pretty sure that user closed browser window). You can also perform "ping" requests from your ember app to your backend once in a while, so idle users will not be deleted.
i have an object that use some resources like
class UseResources {
protected $proc_open;
public function __construct()
{
$this->proc_open = proc_open( ... );
}
public function cleanup()
{
proc_close($this->proc_open);
}
};
i tried to send an ajax request on .unload event, but its not what am looking for.
i want to be able to call the cleanup method once the user close the window or the browser, or once the connection is lost?
The unload event fires when the window closes, but also when user refreshes the page or navigates to a new page (even if new page is within your site). I recommend against using unload in this case. (unload is better used for things like prompting user to save or lose changes before leaving a form, for example.)
Also, if you implement a javascript solution you either want to (1) require javascript to use the site (and show <noscript></noscript> element to folks with JS disabled), or (2) code such that you are not relying solely on your window close / cleanup detection routine to release resources.
The strategy I typically implement to do stuff like this is to use a ping process. Basically: the client sends regular pings to the server which resets the connection's/session's idle counter to 0 when it receives a ping. The pings occur every minute or so depending on need. A long-running looping thread (a Session manager, if you will) then checks for any clients that have idled beyond the idle threshhold (say 3 minutes, but up to you), and if exceeded releases the resources tied to that connection/session. Then every page (where appropriate) in your site adds this ping code so that the client starts pinging for as long as the page is open in the browser.
Benefits
keeps track of how long user is viewing your site (useful for metrics, especially when coupled with a metric for tracking number of requests)
closes all connection-related resources when browser is closed, and also when user navigates away from your site
if user navigates to different page [that has ping code] on your site their connection resources will not be cleared because the idle counter will reset on the next page
Drawbacks
requires javascript (setTimeout or setInterval and XMLHttpRequest, for example)
unless you also have something like an "authentication timeout" on the server, the client could potentially keep your server (and resources) connected for a long time if user walks away from an open web browser for a while (you could get around this by using alternate ping triggers such as mouse movement, setting focus to form fields, clicking, scrolling, etc.)
Rough example code for client page:
function pingServer() {
var req;
if (window.XMLHttpRequest) {
req = new XMLHttpRequest();
} else {
req = new ActiveXObject("Microsoft.XMLHTTP");
}
req.onreadystatechange = function() {
if (req.readyState == 4 && req.status == 200) {
alert("Idle timer reset on server");
}
}
var url = 'ping.php'; // call php code that will reset idle timer for this client
req.open("GET", url, true);
req.send();
}
var pingFrequencyMs = 5 * 1000; // ping every 5 secs
setInterval(pingServer, pingFrequencyMs);
<noscript>Sorry, JavaScript is required for this website.</noscript>
And here is a really rough example of process you could use on server side, but until you provide more details I can only speculate about what kind of web app you are working with:
Server Ping routine (pseudocode, could be PHP, ASP, JSP, etc.)
get client connection unique ID (or Session ID, or whatever)
get current value for client idle timer, else default to 0 (could get this from memory cache, database, file on disk; your choice)
get system value for max idle (timeout)
compare client idle to max idle
if client idle exceeds max idle then end the session (whatever that means; close connection/session-specific resources, etc. -- in your case it means call the cleanup routine, but make sure it has the correct object context), else reset idle to 0
On my website, I have built a chatroom with support for multiple rooms. When a user joins the room, a session is placed into the database so that if they try to join the room again in another browser window, they are locked out.
It works like this
1. Join the chatroom page
2. Connect to chatroom #main
If the user has a session in the database for #main
--- Block user from joining
else
--- Load chatroom
When the chatroom is closed client side or the user terminates there connection with the /quit command, all of their sessions are deleted, and this works fine.
However
There is a possibility that users will just close the browser window rather than terminating their connection. The problem with this is that their session will stay in the database, meaning when they try to connect to the room, they are blocked.
I'm using this code onbeforeunload to try and prevent that
function disconnect() {
$.ajax({
url: "/remove-chat-sessions.php?global",
async: false
});
};
This is also the function called when the user types the /quit command
The problem
The problem with this is that when I reload the page, 5 times out of 10 the sessions have not been taken out of the database, as if the ajax request failed or the page reloaded before it could finish. This means that when I go back into the chatroom, the database still thinks that I am connected, and blocks me from entering the chatroom
Is there a better way to make sure that this AJAX call will load and if not, is there a better alternative than storing user sessions in an online database?
Edit:
The reason users are blocked from joining rooms more than once is because messages you post do not appear to you when the chatroom updates for new messages. They are appended to the chatroom box when you post them. This means that if users could be in the same chatroom over multiple windows, they would not be able to see the comments that they posted across all of the windows.
In this situation you could add some sort of polling. Basically, you request with javascript a page every X time. That page adds the user session to the database. Then there's a script executing every Y time, where Y > X, that cleans old sessions.
The script that is called every X time
...
// DB call (do as you like)
$All = fetch_all_recent();
foreach ($All as $Session)
{
if ($Session['time'] < time() - $y)
{
delete_session($Session['id']);
}
}
The script that javascript is calling every X time
...
delete_old_session($User->id);
add_user_session($User->id, $Chat->id, time());
The main disadvantage of this method is the increment in requests, something Apache is not so used to (for large request number). There are two non-exclusive alternatives for this, which involve access to the server, are:
Use nginx server. I have no experience in this but I've read it supports many more connections than Apache.
Use some modern form of persistent connection, like socket.io. However, it uses node.js, which can be good or bad, depending on your business.
I have an ASP.NET application that is using forms authentication with a timeout set to five minutes. On my page I have a button, that when clicked, makes an AJAX call to an operation that lives on the service named in my .svc file. How do I know, from the client javascipt that the application has timed out? Or, how can I detect this in the global.asax; maybe in the application_beginrequest?
If you're talking about the session timeout. When this occurs, the IHttpSessionState.IsNewSession property should be set to true.
If you're referring to the auth timeout, then you have to check the AuthenticationTicket for expiration.
A variation on your approach: have a separate client script that first checks for authentication expiration by requesting a special page/handler which returns JSON structure to indicate the authentication status of the current user. Only after knowing that the user is still active do you then run your main ajax action. It's one more request but keeps you from entangling the timeout logic with the main ajax logic. You can also use separately in a "warning, your session will time out in x minutes" popup.
See this question and my answer there for more details about how to set it up, but the key point is that if you don't want the check for expiration to extend the sliding expiration you have to configure the expiration page/handler as a separate virtual directory with forms authentication set with slidingExpiration=false.
Here's the specs:
ASP.NET 3.5 using ASP.NET AJAX
AJAX Control Toolkit
jQuery 1.3.2
web services
IIS6 on Windows Server 2003 SP1
SP1 SQLServer 2005 SP3 Site is SSL
Infragistics Web Components 2009 Vol. 2 (using non-Aikido controls), UltraWebGrid and Tree control are main ones used.
Here's the problem:
I'm getting the White Screen of Death (WSOD) in IE 7/8. Basically, I have a page that has a left pane that has an AJAXControl Toolkit Accordion control where each accordion panes content is an Infragistics Tree Control. The right pane is a <div> that has an <iframe> whose content is reloaded based on what's clicked in the left menu pane.
In the <iframe>, a page with one or more UltraWebGrid controls loads up when you click on a menu item in the left pane. The grids all havea templated button column. When you click on the edit button of a grid row a popup window to edit the record is opened. This works fine for about ten times and then on the tenth time (sometimes earlier), the popup window opens with the correct URL in the address bar, but the page never loads.
We have an application that uses one popup window for updating records. Most of the time when you click on the [Edit] button to edit a record, the popup window opens and loads the update page. However, after editing records for a while, all of a sudden the popup window will open, but it stays blank and just hangs. The URL is in the address bar.
Loading up Fiddler I noticed that the request for the update page is never sent which leads me to believe it's some kind of lockup on the client-side. If I copy the same URL that's in the popup window into a new browser window, the page generally loads fine.
Observations:
- Since the request is never sent to the server, it's definitely something client-side or browser related.
- Only appears to happen when there is some semblance of traffic on the site which is weird because this appears to be contained within client-side code
- There is a web service being called in the background every few seconds checking if the user is logged on, but this doesn't cause the freeze.
I'm really at a loss here. I've googled WSOD but not much seems to appear related to my specific WSOD. Any ideas?
What the problem really is
So turns out the memory leaks (although I've sealed up some on the client-side) are not the issue. The issue is web service calls being made on the client-side. There is one that checks if a user is logged on every 4 seconds (to synchronize with another window) and then there are web service calls to get user preferences for a popup window and grid state. From what I've read, the web services have to be asynchronous. I assumed by calling them from JavaScript with success/fail callbacks that they were asynchronous but they really aren't. They're asynchronous from the client-side/browser point of view, but from the server-side, the call to the web service is made and returns when it is completed holding up any other operations since there is a limited number of connections.
So what is the easiest way to just make the web service methods asynchronous? Does the web service need to be converted to a WCF web service or can I use my existing ASP.NET web service call?
And for historical purposes, here's what I thought the problem was originally:
I wasn't able to reproduce this locally or on our testing servers. However, I got Fiddler to simulate modem speeds and all of sudden I can replicate the WSOD on my local PC. So it appears to be a slow or temporarily slow connection when opening a popup window that causes it to choke, at least in my test environment.
I did another test running IE without add-ons, iexplore.exe -extoff, but end up with the same result. I also fixed an issue where the iframe on the page was being recreated everytime the URL for the iframe changed. Part of my logic was omitted. Now the iframe is only created once. After that only the src attribute is updated when I want to load new content... my goof. I noticed some lingering window references in JavaScript closures, so now those are explicitly set to null in the closures when I'm done with them.
I've also done some memory leak investigation:
- As far as I can tell I do not have any circular references in the DOM and JavaScript or the other leak patterns mentioned here, http://www.ibm.com/developerworks/web/library/wa-memleak/?S_TACT=105AGX52&S_CMP=cn-a-wa
I've added the Crockenator's purge code for IE memory leaks (see http://www.crockford.com/javascript/memory/leak.html):
$(document).ready(function() {
function purge(d) {
var a = d.attributes, i, l, n;
if (a) {
l = a.length;
for (i = 0; i < l; i += 1) {
if (a[i]) {
n = a[i].name;
if (typeof d[n] === 'function') {
d[n] = null;
purgeCount++;
}
}
}
}
a = d.childNodes;
if (a) {
l = a.length;
for (i = 0; i < l; i += 1) {
purge(d.childNodes[i]);
}
}
}
$(window).unload(function() {
purge(document.body);
//alert("purge count: " + purgeCount);
});
});
None of my improvements have fixed the problem. in my local test scenario. Any ideas? Anyone? Anyone? Bueller?
Last Update
Thanks David for pointing out that it was session state causing the problems in the web services. "ASP.NET queues all requests to the same 'session'. So if the first request blocks for too long, it will hold up any other queued requests."
So what we ended up doing was try to minimize web services using session state but we also added the recommended settings by Microsoft for the number of connections, see http://msdn.microsoft.com/en-us/library/ff647786.aspx#scalenetchapt10_topic9
I think you may be having an issue with Session request synchronization. Have you marked your web service handlers as requiring session state?
ASP.NET queues all requests to the same "session". So if the first request blocks for too long, it will hold up any other queued requests. You can turn off session state for the page to avoid this and be truly asynchronous, however you will be unable to access session on the server, etc.
If you are using .ashx, you have to use an interface to get access to session state,the default is off, so check if you added one of these interfaces and remove if possible:
public class FooHandler : IHttpHandler, IReadOnlySessionState // readonly access
public class FooHandler : IHttpHandler, IRequiresSessionState // read-write access
If you are using an aspx page, it is on by default and you have to turn it off with a Page directive attribute:
<%# Page language="c#" Codebehind="WebForm1.aspx.cs"
AutoEventWireup="false" Inherits="WebApplication1.WebForm1"
EnableSessionState="false" %>