Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Java Development

Reply
New Developer
Posts: 10
Registered: ‎03-26-2014
My Device: 8520
My Carrier: Vodafone
Accepted Solution

Deadlock of UI Thread

Hi all,

 

I've written an utility class to make sure that at one time there is at most one Dialog showed and avoid overlaps (this may happen since the application is made of many threads and each one may need to alert the user).

 

This is the code:

 

public class UserDialogUtils extends Thread implements SystemListener2 {

	private boolean isForeground = false, isBacklightOn = false;
	private final Object screenLock = new Object();

	private boolean newDialogRequestPresent = false, lastDialogClosed = true,
			lastDialogResultConsumed = true;

	private static UserDialogUtils instance = null;

	private UserDialog currentDialog = null;

	private UserDialogUtils() {
		super();
		isBacklightOn = Backlight.isEnabled();
		isForeground = Application.getApplication().isForeground();
	}

	public static synchronized UserDialogUtils getInstance() {
		if (instance == null || !instance.isAlive()) {
			if (instance != null) {
				Application.getApplication().removeSystemListener(instance);
			}
			instance = new UserDialogUtils();
			Application.getApplication().addSystemListener(instance);
			instance.start();
		}
		return instance;
	}

	public synchronized void popupAlert(String msg) {
		while (!(!newDialogRequestPresent && lastDialogClosed && lastDialogResultConsumed)) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}
		currentDialog = new UserDialog(UserDialog.DIALOG_ALERT, msg, 0);
		newDialogRequestPresent = true;
		lastDialogResultConsumed = false;
		notifyAll();

		while (!(!newDialogRequestPresent && lastDialogClosed))
			try {
				wait();
			} catch (InterruptedException e) {
			}
		// int res = currentDialog.res;
		lastDialogResultConsumed = true;
		notifyAll();
		return;
	}

	public synchronized boolean popupYesNo(String msg) {
		while (!(!newDialogRequestPresent && lastDialogClosed && lastDialogResultConsumed)) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}
		currentDialog = new UserDialog(UserDialog.DIALOG_YES_NO, msg, 0);
		newDialogRequestPresent = true;
		lastDialogResultConsumed = false;
		notifyAll();

		while (!(!newDialogRequestPresent && lastDialogClosed))
			try {
				wait();
			} catch (InterruptedException e) {
			}
		int res = currentDialog.res;
		lastDialogResultConsumed = true;
		notifyAll();
		return res == Dialog.YES;
	}
	
// Implements method from SystemListener2 public void backlightStateChange(boolean on) { synchronized (screenLock) { isBacklightOn = on; screenLock.notifyAll(); } }
// Called by main UIapp when switching fgd/bkgd public void setForeground(boolean isForeground) { synchronized (screenLock) { this.isForeground = isForeground; screenLock.notifyAll(); } } public void run() { while (true) { synchronized (instance) { while (!(newDialogRequestPresent && lastDialogClosed)) { try { wait(); } catch (InterruptedException e) { } } newDialogRequestPresent = false; lastDialogClosed = false; currentDialog.manage(); notifyAll(); } } } public synchronized void dialogClosed(UserDialog closedDialog) { if (closedDialog == currentDialog) { lastDialogClosed = true; notifyAll(); } else { // shouldn't ever happen } } private class UserDialog { public static final int DIALOG_STATUS_SHOW = 1; public static final int DIALOG_INFORM = 2; public static final int DIALOG_ALERT = 3; public static final int DIALOG_YES_NO = 4; public static final int DIALOG_OK_CANCEL = 5; private int type = 0; private String msg = null; private int millis = 0; private int res = 0; public UserDialog(int type, String msg, int millis) { super(); this.type = type; this.msg = msg; this.millis = millis; } public void manage() { synchronized (screenLock) { while (!(isBacklightOn && isForeground)) { try { screenLock.wait(); } catch (InterruptedException e) { } } UiApplication.getUiApplication().invokeLater(new Runnable() { public void run() { switch (type) { case DIALOG_STATUS_SHOW: if (millis > 0) Status.show(msg, millis); else Status.show(msg); break; case DIALOG_INFORM: Dialog.inform(msg); break; case DIALOG_ALERT: Dialog.alert(msg); break; case DIALOG_YES_NO: res = Dialog.ask(Dialog.D_YES_NO, msg); break; case DIALOG_OK_CANCEL: res = Dialog.ask(Dialog.D_OK_CANCEL, msg); break; default: break; } dialogClosed(UserDialog.this); } }); } } } }

 

