Rebrand hush3 to DragonX and share RandomX dataset across mining threads
Minimal rebrand (see compliant-rebrand branch for full rebrand): - Rename binaries: hushd/hush-cli/hush-tx → dragonxd/dragonx-cli/dragonx-tx - Default to DRAGONX chain params without -ac_* flags (randomx, blocktime=36, private=1) - Update configure.ac: AC_INIT([DragonX],[1.0.0]) - Update client version string and user-agent to /DragonX:1.0.0/ - Add chainparams.cpp with DRAGONX network parameters - Update build.sh, miner.cpp, pow.cpp for DragonX - Add bootstrap-dragonx.sh utility script - Update .gitignore for release directory Share single RandomX dataset across all mining threads: - Add RandomXDatasetManager with readers-writer lock, reducing RAM from ~2GB per thread to ~2GB total plus ~2MB per thread for the VM scratchpad - Add LogProcessMemory() diagnostic helper for Linux and Windows
This commit is contained in:
385
src/miner.cpp
385
src/miner.cpp
@@ -23,6 +23,7 @@
|
||||
#include "pow/tromp/equi_miner.h"
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include "amount.h"
|
||||
#include "chainparams.h"
|
||||
#include "consensus/consensus.h"
|
||||
@@ -51,6 +52,7 @@
|
||||
#include "transaction_builder.h"
|
||||
#include "sodium.h"
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/thread/shared_mutex.hpp>
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
#ifdef ENABLE_MINING
|
||||
#include <functional>
|
||||
@@ -1011,8 +1013,213 @@ enum RandomXSolverCancelCheck
|
||||
Reason2
|
||||
};
|
||||
|
||||
int GetRandomXInterval() { return GetArg("-ac_randomx_interval",1024); }
|
||||
int GetRandomXBlockLag() { return GetArg("-ac_randomx_lag", 64); }
|
||||
int GetRandomXInterval();
|
||||
int GetRandomXBlockLag();
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
static void LogProcessMemory(const char* label) {
|
||||
// Use K32GetProcessMemoryInfo from kernel32.dll (available on Win7+)
|
||||
// to avoid linking psapi.lib
|
||||
typedef struct {
|
||||
DWORD cb;
|
||||
DWORD PageFaultCount;
|
||||
SIZE_T PeakWorkingSetSize;
|
||||
SIZE_T WorkingSetSize;
|
||||
SIZE_T QuotaPeakPagedPoolUsage;
|
||||
SIZE_T QuotaPagedPoolUsage;
|
||||
SIZE_T QuotaPeakNonPagedPoolUsage;
|
||||
SIZE_T QuotaNonPagedPoolUsage;
|
||||
SIZE_T PagefileUsage;
|
||||
SIZE_T PeakPagefileUsage;
|
||||
SIZE_T PrivateUsage;
|
||||
} PMC_EX;
|
||||
typedef BOOL (WINAPI *PFN)(HANDLE, PMC_EX*, DWORD);
|
||||
static PFN pfn = (PFN)GetProcAddress(GetModuleHandleA("kernel32.dll"), "K32GetProcessMemoryInfo");
|
||||
if (pfn) {
|
||||
PMC_EX pmc = {};
|
||||
pmc.cb = sizeof(pmc);
|
||||
if (pfn(GetCurrentProcess(), &pmc, sizeof(pmc))) {
|
||||
LogPrintf("MemDiag [%s]: WorkingSet=%.1fMB, PrivateUsage=%.1fMB, PagefileUsage=%.1fMB\n",
|
||||
label,
|
||||
pmc.WorkingSetSize / (1024.0 * 1024.0),
|
||||
pmc.PrivateUsage / (1024.0 * 1024.0),
|
||||
pmc.PagefileUsage / (1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void LogProcessMemory(const char* label) {
|
||||
// Linux: read /proc/self/status
|
||||
FILE *f = fopen("/proc/self/status", "r");
|
||||
if (f) {
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
if (strncmp(line, "VmRSS:", 6) == 0 || strncmp(line, "VmSize:", 7) == 0) {
|
||||
// Remove newline
|
||||
line[strlen(line)-1] = '\0';
|
||||
LogPrintf("MemDiag [%s]: %s\n", label, line);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Shared RandomX dataset manager — all miner threads share a single ~2GB dataset
|
||||
// instead of each allocating their own. The dataset is read-only after initialization
|
||||
// and RandomX explicitly supports multiple VMs sharing one dataset.
|
||||
struct RandomXDatasetManager {
|
||||
randomx_flags flags;
|
||||
randomx_cache *cache;
|
||||
randomx_dataset *dataset;
|
||||
unsigned long datasetItemCount;
|
||||
std::string currentKey;
|
||||
std::mutex mtx; // protects Init/Shutdown/CreateVM
|
||||
boost::shared_mutex datasetMtx; // readers-writer lock: shared for hashing, exclusive for rebuild
|
||||
bool initialized;
|
||||
|
||||
RandomXDatasetManager() : flags(randomx_get_flags()), cache(nullptr), dataset(nullptr),
|
||||
datasetItemCount(0), initialized(false) {}
|
||||
|
||||
bool Init() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (initialized) return true;
|
||||
|
||||
flags |= RANDOMX_FLAG_FULL_MEM;
|
||||
LogPrintf("RandomXDatasetManager: flags=0x%x (JIT=%d, HARD_AES=%d, FULL_MEM=%d, LARGE_PAGES=%d)\n",
|
||||
(int)flags,
|
||||
!!(flags & RANDOMX_FLAG_JIT), !!(flags & RANDOMX_FLAG_HARD_AES),
|
||||
!!(flags & RANDOMX_FLAG_FULL_MEM), !!(flags & RANDOMX_FLAG_LARGE_PAGES));
|
||||
|
||||
LogProcessMemory("before cache alloc");
|
||||
|
||||
cache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES | RANDOMX_FLAG_SECURE);
|
||||
if (cache == nullptr) {
|
||||
LogPrintf("RandomXDatasetManager: cache alloc failed with large pages, trying without...\n");
|
||||
cache = randomx_alloc_cache(flags | RANDOMX_FLAG_SECURE);
|
||||
if (cache == nullptr) {
|
||||
LogPrintf("RandomXDatasetManager: cache alloc failed with secure, trying basic...\n");
|
||||
cache = randomx_alloc_cache(flags);
|
||||
if (cache == nullptr) {
|
||||
LogPrintf("RandomXDatasetManager: cannot allocate cache!\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
LogProcessMemory("after cache alloc");
|
||||
|
||||
// Try to allocate dataset with large pages first for better performance
|
||||
dataset = randomx_alloc_dataset(flags | RANDOMX_FLAG_LARGE_PAGES);
|
||||
if (dataset == nullptr) {
|
||||
LogPrintf("RandomXDatasetManager: dataset alloc failed with large pages, trying without...\n");
|
||||
dataset = randomx_alloc_dataset(flags);
|
||||
if (dataset == nullptr) {
|
||||
LogPrintf("RandomXDatasetManager: cannot allocate dataset!\n");
|
||||
randomx_release_cache(cache);
|
||||
cache = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
datasetItemCount = randomx_dataset_item_count();
|
||||
initialized = true;
|
||||
LogProcessMemory("after dataset alloc");
|
||||
// Log the actual memory addresses to help diagnose sharing issues
|
||||
uint8_t *datasetMemory = (uint8_t*)randomx_get_dataset_memory(dataset);
|
||||
size_t datasetSize = datasetItemCount * RANDOMX_DATASET_ITEM_SIZE;
|
||||
LogPrintf("RandomXDatasetManager: allocated shared dataset:\n");
|
||||
LogPrintf(" - Dataset struct at: %p\n", (void*)dataset);
|
||||
LogPrintf(" - Dataset memory at: %p (size: %.2f GB)\n", (void*)datasetMemory, datasetSize / (1024.0 * 1024.0 * 1024.0));
|
||||
LogPrintf(" - Items: %lu, Item size: %d bytes\n", datasetItemCount, RANDOMX_DATASET_ITEM_SIZE);
|
||||
LogPrintf(" - Expected total process memory: ~%.2f GB + ~2MB per mining thread\n", datasetSize / (1024.0 * 1024.0 * 1024.0));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Initialize cache with a key and rebuild the dataset.
|
||||
// Thread-safe: acquires exclusive lock so all hashing threads must finish first.
|
||||
void UpdateKey(const void *key, size_t keySize) {
|
||||
std::string newKey((const char*)key, keySize);
|
||||
|
||||
// Fast check with shared lock — skip if key hasn't changed
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> readLock(datasetMtx);
|
||||
if (newKey == currentKey) return; // already up to date
|
||||
}
|
||||
|
||||
// Acquire exclusive lock — blocks until all hashing threads release their shared locks
|
||||
boost::unique_lock<boost::shared_mutex> writeLock(datasetMtx);
|
||||
// Double-check after acquiring exclusive lock (another thread may have rebuilt first)
|
||||
if (newKey == currentKey) return;
|
||||
|
||||
LogPrintf("RandomXDatasetManager: updating key (size=%lu)\n", keySize);
|
||||
randomx_init_cache(cache, key, keySize);
|
||||
currentKey = newKey;
|
||||
|
||||
// Rebuild dataset using all available CPU threads
|
||||
const int initThreadCount = std::thread::hardware_concurrency();
|
||||
if (initThreadCount > 1) {
|
||||
std::vector<std::thread> threads;
|
||||
uint32_t startItem = 0;
|
||||
const auto perThread = datasetItemCount / initThreadCount;
|
||||
const auto remainder = datasetItemCount % initThreadCount;
|
||||
for (int i = 0; i < initThreadCount; ++i) {
|
||||
const auto count = perThread + (i == initThreadCount - 1 ? remainder : 0);
|
||||
threads.push_back(std::thread(&randomx_init_dataset, dataset, cache, startItem, count));
|
||||
startItem += count;
|
||||
}
|
||||
for (unsigned i = 0; i < threads.size(); ++i) {
|
||||
threads[i].join();
|
||||
}
|
||||
} else {
|
||||
randomx_init_dataset(dataset, cache, 0, datasetItemCount);
|
||||
}
|
||||
LogPrintf("RandomXDatasetManager: dataset rebuilt\n");
|
||||
LogProcessMemory("after dataset init");
|
||||
}
|
||||
|
||||
// Creates a per-thread VM using the shared dataset.
|
||||
// Caller must hold a shared lock on datasetMtx.
|
||||
// The VM itself is small (~2MB scratchpad + ~84KB JIT code) — the ~2GB dataset is shared via pointer.
|
||||
// VMs should be created ONCE per thread and reused across blocks to avoid
|
||||
// heap fragmentation on Windows (repeated 2MB alloc/free causes address-space bloat).
|
||||
randomx_vm *CreateVM() {
|
||||
static std::atomic<int> vmCount{0};
|
||||
LogProcessMemory("before CreateVM");
|
||||
randomx_vm *vm = randomx_create_vm(flags, nullptr, dataset);
|
||||
if (vm != nullptr) {
|
||||
int id = ++vmCount;
|
||||
uint8_t *datasetMemory = (uint8_t*)randomx_get_dataset_memory(dataset);
|
||||
LogPrintf("RandomXDatasetManager: VM #%d created — VM at %p, shared dataset at %p\n",
|
||||
id, (void*)vm, (void*)datasetMemory);
|
||||
LogPrintf(" Per-thread overhead: ~2MB scratchpad + ~84KB JIT (dataset NOT copied)\n");
|
||||
LogProcessMemory("after CreateVM");
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (dataset != nullptr) {
|
||||
randomx_release_dataset(dataset);
|
||||
dataset = nullptr;
|
||||
}
|
||||
if (cache != nullptr) {
|
||||
randomx_release_cache(cache);
|
||||
cache = nullptr;
|
||||
}
|
||||
initialized = false;
|
||||
currentKey.clear();
|
||||
LogPrintf("RandomXDatasetManager: shutdown complete\n");
|
||||
}
|
||||
|
||||
~RandomXDatasetManager() {
|
||||
Shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
// Global shared dataset manager, created by GenerateBitcoins before spawning miner threads
|
||||
static RandomXDatasetManager *g_rxDatasetManager = nullptr;
|
||||
|
||||
#ifdef ENABLE_WALLET
|
||||
void static RandomXMiner(CWallet *pwallet)
|
||||
@@ -1050,33 +1257,12 @@ void static RandomXMiner()
|
||||
);
|
||||
miningTimer.start();
|
||||
|
||||
randomx_flags flags = randomx_get_flags();
|
||||
flags |= RANDOMX_FLAG_FULL_MEM;
|
||||
randomx_cache *randomxCache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES | RANDOMX_FLAG_SECURE );
|
||||
if (randomxCache == NULL) {
|
||||
LogPrintf("RandomX cache is null, trying without large pages...\n");
|
||||
randomxCache = randomx_alloc_cache(flags | RANDOMX_FLAG_SECURE);
|
||||
if (randomxCache == NULL) {
|
||||
LogPrintf("RandomX cache is null, trying without secure...\n");
|
||||
}
|
||||
randomxCache = randomx_alloc_cache(flags);
|
||||
if (randomxCache == NULL) {
|
||||
LogPrintf("RandomX cache is null, cannot mine!\n");
|
||||
}
|
||||
}
|
||||
|
||||
rxdebug("%s: created randomx flags + cache\n");
|
||||
randomx_dataset *randomxDataset = randomx_alloc_dataset(flags);
|
||||
rxdebug("%s: created dataset\n");
|
||||
|
||||
if( randomxDataset == nullptr) {
|
||||
LogPrintf("%s: allocating randomx dataset failed!\n", __func__);
|
||||
// Use the shared dataset manager — no per-thread dataset allocation
|
||||
if (g_rxDatasetManager == nullptr || !g_rxDatasetManager->initialized) {
|
||||
LogPrintf("HushRandomXMiner: shared dataset manager not initialized, aborting!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto datasetItemCount = randomx_dataset_item_count();
|
||||
rxdebug("%s: dataset items=%lu\n", datasetItemCount);
|
||||
|
||||
char randomxHash[RANDOMX_HASH_SIZE];
|
||||
rxdebug("%s: created randomxHash of size %d\n", RANDOMX_HASH_SIZE);
|
||||
char randomxKey[82]; // randomx spec says keysize of >60 bytes is implementation-specific
|
||||
@@ -1147,48 +1333,37 @@ void static RandomXMiner()
|
||||
|
||||
// fprintf(stderr,"RandomXMiner: using initial key with interval=%d and lag=%d\n", randomxInterval, randomxBlockLag);
|
||||
rxdebug("%s: using initial key, interval=%d, lag=%d, Mining_height=%u\n", randomxInterval, randomxBlockLag, Mining_height);
|
||||
// Use the initial key at the start of the chain, until the first key block
|
||||
// Update the shared dataset key — only one thread will actually rebuild,
|
||||
// others will see the key is already current and skip.
|
||||
if( (Mining_height) < randomxInterval + randomxBlockLag) {
|
||||
randomx_init_cache(randomxCache, &randomxKey, sizeof randomxKey);
|
||||
rxdebug("%s: initialized cache with initial key\n");
|
||||
g_rxDatasetManager->UpdateKey(randomxKey, strlen(randomxKey));
|
||||
rxdebug("%s: updated shared dataset with initial key\n");
|
||||
} else {
|
||||
rxdebug("%s: calculating keyHeight with randomxInterval=%d\n", randomxInterval);
|
||||
// At heights between intervals, we use the same block key and wait randomxBlockLag blocks until changing
|
||||
const int keyHeight = ((Mining_height - randomxBlockLag) / randomxInterval) * randomxInterval;
|
||||
uint256 randomxBlockKey = chainActive[keyHeight]->GetBlockHash();
|
||||
|
||||
randomx_init_cache(randomxCache, &randomxBlockKey, sizeof randomxBlockKey);
|
||||
rxdebug("%s: initialized cache with keyHeight=%d, randomxBlockKey=%s\n", keyHeight, randomxBlockKey.ToString().c_str());
|
||||
g_rxDatasetManager->UpdateKey(&randomxBlockKey, sizeof randomxBlockKey);
|
||||
rxdebug("%s: updated shared dataset with keyHeight=%d, randomxBlockKey=%s\n", keyHeight, randomxBlockKey.ToString().c_str());
|
||||
}
|
||||
|
||||
const int initThreadCount = std::thread::hardware_concurrency();
|
||||
if(initThreadCount > 1) {
|
||||
rxdebug("%s: initializing dataset with %d threads\n", initThreadCount);
|
||||
std::vector<std::thread> threads;
|
||||
uint32_t startItem = 0;
|
||||
const auto perThread = datasetItemCount / initThreadCount;
|
||||
const auto remainder = datasetItemCount % initThreadCount;
|
||||
for (int i = 0; i < initThreadCount; ++i) {
|
||||
const auto count = perThread + (i == initThreadCount - 1 ? remainder : 0);
|
||||
threads.push_back(std::thread(&randomx_init_dataset, randomxDataset, randomxCache, startItem, count));
|
||||
startItem += count;
|
||||
// Create a per-thread VM once and reuse across blocks.
|
||||
// The VM just stores a pointer to the shared dataset — the pointer
|
||||
// remains valid across key changes since UpdateKey rebuilds the dataset
|
||||
// contents in-place without reallocating. Reusing the VM avoids
|
||||
// repeated 2MB scratchpad + 84KB JIT alloc/free churn that causes
|
||||
// Windows heap fragmentation and apparent memory growth per thread.
|
||||
if (myVM == nullptr) {
|
||||
// First iteration: acquire shared lock briefly to create VM
|
||||
boost::shared_lock<boost::shared_mutex> initLock(g_rxDatasetManager->datasetMtx);
|
||||
myVM = g_rxDatasetManager->CreateVM();
|
||||
if (myVM == nullptr) {
|
||||
LogPrintf("RandomXMiner: Cannot create RandomX VM, aborting!\n");
|
||||
return;
|
||||
}
|
||||
for (unsigned i = 0; i < threads.size(); ++i) {
|
||||
threads[i].join();
|
||||
}
|
||||
threads.clear();
|
||||
} else {
|
||||
rxdebug("%s: initializing dataset with 1 thread\n");
|
||||
randomx_init_dataset(randomxDataset, randomxCache, 0, datasetItemCount);
|
||||
}
|
||||
|
||||
rxdebug("%s: dataset initialized\n");
|
||||
|
||||
myVM = randomx_create_vm(flags, nullptr, randomxDataset);
|
||||
if(myVM == NULL) {
|
||||
LogPrintf("RandomXMiner: Cannot create RandomX VM, aborting!\n");
|
||||
return;
|
||||
}
|
||||
// Acquire shared lock to prevent dataset rebuild while we're hashing
|
||||
boost::shared_lock<boost::shared_mutex> datasetLock(g_rxDatasetManager->datasetMtx);
|
||||
//fprintf(stderr,"RandomXMiner: Mining_start=%u\n", Mining_start);
|
||||
#ifdef ENABLE_WALLET
|
||||
CBlockTemplate *ptr = CreateNewBlockWithKey(reservekey, pindexPrev->GetHeight()+1, gpucount, 0);
|
||||
@@ -1268,16 +1443,17 @@ void static RandomXMiner()
|
||||
arith_uint256 hashTarget;
|
||||
hashTarget = HASHTarget;
|
||||
|
||||
CRandomXInput rxInput(pblocktemplate->block);
|
||||
CDataStream randomxInput(SER_NETWORK, PROTOCOL_VERSION);
|
||||
// Use the current block as randomx input
|
||||
randomxInput << pblocktemplate->block;
|
||||
// Serialize block header without nSolution but with nNonce for deterministic RandomX input
|
||||
randomxInput << rxInput;
|
||||
|
||||
// std::cerr << "RandomXMiner: randomxInput=" << HexStr(randomxInput) << "\n";
|
||||
// fprintf(stderr,"RandomXMiner: created randomxKey=%s , randomxInput.size=%lu\n", randomxKey, randomxInput.size() ); //randomxInput);
|
||||
rxdebug("%s: randomxKey=%s randomxInput=%s\n", randomxKey, HexStr(randomxInput).c_str());
|
||||
|
||||
rxdebug("%s: calculating randomx hash\n");
|
||||
randomx_calculate_hash(myVM, &randomxInput, sizeof randomxInput, randomxHash);
|
||||
randomx_calculate_hash(myVM, &randomxInput[0], randomxInput.size(), randomxHash);
|
||||
rxdebug("%s: calculated randomx hash\n");
|
||||
|
||||
rxdebug("%s: randomxHash=");
|
||||
@@ -1324,14 +1500,28 @@ void static RandomXMiner()
|
||||
|
||||
CValidationState state;
|
||||
//{ LOCK(cs_main);
|
||||
if ( !TestBlockValidity(state,B, chainActive.LastTip(), true, false))
|
||||
// Skip RandomX re-validation during TestBlockValidity — we already
|
||||
// computed the correct hash, and re-verifying allocates ~256MB which
|
||||
// can trigger the OOM killer on memory-constrained systems.
|
||||
SetSkipRandomXValidation(true);
|
||||
bool fValid = TestBlockValidity(state,B, chainActive.LastTip(), true, false);
|
||||
SetSkipRandomXValidation(false);
|
||||
if ( !fValid )
|
||||
{
|
||||
h = UintToArith256(B.GetHash());
|
||||
fprintf(stderr,"RandomXMiner: Invalid randomx block mined, try again ");
|
||||
fprintf(stderr,"RandomXMiner: TestBlockValidity FAILED at ht.%d nNonce=%s hash=",
|
||||
Mining_height, pblock->nNonce.ToString().c_str());
|
||||
for (z=31; z>=0; z--)
|
||||
fprintf(stderr,"%02x",((uint8_t *)&h)[z]);
|
||||
gotinvalid = 1;
|
||||
fprintf(stderr," nSolution.size=%lu\n", B.nSolution.size());
|
||||
// Dump nSolution hex for comparison with validator
|
||||
fprintf(stderr,"RandomXMiner: nSolution=");
|
||||
for (unsigned i = 0; i < B.nSolution.size(); i++)
|
||||
fprintf(stderr,"%02x", B.nSolution[i]);
|
||||
fprintf(stderr,"\n");
|
||||
LogPrintf("RandomXMiner: TestBlockValidity FAILED at ht.%d, gotinvalid=1, state=%s\n",
|
||||
Mining_height, state.GetRejectReason());
|
||||
gotinvalid = 1;
|
||||
return(false);
|
||||
}
|
||||
//}
|
||||
@@ -1399,21 +1589,24 @@ void static RandomXMiner()
|
||||
pblock->nBits = savebits;
|
||||
}
|
||||
|
||||
rxdebug("%s: going to destroy rx VM\n");
|
||||
randomx_destroy_vm(myVM);
|
||||
rxdebug("%s: destroyed VM\n");
|
||||
// Release shared lock so UpdateKey can acquire exclusive lock for dataset rebuild
|
||||
// VM is kept alive — its dataset pointer remains valid across rebuilds
|
||||
datasetLock.unlock();
|
||||
}
|
||||
|
||||
} catch (const boost::thread_interrupted&) {
|
||||
miningTimer.stop();
|
||||
c.disconnect();
|
||||
|
||||
randomx_destroy_vm(myVM);
|
||||
LogPrintf("%s: destroyed vm via thread interrupt\n", __func__);
|
||||
randomx_release_dataset(randomxDataset);
|
||||
rxdebug("%s: released dataset via thread interrupt\n");
|
||||
randomx_release_cache(randomxCache);
|
||||
rxdebug("%s: released cache via thread interrupt\n");
|
||||
if (myVM != nullptr) {
|
||||
randomx_destroy_vm(myVM);
|
||||
myVM = nullptr;
|
||||
LogPrintf("%s: destroyed vm via thread interrupt\n", __func__);
|
||||
} else {
|
||||
LogPrintf("%s: WARNING myVM already null in thread interrupt handler, skipping destroy (would double-free)\n", __func__);
|
||||
fprintf(stderr, "%s: WARNING myVM already null in thread interrupt, would have double-freed!\n", __func__);
|
||||
}
|
||||
// Dataset and cache are owned by g_rxDatasetManager — do NOT release here
|
||||
|
||||
LogPrintf("HushRandomXMiner terminated\n");
|
||||
throw;
|
||||
@@ -1422,20 +1615,21 @@ void static RandomXMiner()
|
||||
c.disconnect();
|
||||
fprintf(stderr,"RandomXMiner: runtime error: %s\n", e.what());
|
||||
|
||||
randomx_destroy_vm(myVM);
|
||||
LogPrintf("%s: destroyed vm because of error\n", __func__);
|
||||
randomx_release_dataset(randomxDataset);
|
||||
rxdebug("%s: released dataset because of error\n");
|
||||
randomx_release_cache(randomxCache);
|
||||
rxdebug("%s: released cache because of error\n");
|
||||
if (myVM != nullptr) {
|
||||
randomx_destroy_vm(myVM);
|
||||
myVM = nullptr;
|
||||
LogPrintf("%s: destroyed vm because of error\n", __func__);
|
||||
}
|
||||
// Dataset and cache are owned by g_rxDatasetManager — do NOT release here
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
randomx_release_dataset(randomxDataset);
|
||||
rxdebug("%s: released dataset in normal exit\n");
|
||||
randomx_release_cache(randomxCache);
|
||||
rxdebug("%s: released cache in normal exit\n");
|
||||
// Only destroy per-thread VM, dataset/cache are shared
|
||||
if (myVM != nullptr) {
|
||||
randomx_destroy_vm(myVM);
|
||||
myVM = nullptr;
|
||||
}
|
||||
miningTimer.stop();
|
||||
c.disconnect();
|
||||
}
|
||||
@@ -1879,8 +2073,18 @@ void static BitcoinMiner()
|
||||
if (minerThreads != NULL)
|
||||
{
|
||||
minerThreads->interrupt_all();
|
||||
// Wait for all miner threads to fully terminate before destroying shared resources
|
||||
minerThreads->join_all();
|
||||
delete minerThreads;
|
||||
minerThreads = NULL;
|
||||
|
||||
// Shutdown shared RandomX dataset manager after all threads are done
|
||||
if (g_rxDatasetManager != nullptr) {
|
||||
g_rxDatasetManager->Shutdown();
|
||||
delete g_rxDatasetManager;
|
||||
g_rxDatasetManager = nullptr;
|
||||
LogPrintf("%s: destroyed shared RandomX dataset manager\n", __func__);
|
||||
}
|
||||
}
|
||||
|
||||
if(fDebug)
|
||||
@@ -1895,6 +2099,21 @@ void static BitcoinMiner()
|
||||
|
||||
minerThreads = new boost::thread_group();
|
||||
|
||||
// Initialize shared RandomX dataset manager before spawning miner threads
|
||||
if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX) {
|
||||
g_rxDatasetManager = new RandomXDatasetManager();
|
||||
if (!g_rxDatasetManager->Init()) {
|
||||
LogPrintf("%s: FATAL - Failed to initialize shared RandomX dataset manager\n", __func__);
|
||||
fprintf(stderr, "%s: FATAL - Failed to initialize shared RandomX dataset manager\n", __func__);
|
||||
delete g_rxDatasetManager;
|
||||
g_rxDatasetManager = nullptr;
|
||||
delete minerThreads;
|
||||
minerThreads = NULL;
|
||||
return;
|
||||
}
|
||||
LogPrintf("%s: shared RandomX dataset manager initialized\n", __func__);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nThreads; i++) {
|
||||
#ifdef ENABLE_WALLET
|
||||
if ( ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH ) {
|
||||
|
||||
Reference in New Issue
Block a user