SWT- Bug (!?) - CreateIconIndirect

enrichard

Grünschnabel
Hallo zusammen,

ich hoffe zwar nicht, aber es scheint, als hätte ich einen BUG in SWT gefunden. Zumindest habe ich einen Weg gefunden ihn auszulösen :T

Der Fehler in der Java-Console sieht folgendermaßen aus:

#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x77f481bd, pid=3900, tid=2372
#
# Java VM: Java HotSpot(TM) Client VM (1.5.0_05-b05 mixed mode)
# Problematic frame:
# C [ntdll.dll+0x81bd]

...gefolgt von endlos vielen "[Too many errors, abort]"-Zeilen und beendet von "An unrecoverable stack overflow has occurred.", was auf eine Endlosschleife hindeutet. Im hs_err_pid*.log-File geht der Fehler auf die SWT-Methode CreateIconIndirect zurück.

Hier mal der Stack-Trace:

Stack: [0x0ae40000,0x0ae80000), sp=0x0ae7f4a8, free space=253k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C [ntdll.dll+0x81bd]
C [USER32.dll+0x13bbe]
C [USER32.dll+0x13c70]
C [USER32.dll+0x2768a]

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j org.eclipse.swt.internal.win32.OS.CreateIconIndirect(Lorg/eclipse/swt/internal/win32/ICONINFO;)I+0
j org.eclipse.swt.widgets.Decorations.createIcon(Lorg/eclipse/swt/graphics/Image;)Lorg/eclipse/swt/graphics/Image;+340
j org.eclipse.swt.widgets.Decorations.setImages(Lorg/eclipse/swt/graphics/Image;[Lorg/eclipse/swt/graphics/Image;)V+244
j org.eclipse.swt.widgets.Decorations.setImage(Lorg/eclipse/swt/graphics/Image;)V+28
j com.pictureshuffler.view.main.MainWindow.createShell()V+40
j com.pictureshuffler.view.AbstractView.createView()V+1
j com.pictureshuffler.view.main.MainWindow.createView()V+1
j com.pictureshuffler.view.AbstractView.open()Ljava/lang/Object;+11
j com.pictureshuffler.plugin.core.action.NormalRun$3.run()V+10
j org.eclipse.swt.widgets.Synchronizer.syncExec(Ljava/lang/Runnable;)V+15
j org.eclipse.swt.widgets.Display.syncExec(Ljava/lang/Runnable;)V+18
j com.pictureshuffler.plugin.core.action.NormalRun$2.handleEvent(Lorg/eclipse/swt/widgets/Event;)V+11
j org.eclipse.swt.widgets.EventTable.sendEvent(Lorg/eclipse/swt/widgets/Event;)V+71
j org.eclipse.swt.widgets.Widget.sendEvent(Lorg/eclipse/swt/widgets/Event;)V+25
j org.eclipse.swt.widgets.Display.runDeferredEvents()Z+84
j org.eclipse.swt.widgets.Display.readAndDispatch()Z+55
j com.pictureshuffler.view.ViewProvider.run()V+37
j java.lang.Thread.run()V+11
v ~StubRoutines::call_stub

In der Methode gibt es einen Aufruf, der durchaus das Potential für eine Endlosschleiße hat (Auszug aus swt.c):
C:
JNIEXPORT jint JNICALL Java_org_eclipse_swt_internal_win32_OS_CreateIconIndirect
	(JNIEnv *env, jclass that, jobject arg0)
{
	DECL_GLOB(pGlob)
	ICONINFO _arg0, *lparg0=NULL;
	jint rc;

	DEBUG_CALL("CreateIconIndirect\n")

	if (arg0) lparg0 = getICONINFOFields(env, arg0, &_arg0, &PGLOB(ICONINFOFc));

	rc = (jint)CreateIconIndirect(lparg0);

	if (arg0) setICONINFOFields(env, arg0, lparg0, &PGLOB(ICONINFOFc));

	return rc;
}
Der Aufruf von CreateIconIndirect in der CreateIconIndirect-Methode könnte das Problem entstehen lassen. Da die beiden Methoden aber eine unterschiedliche Anzahl Parameter haben, sollten sie eigentlich verschieden sein. Ich habe allerdings keine andere passende Methode gefunden.

Nun gibt es ja sonst keine Probleme mit dieser Methode, warum aber bei mir? Ich vermute, dass es mit einer eigenen DLL zusammenhängt., n der ich eine Dateisystemüberwachung implementiere. Der Code ist zu großen Teilen von anonytmouse aus http://cboard.cprogramming.com/showthread.php?t=77061 übernommen. Die DLL ist unten angehangen. Interessant ist dabei sicherlich der untere Teil, mit den JNI-Funktionen. Außerdem ist zu erwähnen, dass ich einen Java-Callback aus C++-Code heraus durchführe, der ausgelöst wird, wenn Dateisystemereignisse auftreten und ich mittels "WaitForSingleObjectEx( abortHandle, INFINITE, TRUE );" auf ein Abbruch-Event warte.

Hat einer von Euch vielleicht eine Idee, worin genau das Problem liegen könnte und wie ich es umgehen kann? Oder kennt jemand zufällig eine andere Möglichkeit mit Java Windows-Verzeichnisse zu überwachen, die keine Konflikte mit SWT verursacht (wohl eher nicht)?

Vielen Dank im voraus,
Henrik

Hier besagte DLL:
C:
#define _WIN32_WINNT 0x0400
#include <windows.h>

#include "Win32DirectoryWatcher.h"


// global vars for java-callback-function
jobject globalJavaObject;
jmethodID globalMid;
jobject globalMonitorHandle;

HANDLE abortHandle;


void throwJavaException(JNIEnv *env, LPCTSTR msg, DWORD errorCode)
{
	jclass exceptionClass = env->FindClass("java/lang/Exception");
	env->ThrowNew(exceptionClass, msg);
}


typedef void (CALLBACK *FileChangeCallback)(LPTSTR, DWORD, LPARAM);

typedef struct tagDIR_MONITOR
{
	OVERLAPPED ol;
	HANDLE     hDir;
	BYTE       buffer[32 * 1024];
	LPARAM     lParam;
	DWORD      notifyFilter;
	BOOL       fStop;
	FileChangeCallback callback;
} *HDIR_MONITOR;

/* 
 * Unpacks events and passes them to a user defined callback.
 */
VOID CALLBACK MonitorCallback(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
{
	TCHAR                    szFile[MAX_PATH];
	PFILE_NOTIFY_INFORMATION pNotify;
	HDIR_MONITOR             pMonitor  = (HDIR_MONITOR) lpOverlapped;
	size_t                   offset    =  0;
	BOOL RefreshMonitoring(HDIR_MONITOR pMonitor);

	if (dwErrorCode == ERROR_SUCCESS)
	{
		do
		{
			pNotify = (PFILE_NOTIFY_INFORMATION) &pMonitor->buffer[offset];
			offset += pNotify->NextEntryOffset;

#			if defined(UNICODE)
			{
			    lstrcpynW(szFile, pNotify->FileName,
			                min(MAX_PATH, pNotify->FileNameLength / sizeof(WCHAR) + 1));
			}
#			else
			{
			    int count = WideCharToMultiByte(CP_ACP, 0, pNotify->FileName,
			                                    pNotify->FileNameLength / sizeof(WCHAR),
			                                    szFile, MAX_PATH - 1, NULL, NULL);
			    szFile[count] = TEXT('\0');
			}
#			endif

			pMonitor->callback(szFile, pNotify->Action, pMonitor->lParam);

		} while (pNotify->NextEntryOffset != 0);
	}

	if (!pMonitor->fStop)
	{
		RefreshMonitoring(pMonitor);
	} 
}

/*
 * Refreshes the directory monitoring.
 */
BOOL RefreshMonitoring(HDIR_MONITOR pMonitor)
{
	return
	ReadDirectoryChangesW(pMonitor->hDir, pMonitor->buffer, sizeof(pMonitor->buffer), FALSE,
	                      pMonitor->notifyFilter, NULL, &pMonitor->ol, MonitorCallback);
}

void CALLBACK FileCallback(LPTSTR szFile, DWORD action, LPARAM lParam)
{
	JNIEnv *env;
	JavaVM *jvm;
	jsize count;

	if ( !JNI_GetCreatedJavaVMs(&jvm, 1, &count) ) 
	{
		if ( !jvm->AttachCurrentThread( (void**) &env, NULL) ) 
		{
			env->CallVoidMethod(
				globalJavaObject, 
				globalMid, 
				env->NewStringUTF(szFile),
				(jlong) action );
			jvm->DetachCurrentThread();
		}
	}
}

/*
 * Stops monitoring a directory.
 */
void StopMonitoring(HDIR_MONITOR pMonitor)
{
	if (pMonitor)
	{
		pMonitor->fStop = TRUE;

		CancelIo(pMonitor->hDir);

		if (!HasOverlappedIoCompleted(&pMonitor->ol))
		{
			SleepEx(5, TRUE);
		}

		CloseHandle(pMonitor->ol.hEvent);
		CloseHandle(pMonitor->hDir);
		HeapFree(GetProcessHeap(), 0, pMonitor);
	}
}

/*
 * Starts monitoring a directory.
 */
HDIR_MONITOR StartMonitoring(LPCTSTR szDirectory, DWORD notifyFilter, FileChangeCallback callback)
{
	HDIR_MONITOR pMonitor = (tagDIR_MONITOR*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*pMonitor));

	pMonitor->hDir = CreateFile(szDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
	                            NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

	if (pMonitor->hDir != INVALID_HANDLE_VALUE)
	{
		abortHandle = CreateEvent(NULL, TRUE, FALSE, NULL);
		pMonitor->ol.hEvent    = CreateEvent(NULL, TRUE, FALSE, NULL);
		pMonitor->notifyFilter = notifyFilter;
		pMonitor->callback     = callback;

		if (RefreshMonitoring(pMonitor))
		{
			return pMonitor;
		}
		else
		{
			CloseHandle(pMonitor->ol.hEvent);
			CloseHandle(pMonitor->hDir);
		}
	}

	HeapFree(GetProcessHeap(), 0, pMonitor);
	return NULL;
}

/* JAVA PART */

/*
 * Class:     com_pictureshuffler_plugin_incomming_localfolder_win32dirwatcher_Win32DirectoryWatcher
 * Method:    stopWatching
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL Java_com_pictureshuffler_plugin_incomming_localfolder_win32dirwatcher_Win32DirectoryWatcher_startWatchingNative
	(JNIEnv *env, jobject obj, jstring path, jlong filter) 
{
	// save object reference for callback
	globalJavaObject = env->NewGlobalRef(obj);
	
	// retrieve method id of callback function
	jclass cls = env->GetObjectClass(obj);
	globalMid = env->GetMethodID(cls, "fileSystemEventOccured", "(Ljava/lang/String;J)V");

	return (jlong) StartMonitoring(
		env->GetStringUTFChars (path, NULL),
		filter,
	    FileCallback);		
}


/*
 * Class:     com_pictureshuffler_plugin_incomming_localfolder_win32dirwatcher_Win32DirectoryWatcher
 * Method:    catchEvents
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_pictureshuffler_plugin_incomming_localfolder_win32dirwatcher_Win32DirectoryWatcher_catchEvents
  (JNIEnv *env, jobject, jlong handle)
{
	DWORD result;
	BOOL done = FALSE;

	while (!done) 
	{
		result = WaitForSingleObjectEx( abortHandle, INFINITE, TRUE );

		if (result != WAIT_IO_COMPLETION) 
		{
			done = TRUE;
		}

		if (result == WAIT_FAILED)
		{	
			throwJavaException(env, "catchEvents: wait failed", GetLastError());
		}
	}
}

/*
 * Class:     com_pictureshuffler_plugin_incomming_localfolder_win32dirwatcher_Win32DirectoryWatcher
 * Method:    stopWatching
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT void JNICALL Java_com_pictureshuffler_plugin_incomming_localfolder_win32dirwatcher_Win32DirectoryWatcher_stopWatchingNative
	(JNIEnv *env, jobject obj, jlong handle)
{
	// stop watcher and break up message loop
	if (handle) 
	{
		if (!SetEvent(abortHandle))
		{
			jclass exceptionClass = env->FindClass("java/lang/Exception");
			env->ThrowNew(exceptionClass, "Could not set abortEvent to signaled");
		}
		StopMonitoring( (HDIR_MONITOR) handle );
	}

	env->DeleteGlobalRef(globalJavaObject);
}
 
...ach so... vielleicht sollte ich noch dazu sagen, dass der Fehler wirklich erst dann auftritt, wenn ich das erste Mal die DLL genutzt habe.

Henrik
 
Hallo!

Darf man mal fragen, was du da genau machst? Wenn du nur ein Verzeichnis ueberwachen willst, reicht es doch vollkommen, wenn du einen Thread im Hintergrund laufen laesst, der mit list(...) bzw. listFiles(...) am entsprechenden Directory auf Veraenderungen ueberprueft.

Dieser Low-Level Ansatz erscheint mir etwas uebertrieben und weiterhin sind solche Geschichten auch nicht gerade Plattformunabhaengig ;)

Gruss Tom
 
Hi Tom!

Ganz konkret wird ein asynchroner Aufruf von ReadDirectoryChangesW gestartet, dem ein Callback-Funktionspointer übergeben wird. Diese Callback-Funktion wird immer dann aufgerufen, wenn ein Dateisystemereigniss auftritt. Um entsprechende Callback-Aufrufe zu ermöglichen, blocke ich mit WaitForSingleObjectEx den Java-Thread. Jene Callbacks rufen dann die Java-Funktion fileSystemEventOccured() auf.

Die Möglichkeit, die Du vorschlägst, funktioniert sicher auch, nur ist es nicht gerade sehr Performance-freundlich, in einer while-true-Schleife oder mit einem Intervall-Timer Verzeichnisse zu listen. Gerade wenn die Verzeichnisse sehr groß sind und/oder andere Prozesse auch darauf zugreifen, ist das nicht sehr vorteilhaft.

Die Problematik Plattformunabhängigkeit ist mir durchaus bewusst. Darauf kommt es aber in meinem Fall nicht an, da die entsprechende Anwendung vorerst nur unter Windows laufen soll.


Beste Gruesse,
Henrik
 
@Tom:

Ich glaube, ich habe das Problem gefunden. Es liegt scheinbar nicht in besagter SWT-Funktion, sondern vielmehr daran, dass mit HeapFree, die Variable pMonitor zerstört werden soll. Hier wird scheinbar mehr als nur pMonitor zerstört und der Java-Heap wird inkonsistent. Wenn ich entsprechende Aufrufe entferne, gibt es keine Probleme. Fragt sich nur, was ich mit den pMonitor-Leichen mache...


Beste Gruesse,
Henrik
 
Hallo!

Die Möglichkeit, die Du vorschlägst, funktioniert sicher auch, nur ist es nicht gerade sehr Performance-freundlich, in einer while-true-Schleife oder mit einem Intervall-Timer Verzeichnisse zu listen. Gerade wenn die Verzeichnisse sehr groß sind und/oder andere Prozesse auch darauf zugreifen, ist das nicht sehr vorteilhaft.
Also alle gaengigen applikationServer die hot Deployment unterstuetzen machen das so ;) und das funktioniert meiner Erfahrung nach recht gut. Solange du nicht jedesmal die gesamte Festplatte absuchen musst wirst du dir da Performance-Technisch schon kein Bein brechen ;) Probiers doch einfach mal aus.

Die Problematik Plattformunabhängigkeit ist mir durchaus bewusst. Darauf kommt es aber in meinem Fall nicht an, da die entsprechende Anwendung vorerst nur unter Windows laufen soll.
Ich kenne dein Umfeld nicht, aber diese Anforderungen habe ich auch schon sehr oft gehoert und da war Linux/Unix/Mac auch ganz schnell ein Thema ;)

Gruss Tom
 
Hi!

Also alle gaengigen applikationServer die hot Deployment unterstuetzen machen das so und das funktioniert meiner Erfahrung nach recht gut. Solange du nicht jedesmal die gesamte Festplatte absuchen musst wirst du dir da Performance-Technisch schon kein Bein brechen Probiers doch einfach mal aus.

Wenn Du meinst, dass das Praxis ist... ich denke, dass es nicht gerade sehr günstig ist, ständig IO-Operationen zu provozieren, wenn es bessere Alternativen gibt.

Ich kenne dein Umfeld nicht, aber diese Anforderungen habe ich auch schon sehr oft gehoert und da war Linux/Unix/Mac auch ganz schnell ein Thema

Es ist sicher in absehbarer Zeit Thema ;) Aber dann gilt es wiederum schnittigere Lösungen als einen Brute-Force-Scan zu implementieren, wenn nötig plattformabhängig. Ein entsprechender Test könnte zur Laufzeit erfolgen und eine plattformunabhängige Brute-Force-Lösung als Backup-Variante zur Verfügung stehen.


Dank Dir für Dein Feedback,
Henrik
 
Zurück