I can't understand how and why, but sometimes it will cause a deadlock (i believe on the Event lock).

Basically in some cases UIApplication will never invoke the runnable contained within the manage() method of the UserDialog instance.

This means that, when it happens, the screen freezes like it was before one of the popup methods (UserDialogUtils.getInstance().popupAlert("Please don't freeze")) was called.

 

What am I missing?

 

Thanks,

 

Luca

Developer
Posts: 19,636
Registered: ‎07-14-2008
My Device: Not Specified

Re: Deadlock of UI Thread

I must admit my first reaction here is that the requirement is unecessary and the processing complicated.  The Event Thread already provides a way of running just one thing at a time. so I think 'invokeAndWait' would provide you with the single Dialog that you require. 

 

With respect to your Threads, rather than complicating the UserDialog, you could just implement a call back  - then if the Thread wants to wait, it can.  I also don't really understand the issues around foreground/background and backlight, I mean if the user chooses to background your application while one of your Dialogs is showing, then do you really care?

 

Anyway, can you do something to help us understand your code. Imagine you have two Threads running.  At the same time, one attempts to do a popupAlert(), and the other attempts to a popupYesNo().  Can you step us through the code, explaining where the first one to get in (assume it is the popupAlert) blocks the second one. While doing this, you will need to note why you have used synchronize on the methods and what that will do, and why you have the various flags that will put the processing into a wait loop at various points, and why. 

 

My hope is that if you look at this code hard, you will see that you can simplify it.  But most likely I am just not understanding the code. 

New Developer
Posts: 10
Registered: ‎03-26-2014
My Device: 8520
My Carrier: Vodafone

Re: Deadlock of UI Thread

Thanks @peter_strange for your reply.

 

You're right, invokeAndWait would do that, but in that case it would also block all UI execution, not only second dialogs from appearing... or not?

 

If i implement a callback instead of implementing synchronization here, then a thread may decide to not wait for a dialog to close, and pop up a second dialog (which is exactly what I want to avoid). That means that i would have to use the callback in each and every part of the code that needs to pop up a dialog, thus the reason for trying to manage everything in this utlility class.

 

Regarding the foreground/background/backlight issue, it was mainly due to avoid the

Application Resource Monitor to shut down the application, since it doesn't like when UI jobs are executed when the app is in the background or the backlight is off.

 

However, as you said, the code can be simplified greatly. Like this:

 

public class UserDialogUtils implements SystemListener2 {

	private boolean isForeground = false, isBacklightOn = false;
	private final Object screenLock = new Object();

	private boolean dialogShowing = false, dialogResultReady = false;

	private static UserDialogUtils instance = null;

	private UserDialogUtils() {
		isBacklightOn = Backlight.isEnabled();
		isForeground = Application.getApplication().isForeground();
	}

	public static synchronized UserDialogUtils getInstance() {
		if (instance == null) {
			instance = new UserDialogUtils();
			Application.getApplication().addSystemListener(instance);
		}
		return instance;
	}

	public synchronized void popupAlert(String msg) {
		while (!(!dialogShowing && !dialogResultReady)) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}
		UserDialog dialog = new UserDialog(UserDialog.DIALOG_ALERT, msg, 0);
		dialogShowing = true;
		dialog.manage();

