Worker thread still runs even after browser tab is closed

Hi,

I have a background thread that polls a service every 5 seconds in my vaadin application. I use it to display any real-time changes in my data services. However, when I close the browser tab (firefox 18) I have noticed that the thread is still running on the server even though the vaadin application should no longer exist. In vaadin 7 I can’t find any mechanism to detect that the vaadin application or the browser tab has been closed. I have tried listening to the DetachEvent on the UI application but this never gets fired when I close the browser tab. Is there any mechanism I could use in the worker thread to detect if the UI is not longer running/visible? I have tried testing UI.getCurrent().isVisible() to see if the current page is no longer visible but this always returns true even when the browser tab is closed.

Steve.

Hi,

The UI detach event would be the one to listen for. However, to be able to reliably detect when a UI is not in use anymore, Vaadin 7 uses a heartbeat mechanism which pings the server with a given interval. The server will check for the heartbeats and only when several expected heartbeats in a row are missing will it conclude that the UI has been closed.
This means that the UI detach event will not be sent immediately when you close the tab but some time later.

Hi,

How long should I wait? I have waited for over 5 mins from when the browser tab is closed and I still don’t get the detach event. Below is my sample code. When the browser tab is closed, the worker thread still runs and the detach event is never fired.

import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.Label;
import com.vaadin.ui.ProgressIndicator;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

/**
 * Main UI class
 */
@SuppressWarnings("serial")
public class PingerUI extends UI {

	private ProgressIndicator workingIndicator;
	private Label counter = new Label("0");
	private int cnt = 0;
	private boolean keepAlive = true;
	private boolean finishedUpdating = false;

	@Override
	protected void init(VaadinRequest request) {
		final VerticalLayout layout = new VerticalLayout();
		layout.setMargin(true);
		setContent(layout);

		Label label = new Label("Worker is running!");
		layout.addComponent(label);
		
		workingIndicator = new ProgressIndicator();
		workingIndicator.setIndeterminate(true);
		workingIndicator.setEnabled(true);
		workingIndicator.setValue(0f);
		
        layout.addComponent(workingIndicator);

		
		counter.setImmediate(true);
		layout.addComponent(counter);
       
		this.addDetachListener(new DetachListener() {
			public void detach(DetachEvent event) {
				System.out.println("##### DETACHED #####");
				handleClose();
			}			
		});
		
		UpdateWorker worker = new UpdateWorker();
		worker.start();
	}

	private void handleClose() {
		this.keepAlive = false;
		boolean loop = true;
		while (loop) {
			try {
				while (loop) {
					Thread.sleep(200);
					if (this.finishedUpdating) {
						loop = false;
					}
				}
				System.out.println("PingerUI is finished updating, can now close...");
			}
			catch (Exception ex) {
				ex.printStackTrace();
			}
		}
		close();
	}

	private void updateValues() {
		// update something, for now just the counter
		cnt++;
		counter.setValue(String.valueOf(cnt));
		System.out.println("Still updating...");
	}
	
	public class UpdateWorker extends Thread {
		
