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
Related
We have an ASP.Net WebForms application that uses SignalR (v2.4.1) to do some bi-directional communications between server and client. It's worked fine for years: connections are stable, hundreds of users use it, etc.
However, we've started to get sporadic reports of connection problems from across our client base, all reporting the same thing: if the browser (Chrome) session goes idle for more than 5 minutes, the connection drops in the background. All timers in the page stop being run regularly, which (amongst other things) stops "keepalives" stop being sent, and eventually the connection fails with the client-side error:
The client has been inactive since <date> and it has exceeded the inactivity timeout of 50000 ms. Stopping the connection.
Standard procedure after this would be to automatically restart the connection, but this doesn't do anything. If/when the user reactivates the page (e.g. by switching to the tab), everything starts to spring back into life, albeit with a closed SignalR connection.
After much investigation, it seems that we're being impacted by this change introduced in Chrome v88, where timers (setTimeouts) are severely restricted if
The page has been hidden for more than 5 minutes
The timer has been "chained" 5 or more times - I'm assuming this is similar to recursion, where the timer calls itself.
Page has been "silent" for 30 seconds
The 5 minutes/30 seconds condition fits with the reports we're getting. However, we're running pretty basic Javascript on our page: there are only two uses of setTimeout in our own code, neither of which could ever "chain" (recurse) onto themselves. We also cannot replicate the issue: it's happened to us in testing, but we can't make it happen reliably. Disabling this feature via chrome://flags/#intensive-wake-up-throttling seems to mitigate the issue - but of course, we can't make this a requirement to use our site.
The only other Javascript running on the site is jquery.signalR-2.4.1.js, and from the SignalR source, there are lots of setTimeouts in there. Could SignalR be impacted by this change in Chrome; perhaps when it tries to silently reconnect after a temporary network issue or some other unpredictable event?
If not, is there any way, in any browser or IDE, to track which timers have been launched (and, more importantly, "chained"), so we can see what could be triggering this restriction?
We're as well facing issues with our signalR (WebSockets as transport). We're not able to reproduce it in our lab. The HAR files of our customer and extended logging provided us only the information that the client "consuming only after following interesting groups" is not sending pings within the default 30 seconds needed to keep the connection. Therefore the server closes the connection. We added logs in the signalR client library and only saw the ping timer not being hit on time. No error, no nothing. (Client is JavaScript and the issue occurred on customer site in chrome 87 (throttling was implemented there already for half of the chrome users - https://support.google.com/chrome/a/answer/7679408#87))
And the world is slowly getting aware of "an issue": https://github.com/SignalR/SignalR/issues/4536
Our quick help for our customers will be to create an ET with a manual broadcast ping-pong mechanism from the server site and each client will have to answer. Avoiding being dependent on the JavaScript ping in the signalR library until a "better" solution or fix is provided.
As a workaround, javascript library that does the ping can be modified, to slightly change the way that it uses the timers. One of the conditions for intensive throttling is that the setTimeout()/setInterval() chain count is 5+. This can be avoided for recurring calls, by using a web worker. The main thread can post a dummy message to the web worker, which does nothing other than posting a dummy message back to the main thread. The subsequent setTimeout() call can be made on the message event from the web worker.
i.e.,
main_thread_ping_function :- doPing() -> post_CallMeBack_ToWebWorker()
web_worker :- onmessage -> post_CallingYouBack_ToMainThread()
main_thread :- web_worker.onmessage -> setTimeout(main_thread_ping_function, timeoutValue)
Since the setTimeout() is called on a message from web worker, rather than from the setTimout() execution flow, the chain length remains one, and thus no intensive throttling would be done by chrome 88+.
Note that, chained setTimeout() calls in a web worker are not throttled by chrome at the moment, and thus defining the timer functionality inside a web worker, and acting on the messages(to perform ping) from web worker, too solves the problem. However, if chrome developers decide to throttle the timers in web workers too, in the future, it gets broken again.
A utility(similar to java scheduled executor) which allows scheduling of callbacks using web workers, to avoid throttling, by context switching:
class NonThrottledScheduledExecutor {
constructor(callbackFn, initialDelay, delay) {
this.running = false;
this.callback = callbackFn;
this.initialDelay = initialDelay;
this.delay = delay;
};
start() {
if (this.running) {
return;
}
this.running = true;
// Code in worker.
let workerFunction = "onmessage = function(e) { postMessage('fireTimer'); }";
this.worker = new Worker(URL.createObjectURL(new Blob([workerFunction], {
type: 'text/javascript'
})));
// On a message from worker, schedule the next round.
this.worker.onmessage = (e) => setTimeout(this.fireTimerNow.bind(this), this.delay);
// Start the first round.
setTimeout(this.fireTimerNow.bind(this), this.initialDelay);
};
fireTimerNow() {
if (this.running) {
this.callback();
// dummy message to be posted to web worker.
this.worker.postMessage('callBackNow');
}
};
stop() {
if (this.running) {
this.running = false;
this.worker.terminate();
this.worker = undefined;
}
};
};
<button onclick="startExecutor()">Start Executor</button>
<button onclick="stopExecutor()">Stop Executor</button>
<div id="op"></div>
<script>
var executor;
function startExecutor() {
if (typeof(executor) == 'undefined') {
// Schedules execution of 'doThis' function every 2seconds, after an intial delay of 1 sec
executor = new NonThrottledScheduledExecutor(doThis, 1000, 2000);
executor.start();
console.log("Started scheduled executor");
}
}
function stopExecutor() {
if (typeof(executor) != 'undefined') {
executor.stop();
executor = undefined;
document.getElementById("op").innerHTML = "Executor stopped at " + l;
}
}
var l = 0;
function doThis() {
l = l + 1;
document.getElementById("op").innerHTML = "Executor running... I will run even when the my window is hidden.. counter: " + l;
}
</script>
Microsoft have released SignalR 2.4.2, which should address the issue natively and avoid the need for any manual workarounds.
Nuget package available here, and the list of fixed issues is here
I know that it does not solve the problem altogether with chrome, however, the new edge that uses chromium engine has added a few new settings to govern the timeouts (since it was affected too by the change). There is a new whitelisting option that gives at least the power to the users to decide which pages are excluded from this behavior. I honestly do believe that these setting will be added by google sooner or later. Until then we recommend our customers to switch to edge if they are affected.
You can find it in settings\system:
So we have a logic whereby we will check the status of a URL every 15 seconds like so:
const http = new XMLHttpRequest();
const alive = (url) => {
http.open("HEAD", url, false);
http.send();
return http.status !== 404;
};
This url is scheduled to be working at a certain time of the day for a period of time only and our app will have to detect when it has returned 200. Once it does, we'll display an iframe that has the url content (and also clear the interval). So whenever it's still returning a 404, we will display an "upcoming" image instead.
Is it okay to keep doing this check? Won't we encounter too many requests error on the url at some point? Realise that we may not be able to have control on the server for that url. Let's say about 1000 people will be accessing the page that does the checking. Some of them may visit the page earlier and stay there. The page will start doing this check every 15 seconds up until the url becomes alive (which can take hours after the moment they land on the page).
To determine whether or not something is alive, you'll probably want to return http.status === 200 instead. This means if say there's an internal server error for example, an error 500, then it's assumed it is not alive.
For a polling approach, any interval can be too frequent if you have lots of users.
Your best bet for scalability is to have a socket-based system for communication. As you mentioned, you are using node.js. You can look into using socket.io. When you go "live" then you can just emit an event to all connected clients.
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.
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.
I would like to keep track of how long visitors spend reading a page. If they tab away, or minimize the window, time should not count towards the time on page until they look at the tab again.
I assume some combination of javascript and server side work will be necessary.
A couple of issues I'm struggling with:
What's the best way to store this information in the database?
How do I, with Javascript, capture the time on page with a reasonable degree of accuracy? Do I store events like "page loaded", "user idle", "user returned", "page unloaded", and then separately process all the events in the DB to come up with a time on page?
I've put some work into a small JavaScript library that times how long a user is on a web page. It has the added benefit of more accurately (not perfectly, though) tracking how long a user is actually interacting with the page. It ignores time that a user switches to different tabs, goes idle, minimizes the browser, etc. The Google Analytics method suggested has the shortcoming (as I understand it) that it only checks when a new request is handled by your domain. It compares the previous request time against the new request time, and calls that the 'time spent on your web page'. It doesn't actually know if someone is viewing your page, has minimized the browser, has switched tabs to 3 different web pages since last loading your page, etc.
https://github.com/jasonzissman/TimeMe.js
An example of its usage:
On loading your page:
document.onload = function() {
TimeMe.setIdleDurationInSeconds(30);
TimeMe.setCurrentPageName("my-home-page");
TimeMe.initialize();
}
Retrieving time spent on the page, and sending it to your server when the user leaves your page:
window.onbeforeunload = function (event) {
xmlhttp=new XMLHttpRequest();
xmlhttp.open("POST","ENTER_URL_HERE",false);
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
var timeSpentOnPage = TimeMe.getTimeOnCurrentPageInSeconds();
xmlhttp.send(timeSpentOnPage);
};
First, you need to detect when a user has moved away from a tab or is inactive. When this happens, start a timer, when they come back, stop the timer. Counting time with setTimeout/setInterval can be innacurate because of blocking, so I made myself an accurate javascript timer based on the actual difference in datetime: https://gist.github.com/4600726
So your code would look something like this:
timer = new Timer;
window.addEventListener('focus', function() {
timer.start();
}, false);
window.addEventListener('blur', function() {
timer.stop();
// send timer.msecs() to the server maybe??
// if so, also call timer.reset();
}, false);
window.addEventListener('beforeunload', function() {
timer.stop();
// send timer.msecs() to the server via jquery post, or better yet websocket
}, false);
Then you can get the elapsed time with timer.secs(). I guess it depends on your preference how often you want to send info to the server. You could do it on blur.
Another option could be to decree that no mouseover means inactivity. Start the timer and then do a setTimeout, then on the window's mousemove event cancel the setTimeout and start another setTimeout , after which you stop the timer.
As far as sending data to the server, I'd probably opt for sending it on blur and of course beforeunload. My preferred method would be with socket.io since it is fast and always connected, so you could use it to track lots of user events in real time, but you could just to an ajax call to your server. If you just send them as mini user sessions, { user: userId, page: pageId, elapsedTime: msecs } then you could then aggregate the data on the server end when you are doing analysis.