		while (!(dialogShowing && dialogResultReady))
			try {
				wait();
			} catch (InterruptedException e) {
			}
		dialogShowing = false;
		dialogResultReady = false;
		notifyAll();
		return;
	}

	public synchronized boolean popupYesNo(String msg) {
		while (!(!dialogShowing && !dialogResultReady)) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}
		UserDialog dialog = new UserDialog(UserDialog.DIALOG_YES_NO, msg, 0);
		dialogShowing = true;
		dialog.manage();

		while (!(dialogShowing && dialogResultReady))
			try {
				wait();
			} catch (InterruptedException e) {
			}
		int res = dialog.res;
		dialogShowing = false;
		dialogResultReady = false;
		notifyAll();
		return res == Dialog.YES;
	}

	public void backlightStateChange(boolean on) {
		synchronized (screenLock) {
			isBacklightOn = on;
			screenLock.notifyAll();
		}
	}

	public void setForeground(boolean isForeground) {
		synchronized (screenLock) {
			this.isForeground = isForeground;
			screenLock.notifyAll();
		}
	}

public synchronized void dialogClosed() {
dialogResultReady = true;
notifyAll();
} private class UserDialog { public static final int DIALOG_STATUS_SHOW = 1; public static final int DIALOG_INFORM = 2; public static final int DIALOG_ALERT = 3; public static final int DIALOG_YES_NO = 4; public static final int DIALOG_OK_CANCEL = 5; private int type = 0; private String msg = null; private int millis = 0; private int res = 0; public UserDialog(int type, String msg, int millis) { super(); this.type = type; this.msg = msg; this.millis = millis; } public void manage() { synchronized (screenLock) { while (!(isBacklightOn && isForeground)) { try { screenLock.wait(); } catch (InterruptedException e) { } } UiApplication.getUiApplication().invokeLater(new Runnable() { public void run() { switch (type) { case DIALOG_STATUS_SHOW: if (millis > 0) Status.show(msg, millis); else Status.show(msg); break; case DIALOG_INFORM: Dialog.inform(msg); break; case DIALOG_ALERT: Dialog.alert(msg); break; case DIALOG_YES_NO: res = Dialog.ask(Dialog.D_YES_NO, msg); break; case DIALOG_OK_CANCEL: res = Dialog.ask(Dialog.D_OK_CANCEL, msg); break; default: break; } dialogClosed(); } }); } } } }

 

In this case, the dialogShowing and dialogResultReady are semaphores that indicates the status of the screen.

If they are both false, then there is no dialog currently on the screen.

If only dialogShowing is true, it means that the dialog was rendered on the screen, but the user didn't click the dialog button yet.

If both true, than the user has just click the button and the dialog is being closed, so one that was in the wait() queue may now be managed (and the result of the first one returned to the caller, in the example of popupYesNo()).

 

I believe I didn't explain myself well before. The deadlock error was not due to the synchronization of two threads invoking one of the popup methods, actually the synchronization was working well.

 

The UI deadlock happened even with only one thread requesting a dialog popup.

It appears that the runnable inside the manage() method is never executed when the dialog popup request is generated by a user action, e.g. clicking on a button, for example inside a Screen:

 

ButtonField bf = new ButtonField("Test dialog");
add(bf);
bf.setChangeListener(new FieldChangeListener() {
	public void fieldChanged(Field field, int context) {
		UserDialogUtils.getInstance().popupAlert("This dialog won't ever show up");
	}
});

I have to do more tests and see if this is the case. If so, then may it be caused by the main UI thread requesting a repainting of the button and never releasing the UI lock and never repainting the button because the execution is blocked in one of the wait() calls?

 

Cheers,

 

beto

Highlighted
Developer
Posts: 19,636
Registered: ‎07-14-2008
My Device: Not Specified

Re: Deadlock of UI Thread

[ Edited ]

I appreciated that the issue you were worried about but not specifically related to the UserDialogUtils code, but I thought there is no point in investigating something dependent on UserDialogUtils until sure that UserDialogUtils is actually processing correctly.

 

I might be missing something, but doesn't the synchronize on the methods actually prevent multiple Threads displaying the Dialogs at the same time?

 

You only need the loop because manage() returns before the Dialog has been dismissed.  And this happens because you use invokeLater and not invokeAndWait.  Your dialogs block the Event Thread at some stage, I am not sure that your workaround (the loop) here is really needed. 

 

But to be fair, I have not looked hard at the code.

 

Anyway, I have the answer to your problem I think.

 

fieldChanged() is called on the Event Thread.  So you are holding the Event Tread until it exits.  Now in your processing you never return from popupAlert until something has got the Event Thread, specifically the Dialog.  So you are deadlocked. 

 

