diff --git a/src/plugins/shared/auxtools.cpp b/src/plugins/shared/auxtools.cpp index 3b69e499d..06a8df622 100644 --- a/src/plugins/shared/auxtools.cpp +++ b/src/plugins/shared/auxtools.cpp @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2023 Open Salamander Authors // SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED //**************************************************************************** // @@ -42,11 +43,11 @@ CThreadQueue::CThreadQueue(const char* queueName) CThreadQueue::~CThreadQueue() { - ClearFinishedThreads(); // neni treba sekce, uz by mel pouzivat jen jeden thread + ClearFinishedThreads(); // no need to use sections, only one thread should use this queue if (Continue != NULL) CloseHandle(Continue); if (Head != NULL) - TRACE_E("Some thread is still in " << QueueName << " queue!"); // po terminovani threadu, ktery ceka na (nebo zrovna terminuje) jiny thread z fronty, jinak by nemelo nastat... + TRACE_E("Some thread is still in " << QueueName << " queue!"); // after terminating thread which is waiting for another thread (or is terminating it) from this queue, otherwise it should not happen... } void CThreadQueue::ClearFinishedThreads() @@ -57,7 +58,7 @@ void CThreadQueue::ClearFinishedThreads() { DWORD ec; if (act->Locks == 0 && (!GetExitCodeThread(act->Thread, &ec) || ec != STILL_ACTIVE)) - { // tento thread neni zamceny + uz skoncil, vyhodime ho ze seznamu + { // this thread is not locked and it has finished, remove it from the list if (last != NULL) last->Next = act->Next; else @@ -76,10 +77,10 @@ void CThreadQueue::ClearFinishedThreads() BOOL CThreadQueue::Add(CThreadQueueItem* item) { - // nejprve vyhodime thready, ktere se jiz ukoncily + // first, remove finished threads ClearFinishedThreads(); - // pridame novy thread + // add new thread if (item != NULL) { item->Next = Head; @@ -93,7 +94,7 @@ BOOL CThreadQueue::FindAndLockItem(HANDLE thread) { CS.Enter(); - CThreadQueueItem* act = Head; // zkusime najit otevreny handle threadu + CThreadQueueItem* act = Head; // we wil try to find opened handle of the thread while (act != NULL) { if (act->Thread == thread) @@ -106,7 +107,7 @@ BOOL CThreadQueue::FindAndLockItem(HANDLE thread) CS.Leave(); - return act != NULL; // NULL = nenalezeno + return act != NULL; // NULL = not found } void CThreadQueue::UnlockItem(HANDLE thread, BOOL deleteIfUnlocked) @@ -114,7 +115,7 @@ void CThreadQueue::UnlockItem(HANDLE thread, BOOL deleteIfUnlocked) CS.Enter(); CThreadQueueItem* last = NULL; - CThreadQueueItem* act = Head; // zkusime najit otevreny handle threadu + CThreadQueueItem* act = Head; // we will try to find opened handle of the thread while (act != NULL) { if (act->Thread == thread) @@ -122,13 +123,13 @@ void CThreadQueue::UnlockItem(HANDLE thread, BOOL deleteIfUnlocked) last = act; act = act->Next; } - if (act != NULL) // always true (bylo zamknute, neslo smazat) + if (act != NULL) // always true (was locked, could not be deleted) { if (act->Locks <= 0) TRACE_E("CThreadQueue::UnlockItem(): thread has not locks!"); else { - if (--(act->Locks) == 0 && deleteIfUnlocked) // thread uz neni zamceny a mame ho smazat + if (--(act->Locks) == 0 && deleteIfUnlocked) // thread is not locked anymore and we should delete it { if (last != NULL) last->Next = act->Next; @@ -140,7 +141,7 @@ void CThreadQueue::UnlockItem(HANDLE thread, BOOL deleteIfUnlocked) } } else - TRACE_E("CThreadQueue::UnlockItem(): unable to find thread!"); // to nebyl zamknuty, ze je smazany? + TRACE_E("CThreadQueue::UnlockItem(): unable to find thread!"); // wasn't it locked, that it was deleted? CS.Leave(); } @@ -151,7 +152,7 @@ BOOL CThreadQueue::WaitForExit(HANDLE thread, int milliseconds) BOOL ret = TRUE; if (thread != NULL) { - if (FindAndLockItem(thread)) // handle threadu nalezen a uzamcen - muzeme na nej cekat, pak ho zrusime + if (FindAndLockItem(thread)) // thread handle found and locked - we can wait for it and then delete it { ret = WaitForSingleObject(thread, milliseconds) != WAIT_TIMEOUT; @@ -168,11 +169,10 @@ void CThreadQueue::KillThread(HANDLE thread, DWORD exitCode) CALL_STACK_MESSAGE2("CThreadQueue::KillThread(, %d)", exitCode); if (thread != NULL) { - if (FindAndLockItem(thread)) // handle threadu nalezen a uzamcen - muzeme ho terminovat, pak ho zrusime + if (FindAndLockItem(thread)) // thread handle found and locked - we can terminate it and then delete it { TerminateThread(thread, exitCode); - WaitForSingleObject(thread, INFINITE); // pockame az thread skutecne skonci, nekdy mu to dost trva - + WaitForSingleObject(thread, INFINITE); // waiting for thread to finish for sure, sometimes it takes a long time UnlockItem(thread, TRUE); } } @@ -188,7 +188,7 @@ BOOL CThreadQueue::KillAll(BOOL force, int waitTime, int forceWaitTime, DWORD ex CS.Enter(); - // vykillujeme vsechny thready, ktere nehodlaji koncit sami + // kill all threads which don't want to finish themselves CThreadQueueItem* prevItem = NULL; CThreadQueueItem* item = Head; while (item != NULL) @@ -196,11 +196,11 @@ BOOL CThreadQueue::KillAll(BOOL force, int waitTime, int forceWaitTime, DWORD ex BOOL leaveCS = FALSE; DWORD ec; if (GetExitCodeThread(item->Thread, &ec) && ec == STILL_ACTIVE) - { // thread jeste nejspis bezi + { // most probably, thread is still running DWORD t = GetTickCount() - ti; - if (w == INFINITE || t < w) // mame jeste cekat + if (w == INFINITE || t < w) // we should still wait { - // uvolnime frontu pro dalsi thready (aby se napr. dockaly ukonceni threadu z fronty a pak se sami ukoncily) + // release queue for other threads (so they can wait for thread from this queue to finish and then finish themselves) CS.Leave(); if (w == INFINITE || 50 < w - t) @@ -208,34 +208,34 @@ BOOL CThreadQueue::KillAll(BOOL force, int waitTime, int forceWaitTime, DWORD ex else { Sleep(w - t); - ti -= w; // pro priste vyradime test na cekani + ti -= w; // for next time, we will exclude the test for waiting } CS.Enter(); item = Head; prevItem = NULL; - continue; // zacneme pekne od zacatku (podminka cyklu se otestuje) + continue; // we will start from the beginning (condition of the cycle will be tested) } - if (force) // zabijeme ho + if (force) // we will kill it { TRACE_E("Thread has not ended itself, we must terminate it (" << QueueName << " queue)."); TerminateThread(item->Thread, exitCode); - WaitForSingleObject(item->Thread, INFINITE); // pockame az thread skutecne skonci, nekdy mu to dost trva - // pokud nejaky thread ceka na ukonceni prave zabiteho threadu, pustime pro nej na chvilku - // frontu, jinak zustane zasekly v UnlockItem() + WaitForSingleObject(item->Thread, INFINITE); // we will wait until thread really finishes, sometimes it takes a long time + // if some thread is waiting for the end of just killed thread, we will release queue for a while + // otherwise, it will be locked in UnlockItem() leaveCS = item->Locks > 0; } - else // bez 'force' jen ohlasime, ze jeste neco bezi + else // without 'force', we will only report that something is still running { TRACE_I("KillAll(): At least one thread is still running in " << QueueName << " queue."); - ClearFinishedThreads(); // jen tak pro prehlednost pri debugovani + ClearFinishedThreads(); // to make this more clear during debugging CS.Leave(); return FALSE; } } CThreadQueueItem* delItem = item; item = item->Next; - if (delItem->Locks == 0) // handle je mozne zavrit, polozku smazat + if (delItem->Locks == 0) // handle can be closed, item can be deleted { if (Head == delItem) Head = item; @@ -245,19 +245,19 @@ BOOL CThreadQueue::KillAll(BOOL force, int waitTime, int forceWaitTime, DWORD ex delete delItem; } else - prevItem = delItem; // handle musime nechat byt, takze i polozku + prevItem = delItem; // handle must be kept, so we will keep item too if (leaveCS) { - // uvolnime frontu pro dalsi thready (aby se napr. dockaly ukonceni threadu z fronty a pak se sami ukoncily) + // release queue for other threads (so they can wait for thread from this queue to finish and then finish themselves) CS.Leave(); - Sleep(50); // chvilka na prevzeti fronty a prip. dobeh threadu (nez ho pujdeme zabit jako vsechny ostatni) + Sleep(50); // a while for takeover of the queue and possible finishing of the thread (before we kill it as well as all other threads) CS.Enter(); item = Head; prevItem = NULL; - continue; // zacneme pekne od zacatku (podminka cyklu se otestuje) + continue; // we will start from the beginning (condition of the cycle will be tested) } } @@ -277,14 +277,14 @@ CThreadQueue::ThreadBase(void* param) { CThreadBaseData* d = (CThreadBaseData*)param; - // zaloha dat na stack ('d' prestane byt platne po 'Continue') + // backup data to stack ('d' stops to be valid after 'Continue') unsigned(WINAPI * threadBody)(void*) = d->Body; void* threadParam = d->Param; - SetEvent(d->Continue); // pustime dal hl. thread + SetEvent(d->Continue); // we let main thread to continue d = NULL; - // spustime nas thread + // executing our thread return SalamanderDebug->CallWithCallStack(threadBody, threadParam); } @@ -310,12 +310,12 @@ CThreadQueue::StartThread(unsigned(WINAPI* body)(void*), void* param, unsigned s data.Param = param; data.Continue = Continue; - // spustime thread, nepouzivame _beginthreadex(), protoze ten ma od VC2015 side-effect v podobe - // dalsiho loadu tohoto modulu (pluginu), ktery sice pri beznem ukonceni zase uvolni, - // ale kdyz pouzijeme TerminateThread(), zustane modul naloadeny az do ukonceni procesu - // Salamandera, pak se teprve spusti destruktory globalnich objektu a to muze vest - // k necekanym padum, protoze uz jsou vsechny pluginove rozhrani uvolnene (napr. - // SalamanderDebug) + // execute thread (we don't use _beginthreadex(), because it has side-effect in VC2015 + // in form of another load of this module (plugin), which is released when thread + // finishes normally, but when we use TerminateThread(), module stays loaded until + // Salamander process ends, then destructors of global objects are executed and + // it can lead to unexpected crashes, because all plugin interfaces are already + // released (e.g. SalamanderDebug) DWORD tid; HANDLE thread = CreateThread(NULL, stack_size, CThreadQueue::ThreadBase, &data, CREATE_SUSPENDED, &tid); if (thread == NULL) @@ -328,12 +328,12 @@ CThreadQueue::StartThread(unsigned(WINAPI* body)(void*), void* param, unsigned s } else { - // pridame thread do fronty threadu tohoto pluginu + // adding thread to the queue of this plugin if (!Add(new CThreadQueueItem(thread, tid))) { TRACE_E("Unable to add thread to the queue."); - TerminateThread(thread, 666); // je suspended, takze nebude v zadne kriticke sekci, atd. - WaitForSingleObject(thread, INFINITE); // pockame az thread skutecne skonci, nekdy mu to dost trva + TerminateThread(thread, 666); // it's suspended, so it will not be in any critical section, etc. + WaitForSingleObject(thread, INFINITE); // wait for thread to finish, sometimes it takes a long time CloseHandle(thread); CS.Leave(); @@ -341,7 +341,9 @@ CThreadQueue::StartThread(unsigned(WINAPI* body)(void*), void* param, unsigned s return NULL; } - // zapis dokud thread nebezi (zarucuje, ze uz nedobehl a jeho objekt neni dealokovany) + // save before thread is not running (it assures that thread is not finished and its object is not deallocated) + // Assign thread handle and ID before starting the thread to ensure safe initialization. + // This avoids race conditions and ensures thread resources are correctly tracked and not prematurely deallocated. if (threadHandle != NULL) *threadHandle = thread; if (threadID != NULL) @@ -350,7 +352,7 @@ CThreadQueue::StartThread(unsigned(WINAPI* body)(void*), void* param, unsigned s SalamanderDebug->TraceAttachThread(thread, tid); ResumeThread(thread); - WaitForSingleObject(Continue, INFINITE); // pockame na predani dat do CThreadQueue::ThreadBase + WaitForSingleObject(Continue, INFINITE); // wait for passing data to CThreadQueue::ThreadBase CS.Leave(); @@ -379,15 +381,15 @@ CThread::UniversalBody(void* param) CALL_STACK_MESSAGE2("CThread::UniversalBody(thread name = \"%s\")", thread->Name); SalamanderDebug->SetThreadNameInVCAndTrace(thread->Name); - unsigned ret = thread->Body(); // spusteni tela threadu + unsigned ret = thread->Body(); // executing body of the thread - delete thread; // likvidace objektu threadu + delete thread; // destroying object of the thread return ret; } HANDLE CThread::Create(CThreadQueue& queue, unsigned stack_size, DWORD* threadID) { - // POZOR: po volani StartThread() muze byt 'this' neplatny (proto je zapis do 'Thread' uvnitr) + // CAUTION: after calling StartThread(), 'this' can be invalid (that's why we write to 'Thread' inside) return queue.StartThread(UniversalBody, this, stack_size, &Thread, threadID); } diff --git a/src/plugins/shared/auxtools.h b/src/plugins/shared/auxtools.h index 2efd3b05f..70f5a4075 100644 --- a/src/plugins/shared/auxtools.h +++ b/src/plugins/shared/auxtools.h @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2023 Open Salamander Authors // SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED //**************************************************************************** // @@ -19,8 +20,8 @@ struct CThreadQueueItem { HANDLE Thread; - DWORD ThreadID; // jen pro ladici ucely (nalezeni threadu v seznamu threadu v debuggeru) - int Locks; // pocet zamku, je-li > 0 nesmime zavrit 'Thread' + DWORD ThreadID; // for debugging purposes only (finding thread in debugger's thread list) + int Locks; // number of locks, if > 0 we must not close 'Thread' CThreadQueueItem* Next; CThreadQueueItem(HANDLE thread, DWORD tid) @@ -35,11 +36,11 @@ struct CThreadQueueItem class CThreadQueue { protected: - const char* QueueName; // jmeno fronty (jen pro debugovaci ucely) + const char* QueueName; // name of queue (for debugging purposes only) CThreadQueueItem* Head; - HANDLE Continue; // musime pockat na predani dat do startovaneho threadu + HANDLE Continue; // we have to wait for data to be passed to the thread - struct CCS // pristup z vice threadu -> nutna synchronizace + struct CCS // access from multiple threads -> synchronization needed { CRITICAL_SECTION cs; @@ -51,98 +52,105 @@ class CThreadQueue } CS; public: - CThreadQueue(const char* queueName /* napr. "DemoPlug Viewers" */); + CThreadQueue(const char* queueName /* e.g. "DemoPlug Viewers" */); ~CThreadQueue(); - // spusti funkci 'body' s parametrem 'param' v nove vytvorenem threadu se zasobnikem - // o velikosti 'stack_size' (0 = default); vraci handle threadu nebo NULL pri chybe, - // zaroven vysledek zapise pred spustenim threadu (resume) i do 'threadHandle' - // (neni-li NULL), vraceny handle threadu pouzivat jen na testy na NULL a pro volani - // metod CThreadQueue: WaitForExit() a KillThread(); zavreni handlu threadu zajistuje - // tento objekt fronty - // POZOR: -thread se muze spustit se zpozdenim az po navratu ze StartThread() - // (je-li 'param' ukazatel na strukturu ulozenou na zasobniku, je nutne - // synchronizovat predani dat z 'param' - hl. thread musi pockat - // na prevzeti dat novym threadem) - // -vraceny handle threadu jiz muze byt zavreny pokud thread dobehne pred - // navratem ze StartThread() a z jineho threadu se vola StartThread() nebo - // KillAll() - // mozne volat z libovolneho threadu + // executes fuction 'body' with parameter 'param' in a new thread with stack size + // 'stack_size' (0 = default); returns handle of the thread or NULL on error; + // the result is also written before the thread is started (resume) to 'threadHandle' + // (if not NULL); the returned handle should be used only for testing for NULL and + // for calling methods CThreadQueue: WaitForExit() and KillThread(); closing the + // handle of the thread is done by the queue object + // CAUTION: -the thread can be started with delay after returning from StartThread() + // (if 'param' is a pointer to a structure stored on the stack, it is + // necessary to synchronize the passing of data from 'param' - the main + // thread must wait for the new thread to take over the data) + // -the returned handle of the thread can be already closed if the thread + // finishes before returning from StartThread() and StartThread() or + // KillAll() is called from another thread + // can be called from any thread HANDLE StartThread(unsigned(WINAPI* body)(void*), void* param, unsigned stack_size = 0, HANDLE* threadHandle = NULL, DWORD* threadID = NULL); - // ceka na ukonceni threadu z teto fronty; 'thread' je handle threadu, ktery jiz muze - // byt i zavreny (zavira tento objekt pri volani StartThread a KillAll); pokud se - // docka ukonceni threadu, vyradi thread z fronty a zavre jeho handle + // waits for the thread from this queue to finish; 'thread' is the handle of the + // thread which can be already closed (this object closes it when calling StartThread + // and KillAll); if the thread finishes, it is removed from the queue and its handle + // is closed BOOL WaitForExit(HANDLE thread, int milliseconds = INFINITE); - // zabije thread z teto fronty (pres TerminateThread()); 'thread' je handle threadu, - // ktery jiz muze byt i zavreny (zavira tento objekt pri volani StartThread a KillAll); - // pokud thread najde, zabije ho, vyradi z fronty a zavre jeho handle (objekt threadu - // se nedealokuje, protoze jeho stav je neznamy, mozna nekonzistentni) + // kills the thread from this queue (via TerminateThread()); 'thread' is the handle + // of the thread which can be already closed (this object closes it when calling + // StartThread and KillAll); if the thread is found, it is killed, removed from the + // queue and its handle is closed (the thread object is not deallocated because its + // state is unknown, possibly inconsistent) void KillThread(HANDLE thread, DWORD exitCode = 666); - // overi, ze vsechny thready skoncily; je-li 'force' TRUE a nejaky thread jeste bezi, - // ceka 'forceWaitTime' (v ms) na ukonceni vsech threadu, pak bezici thready zabije - // (jejich objekty se nedealokuji, protoze jejich stav je neznamy, mozna nekonzistentni); - // vraci TRUE, jsou-li vsechny thready ukoncene, pri 'force' TRUE vzdy vraci TRUE; - // je-li 'force' FALSE a nejaky thread jeste bezi, ceka 'waitTime' (v ms) na ukonceni - // vsech threadu, pokud pak jeste stale neco bezi, vraci FALSE; cas INFINITE = neomezene - // dlouhe cekani - // mozne volat z libovolneho threadu + // verifies that all threads have finished; if 'force' is TRUE and some thread is + // still running, it waits 'forceWaitTime' (in ms) for all threads to finish, then + // kills the running threads (their objects are not deallocated because their state + // is unknown, possibly inconsistent); returns TRUE if all threads have finished, + // always returns TRUE if 'force' is TRUE; if 'force' is FALSE and some thread is + // still running, it waits 'waitTime' (in ms) for all threads to finish, if some + // thread is still running after that, it returns FALSE; time INFINITE = wait forever + // can be called from any thread BOOL KillAll(BOOL force, int waitTime = 1000, int forceWaitTime = 200, DWORD exitCode = 666); -protected: // vnitrni nesynchronizovane metody - BOOL Add(CThreadQueueItem* item); // prida polozku do fronty, vraci uspech - BOOL FindAndLockItem(HANDLE thread); // najde polozku pro 'thread' ve fronte a zamkne ji - void UnlockItem(HANDLE thread, BOOL deleteIfUnlocked); // odemkne polozku pro 'thread' ve fronte, pripadne ji smaze - void ClearFinishedThreads(); // vyradi z fronty thready, ktere jiz dobehly - static DWORD WINAPI ThreadBase(void* param); // univerzalni body threadu +protected: // internal unsynchronized methods + BOOL Add(CThreadQueueItem* item); // adds item to the queue, returns success + BOOL FindAndLockItem(HANDLE thread); // adds item for 'thread' to the queue and locks it + void UnlockItem(HANDLE thread, BOOL deleteIfUnlocked); // unlocks item for 'thread' in the queue, possibly deletes it + void ClearFinishedThreads(); // removes finished threads from the queue + static DWORD WINAPI ThreadBase(void* param); // universal thread body }; // // **************************************************************************** // CThread // -// POZOR: musi se alokovat (neni mozne mit CThread jen na stacku); dealokuje se sam -// jen v pripade uspesneho vytvoreni threadu metodou Create() +// CAUTION: must be allocated (it is not possible to have CThread only on stack); +// it is deallocated automatically only if the thread is successfully +// created by Create() class CThread { public: - // handle threadu (NULL = thread jeste nebezi/nebezel), POZOR: po ukonceni threadu se - // sam zavira (je neplatny), navic tento objekt uz je dealokovany + // thread handle (NULL = thread is not running yet/any more), CAUTION: after the thread + // is finished, it is automatically closed (it is invalid), moreover this object is + // already deallocated HANDLE Thread; protected: - char Name[101]; // buffer pro jmeno threadu (pouziva se v TRACE a CALL-STACK pro identifikaci threadu) - // POZOR: pokud budou data threadu obsahovat odkazy na stack nebo jine docasne objekty, - // je potreba zajistit, aby se s temito odkazy pracovalo jen po dobu jejich platnosti + char Name[101]; // a buffer for the thread name (used in TRACE and CALL-STACK to identify the thread) + // CAUTION: if the thread data contains references to the stack or other temporary + // objects, it is necessary to ensure that these references are used only + // for the duration of their validity public: CThread(const char* name = NULL); - virtual ~CThread() {} // aby se spravne volaly destruktory potomku - - // vytvoreni (start) threadu ve fronte threadu 'queue'; 'stack_size' je velikost zasobniku - // noveho threadu v bytech (0 = default); vraci handle noveho threadu nebo NULL pri chybe; - // zavreni handlu zajistuje objekt 'queue'; pokud se podari vytvorit thread, je tento objekt - // dealokovan pri ukonceni threadu, pri chybe spousteni je dealokace objektu na volajicim - // POZOR: bez pridani synchronizace muze thread dobehnout jeste pred navratem z Create() -> - // ukazatel "this" je proto po uspesnem volani Create() nutne povazovat za neplatny, - // to same plati pro vraceny handle threadu (pouzivat jen na testy na NULL a pro volani - // metod CThreadQueue: WaitForExit() a KillThread()) - // mozne volat z libovolneho threadu + virtual ~CThread() {} // so that the destructors of the descendants are called correctly + + // creating (start) of a thread in the queue 'queue'; 'stack_size' is the size of the stack + // of the new thread in bytes (0 = default); returns handle of the new thread or NULL on error; + // closing the handle is done by the 'queue' object; if the thread is successfully created, + // this object is deallocated when the thread is finished, if the thread creation fails, + // the object is deallocated by the caller + // CAUTION: without adding synchronization, the thread can finish even before returning from + // Create() -> the pointer "this" must therefore be considered invalid after a + // successful call to Create(), the same applies to the returned thread handle + // (use only for NULL tests and for calling methods CThreadQueue: WaitForExit() + // and KillThread()) + // can be called from any thread HANDLE Create(CThreadQueue& queue, unsigned stack_size = 0, DWORD* threadID = NULL); - // vraci 'Thread' viz vyse + // returns 'Thread' (see above) HANDLE GetHandle() { return Thread; } - // vraci jmeno threadu + // returns thread name const char* GetName() { return Name; } - // tato metoda obsahuje telo threadu + // this method contains the body of the thread virtual unsigned Body() = 0; protected: - static unsigned WINAPI UniversalBody(void* param); // pomocna metoda pro spousteni threadu + static unsigned WINAPI UniversalBody(void* param); // helper method to execude thread };