        @Override
        public void run() {
            boolean loop = true;
        	try {
                while (loop) {
		            Thread.sleep(5000);
	                updateValues();
	                	
	                if (!keepAlive) {
	                	loop = false;
	                }	                
                }
                System.out.println("No longer pinging!!");
                finishedUpdating = true;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Thanks,
Steve.

I managed to solve my problem. The only way I could get the detach method to fire was to detect the browser onbeforeunload event and stop my worker thread. I added the following code to solve the problem:


JavaScript.getCurrent().addFunction("aboutToClose", new JavaScriptFunction() {
        @Override
        public void call(JSONArray arguments)
                throws JSONException {
        	System.out.println("Got aboutToClose callback!!");
        	handleClose();
        }
 });
		
Page.getCurrent().getJavaScript().execute("window.onbeforeunload = function (e) { var e = e || window.event; aboutToClose(); return; };");

hi Steve,

where do you call this command in your code: Page.getCurrent().getJavaScript().execute(“window.onbeforeunload = function (e) { var e = e || window.event; aboutToClose(); return; };”); ? is it under → public void detach(DetachEvent event) ?
Please advise.

Thanks
David

Hi David,

I just called the code from the UI init method:


public class MyAppUI extends UI {

	@Override
	protected void init(VaadinRequest request) {
              // build your gui.

		this.addDetachListener(new DetachListener() {
			public void detach(DetachEvent event) {
				System.out.println("################## DETACHED");
				handleClose();
			}			
		});
		
               JavaScript.getCurrent().addFunction("aboutToClose", new JavaScriptFunction() {
	        @Override
	        public void call(JSONArray arguments)
	                throws JSONException {
	        	System.out.println("Got aboutToClose callback!!");
	        	handleClose();
	        }
	    });
		
	     Page.getCurrent().getJavaScript().execute("window.onbeforeunload = function (e) { var e = e || window.event; aboutToClose(); return; };");
        }

	public void handleClose() {
             // do whatever you need to do to close the application cleanly.
        }
}

hi Steve,

Thanks for your response. It really helps.
Just found out that your javascript code only works to close tab on Firefox version 19, but not working if you close the browser entirely.
Tried it on Chrome and IE8 and it is not working as well.

So I changed it a little bit as follows:
Page.getCurrent().getJavaScript().execute(“window.onbeforeunload = function (e) { var e = e || window.event; if(e) { aboutToClose(); e.returnValue = ‘’; } return ‘’; };”);
now it works on Chrome (close tab and browser), firefox (close tab only), and failed on IE8.

Seems like the Page.getCurrent().getJavaScript().execute() has bug when executed on IE8 browser.

have you tried it yourself on IE8 btw?

David

Hi David,

Thanks for the fix for chrome and firefox. I only checked it for firefox 19 as that’s all I currently use and I’m only developing my vaadin app for in house use anyway.

Steve.

Sorry for posting in a rather old thread but i’m currently facing a problem where i have a thread periodically running. Now when i close the client the session still stays open indefinetly. This way the thread is also still running.

I also tried to use the heartbeat and set it to 2 minutes (the thread is executed every minute).
Still the session never gets closed.

I want to avoid using the javascript “hack” if possible because i’m running the application on tablets and their behaviour is not really reliable in that regard.

Is there another way to detect the closing of the browser or should it work with hearbeats and i’m just doing something wrong?

Best regards,
M R

I just looked at my Tomcat logs and saw that this behaviour causes memory leaks on my productive system.

I would really appreciate an answer to my problem as this is getting a really big issue for me now.

I don’t really understand why the session would stay open if the client is gone and the session timeout is not infinite.
A background thread can still hold on to the memory of the session if it keeps a reference to it, though, but I assume that is not what you meant.

As for the background thread, how would it be stopped?

There is no reliable way to get a notification for the closing of a client - especially from some browsers or in the case of network problems or browser crash. That is why a heartbeat is the way to go to close sessions earlier than a long timeout.

Note that a single missed heartbeat shouldn’t be reason enough to close the session, as it could be caused by a transient problem.

Are the warnings you are seeing about ThreadLocals?

Tomcat, starting with version 7 (IIRC) started printing out warnings about ThreadLocals that were not cleaned up when an application exits etc. Many very commonly used libraries (logging, …) caused such warnings, Tomcat also implemented some special code for cleaning up after some of them.

As for Vaadin, there used to be a bit more situations where a ThreadLocal could remain set (usually as a result of an exception). The bigger ones have been fixed and there are tickets about one or two cases that can still result in ThreadLocals being left set. Note, however, that the next request will typically overwrite those ThreadLocals and then clean up after itself. Even if that does not happen, these leaks are limited in size.

Furthermore, Tomcat replaces worker threads over time so that any such leaks get cleaned up eventually and don’t hang around indefinitely.

Of course, an application can also leak ThreadLocals, in which case it is not the task of Vaadin to clean them up. Frequent deployments especially in development environments can also leak memory from the permanent generation (loaded classes, statics, …) etc. but there is nothing Vaadin specific about that.

The session is staying open because of the Polling,i guess. At least that was what kept it open in Vaadin 6 using the Refresher add-on. I didn’t set the session-timeout in the web.xml so i think it’s the standard 3 minutes and my polling is on a 1-minute interval.

To stop the background thread i first thought that the thread was attached to the session so that it get’s automatically stopped and invalidated/garbage collected when the session expires. Then because i realized that that isn’t what is happening i wanted to stop/interrupt the thread in the detach function (in DetachListener attached to the UI).
So i did that and added a heartbeat (interval just for testing: 5 seconds). Then i repeatedly opened a new tab went to the application, opened a new tab, closed the “old” tab, … This way i managed to open 9 instances of the thread and only 3 of them got closed (aka the detach listener was called). Also the hearbeat seems to be started when a new window with a new session is opened. Otherwise i wasn’t able to trigger the detach function even ones.

Yes the warning in the logs was about threadlocals:

The web application 
[/rbomonitorV7] created a ThreadLocal with key of type [com.vaadin.util.CurrentInstance$1]

 (value [com.vaadin.util.CurrentInstance$1@23062d8b]
) and a value of type [java.util.HashMap]
....

I know that Tomcat will eventually clean up those “leaks” but i still want to try to get rid of them as they look “ugly” in the logs :slight_smile:

Am i doing something wrong here or did i not understand heartbeat correctly?

Thanks in advance
M R

PS:
Is the DetachListener the Listener that always gets called when the Server-side detect that the Browser is closed/Session is invalidated/… or is this listener more focused towards heartbeat?

Also: Does the heartbeat always gets executed (kinda like a timer?) or only if a new UI/Session is opened?

Regards,
M R

DetachListeners are called when the UI is detached for whatever reason, either due to heartbeat expiration, session expiration, or explicitly calling UI.close().

The cleanup of old UIs is done on each request from the client, there’s no separate cleanup thread. Thus, if you open a UI, then close it, and do nothing else with the application, it will live until the session expires. If you open another UI and interact with it, the first UI is eventually cleaned up.

Ok so in my case when the UI still lives the thread is also still executed.
Now i thought that the hearbeat gets executed periodically (each x seconds depending on how big you set the heartbeatinterval) and will call the detach function if the Client doesn’t “respond” to 3 heartbeats.

Does this get executed when i close the UI and don’t interact with it and not open a new UI. Or is this only happening when the new UI is opened?

Oh and another question: Is the new built-in polling mechanism supposed to keep the session alive if the session timeout is set higher then the polling interval (kinda like it was in Refresher until now) or is it using a different kind of request which causes the Session to still get invalidated?

Sry for asking so many question but the new (beta) polling mechanism is my only change to realize one of my projects in Vaadin 7 as the Refresher Addon uses UIDL request which cause a “No UI provider found for the request”-Exception when the server restarts. Also it doesn’t get continued.

Thank you for all your support so far.

Regards,
M R

There’s a timer on the
client side
that sends a heartbeat request every N seconds, and when a heartbeat arrives on the server side, the “last heartbeat” timestamp of the relevant UI is updated to the current time.

However, there’s no timer on the
server side
that periodically checks whether the “last heartbeat” of any UI was more than 3*N seconds ago. Only when the server receives a request from a client-side UI (that is open), are expired server-side UIs in the same session sweeped. (Note the “in the same session” - sessions do not know about each others’ UIs.)

The Vaadin 7.1 polling request is a regular RPC request, so it does cause the UI cleanup routine to be run on each poll. However, so do heartbeat requests, so simply having a single UI open, without doing anything with it, should cause other, closed, UIs to eventually expire on the server side. Heartbeat requests, however, do not update the client-side state of the UI components.

So i got it to work, finally.

My Detach function wasn’t called because i didn’t set a proper session timeout. When i set it to something like one minute it calls the function when a server request is caused after the timeout exceeded.

Then i had another problem where even when the detach function was called, the thread wasn’t stopped. I made the mistake and just called thread.interrupt which only caused a thread.sleep command to get interrupted and not the entire thread. I fixed it by adding a boolean in my while loop which i set to false when detach is called although in future i think i’m gonna use a timer instead of a looping thread.

Thank you so much for your help. I learned a lot about the new polling/hearbeat/… mechanisms.