Short answer, don't call methods in UserDialogUtils while holding the Event Thread.

New Developer
Posts: 10
Registered: ‎03-26-2014
My Device: 8520
My Carrier: Vodafone

Re: Deadlock of UI Thread

Indeed I tried to use invokeAndWait without using the wait() loops, I switched to invokeLater() and wait() to see if the deadlock problem may have been caused by that.

 

However as you pointed out the problem is not caused by that, but by the caller holding the event thread.

 

I did think that this could be the problem at some point, and I tried to do this modification to the manage() method:

 

public void manage() {
	synchronized (screenLock) {
		while (!(isBacklightOn && isForeground)) {
			try {
				screenLock.wait();
			} catch (InterruptedException e) {
			}
		}

		if (Application.isEventDispatchThread()) {
			switch (type) {
			case DIALOG_STATUS_SHOW:
				if (millis > 0)
					Status.show(msg, millis);
				else
					Status.show(msg);
				break;
			case DIALOG_INFORM:
				Dialog.inform(msg);
				break;
			case DIALOG_ALERT:
				Dialog.alert(msg);
				break;
			case DIALOG_YES_NO:
				res = Dialog.ask(Dialog.D_YES_NO, msg);
				break;
			case DIALOG_OK_CANCEL:
				res = Dialog.ask(Dialog.D_OK_CANCEL, msg);
				break;
			default:
				break;
			}
			dialogClosed();
		} else {
			UiApplication.getUiApplication().invokeLater(
					new Runnable() {
						public void run() {
							switch (type) {
							case DIALOG_STATUS_SHOW:
								if (millis > 0)
									Status.show(msg, millis);
								else
									Status.show(msg);
								break;
							case DIALOG_INFORM:
								Dialog.inform(msg);
								break;
							case DIALOG_ALERT:
								Dialog.alert(msg);
								break;
							case DIALOG_YES_NO:
								res = Dialog.ask(Dialog.D_YES_NO, msg);
								break;
							case DIALOG_OK_CANCEL:
								res = Dialog.ask(Dialog.D_OK_CANCEL,
										msg);
								break;
							default:
								break;
							}
							dialogClosed();
						}
					});
		}
	}
}

However Application.isEventDispatchThread() always returned false because I was calling it from within a background thread (see first version of the code).

 

This is the code I'm using now, and it looks like it did the trick:

public class UserDialogUtils implements SystemListener2 {

	private boolean isForeground = false, isBacklightOn = false;
	private final Object screenLock = new Object();

	private static UserDialogUtils instance = null;

	private UserDialogUtils() {
		super();
		isBacklightOn = Backlight.isEnabled();
		isForeground = Application.getApplication().isForeground();
	}

	public static synchronized UserDialogUtils getInstance() {
		if (instance == null) {
			instance = new UserDialogUtils();
			Application.getApplication().addSystemListener(instance);
		}
		return instance;
	}

	public synchronized void popupAlert(String msg) {
		UserDialog dialog = new UserDialog(UserDialog.DIALOG_ALERT, msg, 0);
		dialog.manage();
	}

	public synchronized boolean popupYesNo(String msg) {
		UserDialog dialog = new UserDialog(UserDialog.DIALOG_YES_NO, msg, 0);
		dialog.manage();
		return dialog.res == Dialog.YES;
	}

	public void backlightStateChange(boolean on) {
		synchronized (screenLock) {
			isBacklightOn = on;
			screenLock.notifyAll();
		}
	}

	public void setForeground(boolean isForeground) {
		synchronized (screenLock) {
			this.isForeground = isForeground;
			screenLock.notifyAll();
		}
	}

	private class UserDialog {
		public static final int DIALOG_STATUS_SHOW = 1;
		public static final int DIALOG_INFORM = 2;
		public static final int DIALOG_ALERT = 3;
		public static final int DIALOG_YES_NO = 4;
		public static final int DIALOG_OK_CANCEL = 5;

		private int type = 0;
		private String msg = null;
		private int millis = 0;

		private int res = 0;

