, CacheFile( cache_file )
, HasChanged( false )
, MinTimeBetweenResolves( min_time_between_resolves )
+ , InShutdown( false )
+ , ForceSave( false )
+ , UseCacheFile( cache_file != DoNotUseCacheFile )
{
// load cache from file
load_from_cachefile();
- // schedule next save
- (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) );
- SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
- boost::asio::placeholders::error ) );
+ // Only schedule the timer if we're actually using a cache file.
+ // For caches that don't use a file (like the global fixture in tests),
+ // there's no need to schedule saves and it avoids issues during shutdown.
+ if (UseCacheFile)
+ {
+ (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) );
+ try
+ {
+ SaveTimer.async_wait( bind( &DnsCache::schedule_save, shared_from_this(),
+ boost::asio::placeholders::error ) );
+ }
+ catch (const boost::bad_weak_ptr&)
+ {
+ // Object is not owned by shared_ptr (e.g., stack-allocated in tests)
+ // Use raw this pointer instead
+ SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
+ boost::asio::placeholders::error ) );
+ }
+ }
}
DnsCache::~DnsCache()
{
- GlobalLogger.info() << "DnsCache: being destructed";
+ // CRITICAL: Set InShutdown FIRST, before any logging or other operations.
+ // This ensures that any timer callbacks that might be triggered by other
+ // objects' destructors will see InShutdown=true and return early.
+ InShutdown = true;
- // save one last time without re-scheduling the next save
- save_to_cachefile();
+ // cancel save timer - pending callbacks will see InShutdown=true and return
+ boost::system::error_code ec;
+ SaveTimer.cancel(ec);
- // cancel save timer
- SaveTimer.cancel();
+ GlobalLogger.info() << "DnsCache: being destructed, InShutdown=" << InShutdown;
+
+ // save for caches that use a file - force save even though InShutdown is true
+ // Use the boolean flag to avoid comparing with potentially corrupted static string
+ if (UseCacheFile)
+ {
+ GlobalLogger.info() << "DnsCache: Will save cache file";
+ ForceSave = true;
+ save_to_cachefile();
+ }
+ else
+ {
+ GlobalLogger.info() << "DnsCache: Skipping save, not using cache file";
+ }
}
void DnsCache::schedule_save(const boost::system::error_code &error)
{
+ // Skip if we're in shutdown mode - the destructor is running
+ // This MUST be checked FIRST to avoid accessing corrupted memory
+ // during destruction
+ if (InShutdown)
+ {
+ return;
+ }
+ // Skip if this cache doesn't use a file (like the global fixture in tests)
+ // Use boolean flag to avoid comparing with potentially corrupted static string
+ if (!UseCacheFile)
+ {
+ return;
+ }
if ( error == boost::asio::error::operation_aborted ) // cancelled
{
- GlobalLogger.info() << "DnsCache: SaveTimer was cancelled "
- << "--> no save and no re-schedule of saving!";
return;
}
else if (error)
{
- GlobalLogger.info() << "DnsCache: Received error " << error
- << " in schedule_save "
- << "--> no save now but re-schedule saving";
+ // schedule next save
+ (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) );
+ // Use try-catch for shared_from_this to support stack-allocated objects
+ try
+ {
+ SaveTimer.async_wait( bind( &DnsCache::schedule_save, shared_from_this(),
+ boost::asio::placeholders::error ) );
+ }
+ catch (const boost::bad_weak_ptr&)
+ {
+ // Object is not owned by shared_ptr (e.g., stack-allocated in tests)
+ // Don't reschedule - callback won't run after this point anyway
+ }
}
else
+ {
save_to_cachefile();
- // schedule next save
- (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) );
- SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
- boost::asio::placeholders::error ) );
+ // schedule next save
+ (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) );
+ // Use try-catch for shared_from_this to support stack-allocated objects
+ try
+ {
+ SaveTimer.async_wait( bind( &DnsCache::schedule_save, shared_from_this(),
+ boost::asio::placeholders::error ) );
+ }
+ catch (const boost::bad_weak_ptr&)
+ {
+ // Object is not owned by shared_ptr (e.g., stack-allocated in tests)
+ // Don't reschedule - callback won't run after this point anyway
+ }
+ }
}
void DnsCache::save_to_cachefile()
{
+ // Skip entirely for caches that don't use a file (like the global fixture)
+ // Use boolean flag to avoid comparing with potentially corrupted static string
+ if (!UseCacheFile)
+ {
+ GlobalLogger.info() << "DnsCache::save_to_cachefile SKIPPED, UseCacheFile=false";
+ return;
+ }
+ GlobalLogger.info() << "DnsCache::save_to_cachefile called, InShutdown=" << InShutdown << ", ForceSave=" << ForceSave << ", CacheFile=" << CacheFile;
+ if (InShutdown && !ForceSave)
+ {
+ GlobalLogger.info() << "DnsCache: skip saving during shutdown";
+ return;
+ }
if (!HasChanged)
GlobalLogger.info() << "DnsCache: skip saving because has not changed";
else if (CacheFile.empty())
{
GlobalLogger.warning() << "DnsCache: Saving failed: " << exc.what();
}
+ catch (...)
+ {
+ // Catch any other exceptions including assertion errors from
+ // Boost serialization during program shutdown
+ GlobalLogger.warning() << "DnsCache: Saving failed due to unknown error";
+ }
}
}
if (CacheFile.empty())
GlobalLogger.info()
<< "DnsCache: cannot load because cache file name is empty!";
- else if (CacheFile == DoNotUseCacheFile)
+ else if (!UseCacheFile)
GlobalLogger.info() << "DnsCache: configured not to use cache file";
else if ( !I2n::file_exists(CacheFile) )
GlobalLogger.warning() << "DnsCache: cannot load because cache file "
*/
void DnsCache::remove_old_entries()
{
+ // Skip if we're in shutdown mode and not forcing save (for destructor)
+ // or not using a file
+ // This prevents issues during program exit
+ // Use boolean flag to avoid comparing with potentially corrupted static string
+ if (!UseCacheFile)
+ {
+ GlobalLogger.debug() << "DnsCache::remove_old_entries skipped, UseCacheFile=false";
+ return;
+ }
+ if (InShutdown && !ForceSave)
+ {
+ GlobalLogger.debug() << "DnsCache::remove_old_entries skipped due to InShutdown=" << InShutdown << ", ForceSave=" << ForceSave;
+ return;
+ }
+
+ GlobalLogger.debug() << "DnsCache::remove_old_entries executing for " << CacheFile;
+
boost::posix_time::ptime thresh
= boost::posix_time::second_clock::universal_time()
- boost::gregorian::days( Config::CACHE_REMOVE_OUTDATED_DAYS );