/* *@@sourcefile threads.c: * contains helper functions for creating, destroying, and * synchronizing threads, including PM threads with a * message queue which is created automatically. * * See thrCreate() for how to start such a thread. * * Usage: All OS/2 programs. * * Function prefixes (new with V0.81): * -- thr* Thread helper functions * * This file is new with V0.81 and contains all the thread * functions that used to be in helpers.c. * * Note: Version numbering in this file relates to XWorkplace version * numbering. * *@@header "helpers\threads.h" */ /* * Copyright (C) 1997-2002 Ulrich M”ller. * This file is part of the "XWorkplace helpers" source package. * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, in version 2 as it comes in the * "COPYING" file of the XWorkplace main distribution. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #define OS2EMX_PLAIN_CHAR // this is needed for "os2emx.h"; if this is defined, // emx will define PSZ as _signed_ char, otherwise // as unsigned char #define INCL_WINMESSAGEMGR #define INCL_DOSPROCESS #define INCL_DOSSEMAPHORES #define INCL_DOSERRORS #include #include #include #include "setup.h" // code generation and debugging options #include "helpers\linklist.h" #include "helpers\threads.h" #pragma hdrstop /* *@@category: Helpers\Control program helpers\Thread management * see threads.c. */ /* ****************************************************************** * * Global variables * ********************************************************************/ LINKLIST G_llThreadInfos; // linked list of all THREADINFOS ever created... // no auto-free HMTX G_hmtxThreadInfos = NULLHANDLE; /* ****************************************************************** * * Functions * ********************************************************************/ /* *@@ LockThreadInfos: * *@@added V0.9.9 (2001-03-07) [umoeller] */ STATIC BOOL LockThreadInfos(VOID) { APIRET arc = NO_ERROR; if (G_hmtxThreadInfos == NULLHANDLE) { // first call: arc = DosCreateMutexSem(NULL, // unnamed &G_hmtxThreadInfos, 0, // unshared TRUE); // request! lstInit(&G_llThreadInfos, FALSE); } else arc = DosRequestMutexSem(G_hmtxThreadInfos, SEM_INDEFINITE_WAIT); return (arc == NO_ERROR); } /* *@@ UnlockThreadInfos: * *@@added V0.9.9 (2001-03-07) [umoeller] */ STATIC VOID UnlockThreadInfos(VOID) { DosReleaseMutexSem(G_hmtxThreadInfos); } /* *@@ thr_fntGeneric: * generic thread function used by thrCreate. * This in turn calls the actual thread function * specified with thrCreate. * *@@added V0.9.2 (2000-03-05) [umoeller] *@@changed V0.9.7 (2000-12-18) [lafaix]: THRF_TRANSIENT support added *@@changed V1.0.0 (2002-08-21) [umoeller]: added WinCancelShutdown to avoid shutdown hangs */ STATIC VOID _Optlink thr_fntGeneric(PVOID ptiMyself) { PTHREADINFO pti = (PTHREADINFO)ptiMyself; if (pti) { HEV hevExitComplete; if (pti->flFlags & THRF_WAIT) // "Wait" flag set: thrCreate is then // waiting on the wait event sem posted... // note: we do not post if THRF_WAIT_EXPLICIT! // thread must then post itself DosPostEventSem(pti->hevRunning); if (pti->flFlags & THRF_PMMSGQUEUE) { // create msg queue if ((pti->hab = WinInitialize(0))) { if ((pti->hmq = WinCreateMsgQueue(pti->hab, 4000))) { // run WinCancelShutdown; this func is used // in XWPDAEMN and causes the default OS/2 // shutdown to hang // V1.0.0 (2002-08-21) [umoeller] WinCancelShutdown(pti->hmq, TRUE); // run thread func ((PTHREADFUNC)pti->pThreadFunc)(pti); WinDestroyMsgQueue(pti->hmq); } WinTerminate(pti->hab); } } else // no msgqueue: // run thread func without msg queue ((PTHREADFUNC)pti->pThreadFunc)(pti); if (pti->flFlags & (THRF_WAIT | THRF_WAIT_EXPLICIT)) // V0.9.9 (2001-03-14) [umoeller] // "Wait" flag set: delete semaphore DosCloseEventSem(pti->hevRunning); // V0.9.9 (2001-03-07) [umoeller] // remove thread from global list if (LockThreadInfos()) { lstRemoveItem(&G_llThreadInfos, pti); UnlockThreadInfos(); } // copy event sem before freeing pti hevExitComplete = pti->hevExitComplete; // set exit flags // V0.9.7 (2000-12-20) [umoeller] pti->tid = NULLHANDLE; if (pti->ptidRunning) // clear "running" flag *(pti->ptidRunning) = 0; // (2000-12-18) [lafaix] clean up pti if thread is transient. if (pti->flFlags & THRF_TRANSIENT) free(pti); if (hevExitComplete) // caller wants notification: DosPostEventSem(hevExitComplete); // V0.9.16 (2001-12-08) [umoeller] } // thread func exits } /* *@@ thrCreate: * this function fills a THREADINFO structure and starts * a new thread using _beginthread. * * You must pass the thread function in pfn, which will * then be executed. The thread will be passed a pointer * to the THREADINFO structure as its thread parameter. * The ulData field in that structure is set to ulData * here. Use whatever you like. * * The thread function must be declared like this: * + void _Optlink fntWhatever(PTHREADINFO ptiMyself) * * You should manually specify _Optlink because if the * function is prototyped somewhere, VAC will automatically * modify the function's linkage, and you'll run into * crashes. * * The thread's ptiMyself is then a pointer to the * THREADINFO structure passed to this function. * In your thread function, ulData may be obtained like this: * + ULONG ulData = ptiMyself->ulData; * * thrCreate does not start your thread func directly, * but only through the thr_fntGeneric wrapper to * provide additional functionality. As a consequence, * in your own thread function, NEVER call _endthread * explicitly, because this would skip the exit processing * (cleanup) in thr_fntGeneric. Instead, just fall out of * your thread function, or return(). * * The THREADINFO structure passed to this function must * be accessible all the time while the thread is running * because the thr* functions will use it for maintenance. * This function does NOT check whether a thread is * already running in *pti. Do not use the same THREADINFO * for several threads. * * If you do not want to manage the structure yourself, * you can pass the THRF_TRANSIENT flag (see below). * * ptidRunning is an optional parameter and can be NULL. * If non-null, it must point to a ULONG which will contain * the thread's TID while the thread is still running. * Once the thread exits, the ULONG will automatically * be set to zero; this is useful for a quick check whether * the thread is currently busy. * * Note: ptidRunning is only reliable if you set the * THRF_WAIT or THRF_WAIT_EXPLICIT flags. * * flFlags can be any combination of the following: * * -- THRF_PMMSGQUEUE: creates a PM message queue on the * thread. Your thread function will find the HAB and * the HMQ in its THREADINFO. These are automatically * destroyed when the thread terminates. * * WARNING: Be careful with this setting for short-lived * threads which are started synchronously while some * other thread is locking PM, e.g. in a WinSendMsg * call. While creation of the thread will still * succeed, the thread will _not_ terminate properly * while PM is locked, so do not wait for such a thread * to terminate!! (V0.9.16) * * -- THRF_WAIT: if this is set, thrCreate does not * return to the caller until your thread function * has successfully started running. This is done by * waiting on an event semaphore which is automatically * posted by thr_fntGeneric. This is useful for the * typical PM "Worker" thread where you need to disable * menu items on thread 1 while the thread is running. * * -- THRF_WAIT_EXPLICIT: like THRF_WAIT, but in this case, * your thread function _must_ post THREADINFO.hevRunning * yourself (thr_fntGeneric will not automatically * post it). Useful for waiting until your own thread * function is fully initialized, e.g. if it creates * an object window. * * WARNING: if your thread forgets to post this, we'll * hang. * * Added V0.9.9 (2001-03-14) [umoeller]. * * -- THRF_TRANSIENT: creates a "transient" thread where * pti may be NULL. A THREADINFO structure is then * allocated from the heap internally, but not visible * to the caller. * * This now (V0.9.9) returns the TID of the new thread or * null on errors. * *@@changed V0.9.0 [umoeller]: default stack size raised for Watcom (thanks, Rdiger Ihle) *@@changed V0.9.0 [umoeller]: _beginthread is now only called after all variables have been set (thanks, Rdiger Ihle) *@@changed V0.9.2 (2000-03-04) [umoeller]: added stack size parameter *@@changed V0.9.2 (2000-03-06) [umoeller]: now using thr_fntGeneric; thrGoodbye is no longer needed *@@changed V0.9.3 (2000-04-29) [umoeller]: removed stack size param; added fCreateMsgQueue *@@changed V0.9.3 (2000-05-01) [umoeller]: added pbRunning and flFlags *@@changed V0.9.5 (2000-08-26) [umoeller]: now using PTHREADINFO *@@changed V0.9.7 (2000-12-18) [lafaix]: THRF_TRANSIENT support added *@@changed V0.9.9 (2001-02-06) [umoeller]: now returning TID *@@changed V0.9.9 (2001-03-07) [umoeller]: added pcszThreadName *@@changed V0.9.9 (2001-03-14) [umoeller]: added THRF_WAIT_EXPLICIT *@@changed V0.9.12 (2001-05-20) [umoeller]: changed pfRunning to ptidRunning *@@changed V0.9.16 (2001-10-28) [umoeller]: made ptidRunning volatile to prohibit compiler optimizations */ ULONG thrCreate(PTHREADINFO pti, // out: THREADINFO data PTHREADFUNC pfn, // in: _Optlink thread function volatile unsigned long *ptidRunning, // out: variable set to TID while thread is running; // ptr can be NULL const char *pcszThreadName, // in: thread name (for identification) ULONG flFlags, // in: THRF_* flags ULONG ulData) // in: user data to be stored in THREADINFO { ULONG ulrc = 0; // V0.9.9 (2001-02-06) [umoeller] // (2000-12-18) [lafaix] TRANSIENT if (flFlags & THRF_TRANSIENT) { if (pti == NULL) pti = (PTHREADINFO)malloc(sizeof(THREADINFO)); // cleaned up by thr_fntGeneric on exit } if (pti) { memset(pti, 0, sizeof(THREADINFO)); pti->cbStruct = sizeof(THREADINFO); pti->pThreadFunc = (PVOID)pfn; pti->ptidRunning = ptidRunning; pti->pcszThreadName = pcszThreadName; // V0.9.9 (2001-03-07) [umoeller] pti->flFlags = flFlags; pti->ulData = ulData; if (flFlags & (THRF_WAIT | THRF_WAIT_EXPLICIT)) // V0.9.9 (2001-03-14) [umoeller] // "Wait" flag set: create an event semaphore which // will be posted by thr_fntGeneric (THRF_WAIT only) if (DosCreateEventSem(NULL, // unnamed &pti->hevRunning, 0, // unshared FALSE) // not posted (reset) != NO_ERROR) { if (flFlags & THRF_TRANSIENT) free(pti); // stop right here pti = NULL; } if (pti) { pti->tid = _beginthread( // moved, V0.9.0 (hint: Rdiger Ihle) thr_fntGeneric, // changed V0.9.2 (2000-03-06) [umoeller] 0, // unused compatibility param 3*96000, // plenty of stack pti); // parameter passed to thread ulrc = pti->tid; if (pti->ptidRunning) // set "running" flag // V0.9.12 (2001-05-20) [umoeller] *(pti->ptidRunning) = pti->tid; if (ulrc) { if (LockThreadInfos()) { lstAppendItem(&G_llThreadInfos, pti); UnlockThreadInfos(); } if (flFlags & (THRF_WAIT | THRF_WAIT_EXPLICIT)) { // "Wait" flag set: wait on event semaphore DosWaitEventSem(pti->hevRunning, SEM_INDEFINITE_WAIT); } } } } return (ulrc); } /* *@@ thrRunSync: * runs the specified thread function synchronously. * * This is a wrapper around thrCreate. However, this * function does not return until the thread function * finishes. This creates a modal message loop on the * calling thread so that the PM message queue is not * blocked while the thread is running. Naturally this * implies that the calling thread has a message queue. * * As a special requirement, your thread function (pfn) * must post WM_USER to THREADINFO.hwndNotify just before * exiting. The mp1 value of WM_USER will then be returned * by this function. * *@@added V0.9.5 (2000-08-26) [umoeller] *@@changed V0.9.9 (2001-03-07) [umoeller]: added pcszThreadName *@@changed V0.9.16 (2001-12-08) [umoeller]: fixed hang with thrWait */ ULONG thrRunSync(HAB hab, // in: anchor block of calling thread PTHREADFUNC pfn, // in: passed to thrCreate const char *pcszThreadName, // in: passed to thrCreate ULONG ulData) // in: passed to thrCreate { ULONG ulrc = 0; QMSG qmsg; BOOL fQuit = FALSE; HWND hwndNotify; if (hwndNotify = WinCreateWindow(HWND_OBJECT, WC_BUTTON, (PSZ)"", 0, 0,0,0,0, 0, HWND_BOTTOM, 0, 0, NULL)) { THREADINFO ti = {0}; volatile unsigned long tidRunning = 0; thrCreate(&ti, pfn, &tidRunning, pcszThreadName, THRF_PMMSGQUEUE, ulData); ti.hwndNotify = hwndNotify; // create event sem to wait on V0.9.16 (2001-12-08) [umoeller] DosCreateEventSem(NULL, &ti.hevExitComplete, 0, FALSE); // not posted while (WinGetMsg(hab, &qmsg, 0, 0, 0)) { // current message for our object window? if ( (qmsg.hwnd == hwndNotify) && (qmsg.msg == WM_USER) ) { fQuit = TRUE; ulrc = (ULONG)qmsg.mp1; } WinDispatchMsg(hab, &qmsg); if (fQuit) break; } // we must wait for the thread to finish, or // otherwise THREADINFO is deleted from the stack // before the thread exits... will crash! // thrWait(&ti); // now using event sem V0.9.16 (2001-12-08) [umoeller] WinWaitEventSem(ti.hevExitComplete, 5000); DosCloseEventSem(ti.hevExitComplete); WinDestroyWindow(hwndNotify); } return (ulrc); } /* *@@ thrListThreads: * returns an array of THREADINFO structures * for all threads that have been started using * thrCreate (or thrRunSync). * * If no threads are running yet, this returns * NULL. * * Otherwise, this returns the pointer to the * first array item, and *pcThreads receives * the array item count (NOT the total array * size). The array is a copied snapshot of all * current THREADINFO's and must be free()'d * by the caller. * *@@added V0.9.9 (2001-03-07) [umoeller] */ PTHREADINFO thrListThreads(PULONG pcThreads) { PTHREADINFO pArray = 0; if (LockThreadInfos()) { PTHREADINFO pThis; PLISTNODE pNode; *pcThreads = lstCountItems(&G_llThreadInfos); if (pArray = (PTHREADINFO)malloc(*pcThreads * sizeof(THREADINFO))) { pThis = pArray; pNode = lstQueryFirstNode(&G_llThreadInfos); while (pNode) { memcpy(pThis, (PTHREADINFO)pNode->pItemData, sizeof(THREADINFO)); pThis++; pNode = pNode->pNext; } } UnlockThreadInfos(); } return (pArray); } /* *@@ thrFindThread: * attempts to find the thread with the specified * TID; if found, returns TRUE and copies its * THREADINFO into *pti. * *@@added V0.9.9 (2001-03-07) [umoeller] */ BOOL thrFindThread(PTHREADINFO pti, ULONG tid) { BOOL brc = FALSE; if (LockThreadInfos()) { PLISTNODE pNode = lstQueryFirstNode(&G_llThreadInfos); while (pNode) { PTHREADINFO ptiThis = (PTHREADINFO)pNode->pItemData; if (ptiThis->tid == tid) { memcpy(pti, ptiThis, sizeof(THREADINFO)); brc = TRUE; break; } pNode = pNode->pNext; } UnlockThreadInfos(); } return brc; } /* *@@ thrClose: * this functions sets the "fExit" flag in * THREADINFO to TRUE. * * The thread should monitor this flag * periodically and then terminate itself. */ BOOL thrClose(PTHREADINFO pti) { if (pti) { pti->fExit = TRUE; return (TRUE); } return (FALSE); } /* *@@ thrWait: * this function waits for a thread to end by calling * DosWaitThread. Note that this blocks the calling * thread, so only use this function when you're sure * the thread will actually terminate. * * Update V0.9.16: Do not use this with PM theads at * all. DosWaitThread can hang the system then. * * Returns FALSE if the thread wasn't running or TRUE * if it was and has terminated. * *@@changed V0.9.0 [umoeller]: now checking for whether pti->tid is still != 0 */ BOOL thrWait(PTHREADINFO pti) { if (pti) if (pti->tid) { DosWaitThread(&pti->tid, DCWW_WAIT); pti->tid = NULLHANDLE; return (TRUE); } return (FALSE); } /* *@@ thrFree: * this is a combination of thrClose and * thrWait, i.e. this func does not return * until the specified thread has ended. */ BOOL thrFree(PTHREADINFO pti) { if (pti->tid) { thrClose(pti); thrWait(pti); } return (TRUE); } /* *@@ thrKill: * just like thrFree, but the thread is * brutally killed, using DosKillThread. * * Note: DO NOT USE THIS. DosKillThread * cannot clean up the C runtime. In the * worst case, this hangs the system * because the runtime hasn't released * a semaphore or something like that. */ BOOL thrKill(PTHREADINFO pti) { if (pti->tid) { DosResumeThread(pti->tid); // this returns an error if the thread // is not suspended, but otherwise the // system might hang DosKillThread(pti->tid); } return (TRUE); } /* *@@ thrQueryID: * returns thread ID or NULLHANDLE if * the specified thread is not or no * longer running. */ TID thrQueryID(const THREADINFO* pti) { if (pti) return (pti->tid); return (NULLHANDLE); } /* *@@ thrQueryPriority: * returns the priority of the calling thread. * The low byte of the low word is a hexadecimal value * representing a rank (value 0 to 31) within a priority class. * Class values, found in the high byte of the low word, are * as follows: * -- 0x01 idle * -- 0x02 regular * -- 0x03 time-critical * -- 0x04 server * * Note: This cannot retrieve the priority of a * thread other than the one on which this function * is running. Use prc16QueryThreadInfo for that. */ ULONG thrQueryPriority(VOID) { PTIB ptib; PPIB ppib; if (DosGetInfoBlocks(&ptib, &ppib) == NO_ERROR) if (ptib) if (ptib->tib_ptib2) return (ptib->tib_ptib2->tib2_ulpri); return (0); }