// EterLib CFileLoaderThread / CThread / Mutex over the Win32Crt kernel-object shims. // 40250 leaves CResourceManager::ms_loadingThread un-Created (the Create call is commented out), so the // live path is a static, zero-initialised loader whose Request finds no semaphore and whose Fetch // never yields; a Created loader reads each request through CEterPackManager on its own thread. #include "EterLib/StdAfx.h" #include "EterLib/FileLoaderThread.h" #include "EterLib/Thread.h" #include "EterLib/Mutex.h" #include "EterLib/ResourceManager.h" #include "EterPack/EterPackManager.h" #include #include #include #include #include #include namespace { int failures = 0; void check(bool ok, const char * name) { if (!ok) { std::printf("FAIL %s\n", name); ++failures; } } CFileLoaderThread s_idleLoader; // like CResourceManager::ms_loadingThread: static storage, never Created CFileLoaderThread s_loader; std::atomic s_counter{0}; class CCountThread : public CThread { public: UINT m_setup = 1; void * m_arg = NULL; protected: UINT Setup() override { return m_setup; } UINT Execute(void * arg) override { m_arg = arg; ++s_counter; return 7; } }; } int main() { // Semaphore: counts, the maximum, timeouts. { HANDLE sem = CreateSemaphore(NULL, 1, 2, NULL); check(sem != NULL, "CreateSemaphore"); check(!CreateSemaphore(NULL, 3, 2, NULL), "CreateSemaphore initial > maximum"); check(WaitForSingleObject(sem, 0) == WAIT_OBJECT_0, "semaphore initial count"); check(WaitForSingleObject(sem, 10) == WAIT_TIMEOUT, "semaphore empty times out"); LONG previous = -1; check(ReleaseSemaphore(sem, 2, &previous) && previous == 0, "ReleaseSemaphore previous count"); check(!ReleaseSemaphore(sem, 1, NULL), "ReleaseSemaphore past maximum fails"); check(WaitForSingleObject(sem, 0) == WAIT_OBJECT_0 && WaitForSingleObject(sem, 0) == WAIT_OBJECT_0 && WaitForSingleObject(sem, 0) == WAIT_TIMEOUT, "semaphore count kept after failed release"); check(!ReleaseSemaphore(NULL, 1, NULL), "ReleaseSemaphore NULL handle"); check(WaitForSingleObject(NULL, 0) == WAIT_FAILED, "WaitForSingleObject NULL handle"); std::thread waker([sem] { std::this_thread::sleep_for(std::chrono::milliseconds(20)); ReleaseSemaphore(sem, 1, NULL); }); check(WaitForSingleObject(sem, INFINITE) == WAIT_OBJECT_0, "semaphore wakes a waiter"); waker.join(); check(CloseHandle(sem) && !CloseHandle(NULL), "CloseHandle"); } // Mutex is recursive (CRITICAL_SECTION); CThread runs Setup then Execute on a new thread. { Mutex mutex; mutex.Lock(); mutex.Lock(); mutex.Unlock(); mutex.Unlock(); CCountThread thread; int arg = 0; check(thread.Create(&arg) != 0, "CThread Create"); for (int i = 0; i < 200 && s_counter.load() == 0; ++i) Sleep(5); check(s_counter.load() == 1 && thread.m_arg == &arg, "CThread Execute with arg"); CCountThread noSetup; noSetup.m_setup = 0; check(noSetup.Create(NULL) != 0, "CThread Create without Setup"); Sleep(30); check(s_counter.load() == 1, "CThread skips Execute when Setup fails"); } char dir[] = "/tmp/mt_fileloader_XXXXXX"; check(mkdtemp(dir) != NULL, "mkdtemp"); std::string path = std::string(dir) + "/a.txt"; FILE * fp = std::fopen(path.c_str(), "wb"); std::fwrite("hello", 1, 5, fp); std::fclose(fp); CEterPackManager packs; // SEARCH_FILE_FIRST: requests read the file from disk g_iLoadingDelayTime = 0; // 40250 live path: never Created. { std::string name = path; s_idleLoader.Request(name); CFileLoaderThread::TData * pData = NULL; check(!s_idleLoader.Fetch(&pData), "un-Created loader never completes a request"); s_idleLoader.Shutdown(); } // Created: requests complete in order with the file contents; a missing file completes empty. { check(s_loader.Create(NULL) != 0, "CFileLoaderThread Create"); Sleep(20); // Setup (the semaphore) runs on the loader thread std::string name = path; std::string missing = std::string(dir) + "/missing.txt"; s_loader.Request(name); s_loader.Request(missing); CFileLoaderThread::TData * got[2] = {}; int count = 0; for (int i = 0; i < 400 && count < 2; ++i) { if (!s_loader.Fetch(&got[count])) Sleep(5); else ++count; } check(count == 2, "both requests complete"); if (count == 2) { check(got[0]->stFileName == path && got[0]->dwSize == 5 && std::memcmp(got[0]->pvBuf, "hello", 5) == 0, "request loaded through CEterPackManager"); check(got[1]->dwSize == 0 && got[1]->pvBuf == NULL, "missing file completes empty"); for (CFileLoaderThread::TData * pData : got) { delete [] ((char *) pData->pvBuf); delete pData; } } s_loader.Shutdown(); } unlink(path.c_str()); rmdir(dir); if (!failures) std::printf("ok\n"); return failures ? 1 : 0; }