Adds CreateSemaphore/ReleaseSemaphore/WaitForSingleObject/CloseHandle/ SetThreadPriority/_beginthreadex to Win32Crt; port.file_loader_thread covers the shims, CThread and both the un-Created (40250 live) and Created loader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
160 lines
4.7 KiB
C++
160 lines
4.7 KiB
C++
// 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 <atomic>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <thread>
|
|
#include <unistd.h>
|
|
|
|
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<int> 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;
|
|
}
|