		public UserDialog(int type, String msg, int millis) {
			super();
			this.type = type;
			this.msg = msg;
			this.millis = millis;
		}

		public void manage() {
			synchronized (screenLock) {
				while (!(isBacklightOn && isForeground)) {
					try {
						screenLock.wait();
					} catch (InterruptedException e) {
					}
				}

				if (Application.isEventDispatchThread()) {
					switch (type) {
					case DIALOG_STATUS_SHOW:
						if (millis > 0)
							Status.show(msg, millis);
						else
							Status.show(msg);
						break;
					case DIALOG_INFORM:
						Dialog.inform(msg);
						break;
					case DIALOG_ALERT:
						Dialog.alert(msg);
						break;
					case DIALOG_YES_NO:
						res = Dialog.ask(Dialog.D_YES_NO, msg);
						break;
					case DIALOG_OK_CANCEL:
						res = Dialog.ask(Dialog.D_OK_CANCEL, msg);
						break;
					default:
						break;
					}
				} else {
					UiApplication.getUiApplication().invokeAndWait(
							new Runnable() {
								public void run() {
									switch (type) {
									case DIALOG_STATUS_SHOW:
										if (millis > 0)
											Status.show(msg, millis);
										else
											Status.show(msg);
										break;
									case DIALOG_INFORM:
										Dialog.inform(msg);
										break;
									case DIALOG_ALERT:
										Dialog.alert(msg);
										break;
									case DIALOG_YES_NO:
										res = Dialog.ask(Dialog.D_YES_NO, msg);
										break;
									case DIALOG_OK_CANCEL:
										res = Dialog.ask(Dialog.D_OK_CANCEL,
												msg);
										break;
									default:
										break;
									}
								}
							});
				}
			}
		}
	}
	
}

Simplified indeed with respect to the first version Smiley Happy

 

Thanks a lot for your help!

 

Best,

beto

Developer
Posts: 19,636
Registered: ‎07-14-2008
My Device: Not Specified

Re: Deadlock of UI Thread

[ Edited ]

This wait code should NOT be executed while holding the Event Thread:

 

while (!(isBacklightOn && isForeground)) {
try {
screenLock.wait();
} catch (InterruptedException e) {
}
}

 

I suggest you move it inside your Event Thread test:

if (Application.isEventDispatchThread()) {

// in here

...

}

 

Your processing on the Event Thread will barf if it ever executes this wait.

 

I am not convinced you need this loop in any circumstances, but you certainly don't need it and shouldn't do it when on the Event Thread.

 

I still think you have a potential problem.  I think there is race condition when your background Thread and your foreground process both do a popupAlert() at the same time, and the background Thread gets in first (just).  The Event Thread would be locked out on the synchronized, and so you would be screwed.  I don't think this will ever happen because the BB is a single processor and thread switches at selected points, so the code needs to be pseudo thread safe rather than truly thread safe.  But if you were trying to write similar code for a more sophisticated environment (like a PC). then be aware that I think this code is flawed. 

New Developer
Posts: 10
Registered: ‎03-26-2014
My Device: 8520
My Carrier: Vodafone

Re: Deadlock of UI Thread

You're right.

 

Also the deadlock problem is still there.

 

Suppose a UI thread started a background thread and is waiting for it to finish before proceeding. In this case if the background thread tries to pop up a dialog, it will result in the exact same deadlock as before.

 

I suppose I uncorked the champagne too soon...

 

At this point I believe the best solution is not using this utility class at all and "praying" that two dialogs don't appear simultaneously...

 

Thanks nonetheless,

 

beto

Developer
Posts: 19,636
Registered: ‎07-14-2008
My Device: Not Specified

Re: Deadlock of UI Thread

The problem is the Foreground waiting on the Background Thread, and how exactly it does that. 

 

You can stall the foreground processing with out blocking the Event Thread.  The easiest approach is to push a popup screen, and have the popup screen ignore any user input.  So the Event Thread is not blocked, just not useful to the user.....  Then when the background Thread is complete, it issues a call back to the foreground processing, which dismisses the popup screen. 

 

Now if the background processing wishes to alert the user, this alert will appear over the top of the popup screen.