10-19-2008 10:02 AM
Hello all,
I have a DLL with functions for:
- acquisition-start
set global housekeeping variables
start new high-priority thread pool
schedule acquisition function in this new pool
- acquisition function in new high-priority pool scheduled by acquisition-start
schedule clean-up function in default-pool after finishing acquisition
start accquisition
monitoring global interrupt variable
- clean-up function scheduled in default-pool by acquisition function
release function resources in the high-priority pool
discard high-priority pool
- interrupt function
sets global variable that is monitored by acquisition function
When using sequence of acquisition-start and waiting until finished: works ok.
When using sequence of acquisition-start and interrupt: works ok
As precaution, I added the interrupt in DLL-main when this is detached.
The function in DLL-main sets the global variable for interrupt.
While testing, it crashed. With a few statements in the clean-up, I noticed that the clean-up function starts to work
- cleanup continues after CmtWaitForThreadPoolFunctionCompletion ie acquisition thread has finished
- cleanup releases acquisition function with return-value 0 ie no errors
- cleanup hangs on CmtDiscardThreadPool
Note that cleanup does NOT hang when it goes on after regular finishing the acquisition
Note that cleanup does NOT hang when interrupt is initiated external ie outside DLL-main
Note that scheduling the cleanup in the DEFAULT_THREAD_POOL is done because all kinds of alternatives do seem not to work inside a DLL.
Questions:
Is it needed to cleanup / discard extra threadpools from DLL-main ?
How to discard extra threadpools from DLL-main ?
Extract from source code:
struct sFBGLAB_Device
{
long DeviceNumber;
... ...
long FileStreamActive; /* -1=OFF/0=INTERIM/1=ON */
long ThreadSampleNumber;
int ThreadPoolHandle;
int ThreadFunctionHandle;
long volatile ThreadInterrupt;
};
static int ThreadStart = -1; // TBD make it a thread-safe variable
static struct sFBGLAB_Device FBGLAB_Devices[FBGLAB_MAXDEVICES+1];
int __stdcall DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
int i, threaddelay=0;
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
/* Respond to DLL loading by initializing the RTE */
if (InitCVIRTE (hinstDLL, 0, 0) == 0) return 0;
/* init housekeeping data */
FBGLAB_Open(-1L, FBGLAB_TYPE_NONE, NULL, 0L);
break;
case DLL_PROCESS_DETACH:
for (i = 1; i <= FBGLAB_MAXDEVICES; i++)
{
if (FBGLAB_Devices[i].FileStreamActive >= 0)
{
FBGLAB_StreamInterrupt(i);
/* arbitrary short time so the streaming can be interrupted and filed closed */
threaddelay += 5.0;
}
}
for (i=0; i < threaddelay; i++)
{
ProcessSystemEvents();
Delay(1.0);
}
// will still cause crash because high-priority threadpool is not discarded
if (!CVIRTEHasBeenDetached ())
CloseCVIRTE ();
break;
}
/* Return 1 to indicate successful initialization */
return 1;
}
int CVICALLBACK FBGLAB_ThreadCleanup(void *functionData)
{
long lDevice = *(long *)functionData;
int ThreadPoolHandle = FBGLAB_Devices[lDevice].ThreadPoolHandle;
int ThreadFunctionHandle = FBGLAB_Devices[lDevice].ThreadFunctionHandle;
CmtWaitForThreadPoolFunctionCompletion (ThreadPoolHandle, ThreadFunctionHandle, OPT_TP_PROCESS_EVENTS_WHILE_WAITING);
FBGLAB_Devices[lDevice].FileStreamActive = 0; /* -1=OFF/0=INTERIM/1=ON */
if (ThreadPoolHandle >= 0)
{
if (ThreadFunctionHandle >= 0)
{
CmtReleaseThreadPoolFunctionID (ThreadPoolHandle, ThreadFunctionHandle);
}
CmtDiscardThreadPool (ThreadPoolHandle);
}
FBGLAB_Devices[lDevice].ThreadSampleNumber = 0;
FBGLAB_Devices[lDevice].ThreadPoolHandle = -1;
FBGLAB_Devices[lDevice].ThreadFunctionHandle = -1;
FBGLAB_Devices[lDevice].ThreadInterrupt = 0;
FBGLAB_Devices[lDevice].FileStreamActive = -1;
return(0);
}
int CVICALLBACK FBGLAB_ThreadStreamingWavelength(void *functionData)
{
long lDevice = *(long *)functionData;
long *ThreadSamples = &(FBGLAB_Devices[lDevice].ThreadSampleNumber);
long volatile *ThreadInterrupt = &(FBGLAB_Devices[lDevice].ThreadInterrupt);
CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE,
FBGLAB_ThreadCleanup, functionData,
NULL);
// dummy function for development without hardware
while ((*ThreadSamples > 0) && (*ThreadInterrupt <= 0))
{
Delay(1.0);
*ThreadSamples -= 1;
}
*ThreadSamples = 0;
return(0);
}
long __stdcall FBGLAB_StartTest(long lDevice, long Nsamples)
{
int Status = 0;
int ThreadPoolHandle = -1, ThreadFunctionHandle = -1;
if ((lDevice < 1) || (lDevice > FBGLAB_MAXDEVICES)) return(FBGLAB_ERROR_DEVICENUMBER);
// if (FBGLAB_Devices[lDevice].DeviceOpen <= 0) return (FBGLAB_ERROR_DEVICENOTOPEN);
if (Nsamples <= 0) return(FBGLAB_ERROR_DATASAMPLES); /* there is no fixed maximum i.e. dependent on #sensors */
if (ThreadStart >= 0) return(FBGLAB_ERROR_MULTITHREADSTART);
ThreadStart = 0;
if (FBGLAB_Devices[lDevice].FileStreamActive >= 0) /* -1=OFF/0=INTERIM/1=ON */
{
ThreadStart = -1;
return(FBGLAB_ERROR_MULTITHREADRUNNING);
}
FBGLAB_Devices[lDevice].FileStreamActive = 0; /* -1=OFF/0=INTERIM/1=ON */
FBGLAB_Devices[lDevice].ThreadSampleNumber = Nsamples;
FBGLAB_Devices[lDevice].ThreadInterrupt = 0;
Status = CmtNewThreadPool (1, &ThreadPoolHandle);
if (Status >= 0)
Status = CmtSetThreadPoolAttribute (ThreadPoolHandle, ATTR_TP_THREAD_PRIORITY,THREAD_PRIORITY_HIGHEST);
if (Status >= 0)
Status = CmtScheduleThreadPoolFunctionAdv (ThreadPoolHandle,
FBGLAB_ThreadStreamingWavelength, &FBGLAB_Devices[lDevice].DeviceNumber,
THREAD_PRIORITY_HIGHEST, NULL,
EVENT_TP_THREAD_FUNCTION_END,
NULL, NULL, &ThreadFunctionHandle);
if (Status < 0)
{
if (ThreadPoolHandle >= 0)
{
if (ThreadFunctionHandle >= 0)
{
CmtReleaseThreadPoolFunctionID (ThreadPoolHandle, ThreadFunctionHandle);
}
CmtDiscardThreadPool (ThreadPoolHandle);
}
FBGLAB_Devices[lDevice].FileStreamActive = -1;
}
else
{
FBGLAB_Devices[lDevice].FileStreamActive = 1;
FBGLAB_Devices[lDevice].ThreadPoolHandle = ThreadPoolHandle;
FBGLAB_Devices[lDevice].ThreadFunctionHandle = ThreadFunctionHandle;
}
ThreadStart = -1;
return((long)Status);
}
long __stdcall FBGLAB_StreamInterrupt(long lDevice)
{
if ((lDevice < 1) || (lDevice > FBGLAB_MAXDEVICES)) return(FBGLAB_ERROR_DEVICENUMBER);
FBGLAB_Devices[lDevice].ThreadInterrupt = 1;
return(0);
}
10-20-2008 09:52 AM
Is it needed to cleanup / discard extra threadpools from DLL-main?
How to discard extra threadpools from DLL-main ?
Ideally, you should cleanup/discard extra threadpools. But you cannot discard threadpools from DllMain. See the function help for CmtDiscardThreadPool - it specifically warns that the program may hang if this function is called from DllMain.
One simple and correct way to discard threadpools from DLLs, is to export a cleanup function from the DLL that the caller of the DLL should call - and this should be done before the DLL is unloaded. If some functions exported from your DLL already have "cleanup" semantics, then you could discard the threadpool in one of those appropriate functions.
Basically, CmtDiscardThreadPool waits for all currently executing thread pool threads to finish before destroying them. But you should not call blocking/waiting functions in DllMain - or else, you may get an hang. Also, it is a bad idea to allow the DLL to unload with the threadpool threads still running - you will get a GPF. So, where ever you wait for the threads to finish would be one good place to also dispose the thread pool. You cannot really protect the callers of the DLL if they unload your DLL without waiting for your threads to finish - the best your DLL can do is to export a cleanup function that has to be called before the DLL can be unloaded, and require your callers to call this from outside DLLMain.
10-20-2008 11:53 AM
Thanks for the reply.
I have already most of your suggestions. But it is not yet user-proof.
I have a clean-up that is scheduled by the thread-function in the default-pool. This one is automatically continued when the thread-function has finished. So basically the thread pool is discarded automatic when the thread-function has finished. The thread-function finishes when it has executed its number of loops. Or it finished when - via exported function - the user sets a global interrup-parameter after which the thread-function will finish within typical 1 second.
I cannot guarantee that the user does not unload the DLL. So I hoped / tried that the exit via the DLL-main would work. I gave it a 50/50 change because I do not call CmtDiscardThreadPool from the DLL but I set the global-interrupt parameter and wait a few seconds. In this interval, the previous scheduled clean-up can discard the threat. But ..... it did not work.
So it seems, it is not possible ! However if somebody did find a work-aroud ..... please reply a message.