I keep seeing an access violation in the MySQL dll. It appears to be random, not 100% of the time, but happens frequently. Originally I thought that I was allowing the session to be destroyed before the results, but I created a small wrapper to ensure that results are always destroyed before the session.
Here's the snippet of my most relevant code. I'm not sure that this is the function that's failing (unfortunately putting breakpoints in changes the timing enough that it significantly reduces the chances of this happening.) You can see that I create move-construct the session into a shared_ptr, construct the Statement for later execution, pass the shared_ptr to query which executes the statement on a thread in my thread pool, and pack the results into a shared pointer (since results don't seem to be copyable but are movable) and a copy of the shared_ptr to the session into a simple result wrapper. The wrapper exists to be copyable and to ensure that the last shared_ptr to the results is released before the last shared_ptr to the session, ensuring that the session's lifespan is longer than the results. I'm not sure all this should strictly speaking be necessary, but I've been chasing this null pointer exception for a while and since the null pointer being dereferenced is the session, I sort of assumed that I was allowing the session to be destroyed before the results. Maybe there's a bug in this code, but it ought to guarantee that the session is not destroyed before the results, and indeed when I look at it in my debugger my session shared pointer is valid.
template< typename Result >
class result_ptr
{
public:
using result_t = Result;
private:
std::shared_ptr< result_t > Result_;
std::shared_ptr< msx::Session > Session_;
public:
result_ptr( ) { }
result_ptr( result_t && result, decltype( Session_ ) session ) :
Result_( std::make_shared< result_t >( std::move( result ) ) ),
Session_( session )
{ }
~result_ptr( )
{
Result_.reset( );
Session_.reset( );
}
result_ptr( result_ptr && ) = default;
result_ptr( result_ptr const & ) = default;
result_ptr & operator=( result_ptr && ) = default;
result_ptr & operator=( result_ptr const & ) = default;
auto & operator*( ) const noexcept
{
return *Result_;
}
auto operator->( ) const noexcept
{
return Result_.get( );
}
};
template< typename Exec >
inline auto query( ContextId const &, std::shared_ptr< msx::Session > session, Exec statement ) -> ppl::task< result_ptr< decltype( statement.execute( ) ) > >
{
return ppl::create_task(
[session, s = std::make_shared< Exec >( std::move( statement ) )]( )
{
try
{
return result_ptr< decltype( s->execute( ) ) >( s->execute( ), session );
}
catch( std::exception const & ex )
{
so::cout << ex.what( ) << "\n";
throw;
}
} );
};
ppl::task< std::vector< TaskId > > MySQLQueue::getReadyTasks(
ContextId const & contextId,
unsigned int const limit )
{
using result_type = decltype( u::query( contextId, std::shared_ptr< msx::Session >( ), Client_.getSession( ).getSchema( "" ).getTable( "" ).select( ) ) )::result_type;
auto session = std::make_shared< msx::Session >( Client_.getSession( ) );
return u::query(
contextId,
session,
session->getSchema( SchemaName_ ).getTable( TableName_
).select(
info::column::select[info::column::_id]
).where(
"state = :state AND processAfter < :processAfter"
).limit(
limit
).bind( std::map< std::string, std::string >{
{ "state", Info::State::String[Info::State::Created] },
{ "processAfter", utility::time_point_to_string( clock::now( ) ) } }
)
).then(
[]( result_type const & res )
{
std::vector< TaskId > rv;
rv.reserve( res->count( ) );
std::transform(
res->begin( ),
res->end( ),
std::back_inserter( rv ),
[]( msx::Row && row ) -> TaskId
{
return std::to_string( row[0].get< uint64_t >( ) );
} );
return rv;
} );
}
I cannot figure out why this is resulting in an access violation. The AV error message when the exception is trapped is:
Exception thrown: read access violation.
this->m_session was nullptr.
You can see from the call stack that the AV is actually buried pretty deep in the MySQL dll:
mysqlcppconn8-2-vs14.dll!cdk::mysqlx::Reply::has_results() Line 132 C++
mysqlcppconn8-2-vs14.dll!cdk::mysqlx::Reply::discard() Line 102 C++
mysqlcppconn8-2-vs14.dll!cdk::mysqlx::Reply::~Reply() Line 59 C++
mysqlcppconn8-2-vs14.dll!cdk::Reply::~Reply() C++
mysqlcppconn8-2-vs14.dll!cdk::Reply::`scalar deleting destructor'(unsigned int) C++
mysqlcppconn8-2-vs14.dll!mysqlx::abi2::r0::common::Result_impl::~Result_impl() Line 181 C++
mysqlcppconn8-2-vs14.dll!mysqlx::abi2::r0::common::Result_impl::`scalar deleting destructor'(unsigned int) C++
mysqlcppconn8-2-vs14.dll!mysqlx::abi2::r0::internal::Result_detail::~Result_detail() Line 493 C++
cgrmerge.exe!mysqlx::abi2::r0::internal::Result_common<mysqlx::abi2::r0::internal::Result_detail>::~Result_common<mysqlx::abi2::r0::internal::Result_detail>() C++
cgrmerge.exe!mysqlx::abi2::r0::Result::~Result() C++
cgrmerge.exe!mysqlx::abi2::r0::Result::`scalar deleting destructor'(unsigned int) C++
cgrmerge.exe!std::_Destroy_in_place<mysqlx::abi2::r0::Result>(mysqlx::abi2::r0::Result & _Obj) Line 322 C++
cgrmerge.exe!std::_Ref_count_obj2<mysqlx::abi2::r0::Result>::_Destroy() Line 1506 C++
cgrmerge.exe!std::_Ref_count_base::_Decref() Line 652 C++
cgrmerge.exe!std::_Ptr_base<mysqlx::abi2::r0::Result>::_Decref() Line 883 C++
> cgrmerge.exe!std::shared_ptr<mysqlx::abi2::r0::Result>::~shared_ptr<mysqlx::abi2::r0::Result>() Line 1133 C++
cgrmerge.exe!std::shared_ptr<mysqlx::abi2::r0::Result>::reset() Line 1178 C++
cgrmerge.exe!utility::result_ptr<mysqlx::abi2::r0::Result>::~result_ptr<mysqlx::abi2::r0::Result>() Line 60 C++
cgrmerge.exe!pplx::details::_ResultHolder<utility::result_ptr<mysqlx::abi2::r0::Result>>::~_ResultHolder<utility::result_ptr<mysqlx::abi2::r0::Result>>() C++
cgrmerge.exe!pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>::~_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>() Line 2432 C++
cgrmerge.exe!pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>::`scalar deleting destructor'(unsigned int) C++
cgrmerge.exe!std::_Destroy_in_place<pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>>(pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>> & _Obj) Line 322 C++
cgrmerge.exe!std::_Ref_count_obj2<pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>>::_Destroy() Line 1506 C++
cgrmerge.exe!std::_Ref_count_base::_Decref() Line 652 C++
cgrmerge.exe!std::_Ptr_base<pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>>::_Decref() Line 883 C++
cgrmerge.exe!std::shared_ptr<pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>>::~shared_ptr<pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>>() Line 1133 C++
cgrmerge.exe!pplx::task<utility::result_ptr<mysqlx::abi2::r0::Result>>::_ContinuationTaskHandle<utility::result_ptr<mysqlx::abi2::r0::Result>,utility::result_ptr<mysqlx::abi2::r0::Result>,`ScheduledTask::MySQLQueue::recover'::`2'::<lambda_1>,std::integral_constant<bool,0>,pplx::details::_TypeSelectorAsyncTask>::~_ContinuationTaskHandle<utility::result_ptr<mysqlx::abi2::r0::Result>,utility::result_ptr<mysqlx::abi2::r0::Result>,`ScheduledTask::MySQLQueue::recover'::`2'::<lambda_1>,std::integral_constant<bool,0>,pplx::details::_TypeSelectorAsyncTask>() Line 3871 C++
cgrmerge.exe!pplx::task<utility::result_ptr<mysqlx::abi2::r0::Result>>::_ContinuationTaskHandle<utility::result_ptr<mysqlx::abi2::r0::Result>,utility::result_ptr<mysqlx::abi2::r0::Result>,`ScheduledTask::MySQLQueue::recover'::`2'::<lambda_1>,std::integral_constant<bool,0>,pplx::details::_TypeSelectorAsyncTask>::`scalar deleting destructor'(unsigned int) C++
cgrmerge.exe!pplx::details::_AutoDeleter<pplx::details::_TaskProcHandle>::~_AutoDeleter<pplx::details::_TaskProcHandle>() Line 99 C++
cgrmerge.exe!pplx::details::_TaskProcHandle::_RunChoreBridge(void * _Parameter) Line 117 C++
cpprest141d_2_10.dll!pplx::details::_Scheduler_Param::DefaultWorkCallback(struct _TP_CALLBACK_INSTANCE *,void *,struct _TP_WORK *) Unknown
ntdll.dll!TppWorkpExecuteCallback() Unknown
ntdll.dll!TppWorkerThread() Unknown
kernel32.dll!BaseThreadInitThunk?() Unknown
ntdll.dll!RtlUserThreadStart?() Unknown
Any help would be greatly appreciated.
Here's the snippet of my most relevant code. I'm not sure that this is the function that's failing (unfortunately putting breakpoints in changes the timing enough that it significantly reduces the chances of this happening.) You can see that I create move-construct the session into a shared_ptr, construct the Statement for later execution, pass the shared_ptr to query which executes the statement on a thread in my thread pool, and pack the results into a shared pointer (since results don't seem to be copyable but are movable) and a copy of the shared_ptr to the session into a simple result wrapper. The wrapper exists to be copyable and to ensure that the last shared_ptr to the results is released before the last shared_ptr to the session, ensuring that the session's lifespan is longer than the results. I'm not sure all this should strictly speaking be necessary, but I've been chasing this null pointer exception for a while and since the null pointer being dereferenced is the session, I sort of assumed that I was allowing the session to be destroyed before the results. Maybe there's a bug in this code, but it ought to guarantee that the session is not destroyed before the results, and indeed when I look at it in my debugger my session shared pointer is valid.
template< typename Result >
class result_ptr
{
public:
using result_t = Result;
private:
std::shared_ptr< result_t > Result_;
std::shared_ptr< msx::Session > Session_;
public:
result_ptr( ) { }
result_ptr( result_t && result, decltype( Session_ ) session ) :
Result_( std::make_shared< result_t >( std::move( result ) ) ),
Session_( session )
{ }
~result_ptr( )
{
Result_.reset( );
Session_.reset( );
}
result_ptr( result_ptr && ) = default;
result_ptr( result_ptr const & ) = default;
result_ptr & operator=( result_ptr && ) = default;
result_ptr & operator=( result_ptr const & ) = default;
auto & operator*( ) const noexcept
{
return *Result_;
}
auto operator->( ) const noexcept
{
return Result_.get( );
}
};
template< typename Exec >
inline auto query( ContextId const &, std::shared_ptr< msx::Session > session, Exec statement ) -> ppl::task< result_ptr< decltype( statement.execute( ) ) > >
{
return ppl::create_task(
[session, s = std::make_shared< Exec >( std::move( statement ) )]( )
{
try
{
return result_ptr< decltype( s->execute( ) ) >( s->execute( ), session );
}
catch( std::exception const & ex )
{
so::cout << ex.what( ) << "\n";
throw;
}
} );
};
ppl::task< std::vector< TaskId > > MySQLQueue::getReadyTasks(
ContextId const & contextId,
unsigned int const limit )
{
using result_type = decltype( u::query( contextId, std::shared_ptr< msx::Session >( ), Client_.getSession( ).getSchema( "" ).getTable( "" ).select( ) ) )::result_type;
auto session = std::make_shared< msx::Session >( Client_.getSession( ) );
return u::query(
contextId,
session,
session->getSchema( SchemaName_ ).getTable( TableName_
).select(
info::column::select[info::column::_id]
).where(
"state = :state AND processAfter < :processAfter"
).limit(
limit
).bind( std::map< std::string, std::string >{
{ "state", Info::State::String[Info::State::Created] },
{ "processAfter", utility::time_point_to_string( clock::now( ) ) } }
)
).then(
[]( result_type const & res )
{
std::vector< TaskId > rv;
rv.reserve( res->count( ) );
std::transform(
res->begin( ),
res->end( ),
std::back_inserter( rv ),
[]( msx::Row && row ) -> TaskId
{
return std::to_string( row[0].get< uint64_t >( ) );
} );
return rv;
} );
}
I cannot figure out why this is resulting in an access violation. The AV error message when the exception is trapped is:
Exception thrown: read access violation.
this->m_session was nullptr.
You can see from the call stack that the AV is actually buried pretty deep in the MySQL dll:
mysqlcppconn8-2-vs14.dll!cdk::mysqlx::Reply::has_results() Line 132 C++
mysqlcppconn8-2-vs14.dll!cdk::mysqlx::Reply::discard() Line 102 C++
mysqlcppconn8-2-vs14.dll!cdk::mysqlx::Reply::~Reply() Line 59 C++
mysqlcppconn8-2-vs14.dll!cdk::Reply::~Reply() C++
mysqlcppconn8-2-vs14.dll!cdk::Reply::`scalar deleting destructor'(unsigned int) C++
mysqlcppconn8-2-vs14.dll!mysqlx::abi2::r0::common::Result_impl::~Result_impl() Line 181 C++
mysqlcppconn8-2-vs14.dll!mysqlx::abi2::r0::common::Result_impl::`scalar deleting destructor'(unsigned int) C++
mysqlcppconn8-2-vs14.dll!mysqlx::abi2::r0::internal::Result_detail::~Result_detail() Line 493 C++
cgrmerge.exe!mysqlx::abi2::r0::internal::Result_common<mysqlx::abi2::r0::internal::Result_detail>::~Result_common<mysqlx::abi2::r0::internal::Result_detail>() C++
cgrmerge.exe!mysqlx::abi2::r0::Result::~Result() C++
cgrmerge.exe!mysqlx::abi2::r0::Result::`scalar deleting destructor'(unsigned int) C++
cgrmerge.exe!std::_Destroy_in_place<mysqlx::abi2::r0::Result>(mysqlx::abi2::r0::Result & _Obj) Line 322 C++
cgrmerge.exe!std::_Ref_count_obj2<mysqlx::abi2::r0::Result>::_Destroy() Line 1506 C++
cgrmerge.exe!std::_Ref_count_base::_Decref() Line 652 C++
cgrmerge.exe!std::_Ptr_base<mysqlx::abi2::r0::Result>::_Decref() Line 883 C++
> cgrmerge.exe!std::shared_ptr<mysqlx::abi2::r0::Result>::~shared_ptr<mysqlx::abi2::r0::Result>() Line 1133 C++
cgrmerge.exe!std::shared_ptr<mysqlx::abi2::r0::Result>::reset() Line 1178 C++
cgrmerge.exe!utility::result_ptr<mysqlx::abi2::r0::Result>::~result_ptr<mysqlx::abi2::r0::Result>() Line 60 C++
cgrmerge.exe!pplx::details::_ResultHolder<utility::result_ptr<mysqlx::abi2::r0::Result>>::~_ResultHolder<utility::result_ptr<mysqlx::abi2::r0::Result>>() C++
cgrmerge.exe!pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>::~_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>() Line 2432 C++
cgrmerge.exe!pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>::`scalar deleting destructor'(unsigned int) C++
cgrmerge.exe!std::_Destroy_in_place<pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>>(pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>> & _Obj) Line 322 C++
cgrmerge.exe!std::_Ref_count_obj2<pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>>::_Destroy() Line 1506 C++
cgrmerge.exe!std::_Ref_count_base::_Decref() Line 652 C++
cgrmerge.exe!std::_Ptr_base<pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>>::_Decref() Line 883 C++
cgrmerge.exe!std::shared_ptr<pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>>::~shared_ptr<pplx::details::_Task_impl<utility::result_ptr<mysqlx::abi2::r0::Result>>>() Line 1133 C++
cgrmerge.exe!pplx::task<utility::result_ptr<mysqlx::abi2::r0::Result>>::_ContinuationTaskHandle<utility::result_ptr<mysqlx::abi2::r0::Result>,utility::result_ptr<mysqlx::abi2::r0::Result>,`ScheduledTask::MySQLQueue::recover'::`2'::<lambda_1>,std::integral_constant<bool,0>,pplx::details::_TypeSelectorAsyncTask>::~_ContinuationTaskHandle<utility::result_ptr<mysqlx::abi2::r0::Result>,utility::result_ptr<mysqlx::abi2::r0::Result>,`ScheduledTask::MySQLQueue::recover'::`2'::<lambda_1>,std::integral_constant<bool,0>,pplx::details::_TypeSelectorAsyncTask>() Line 3871 C++
cgrmerge.exe!pplx::task<utility::result_ptr<mysqlx::abi2::r0::Result>>::_ContinuationTaskHandle<utility::result_ptr<mysqlx::abi2::r0::Result>,utility::result_ptr<mysqlx::abi2::r0::Result>,`ScheduledTask::MySQLQueue::recover'::`2'::<lambda_1>,std::integral_constant<bool,0>,pplx::details::_TypeSelectorAsyncTask>::`scalar deleting destructor'(unsigned int) C++
cgrmerge.exe!pplx::details::_AutoDeleter<pplx::details::_TaskProcHandle>::~_AutoDeleter<pplx::details::_TaskProcHandle>() Line 99 C++
cgrmerge.exe!pplx::details::_TaskProcHandle::_RunChoreBridge(void * _Parameter) Line 117 C++
cpprest141d_2_10.dll!pplx::details::_Scheduler_Param::DefaultWorkCallback(struct _TP_CALLBACK_INSTANCE *,void *,struct _TP_WORK *) Unknown
ntdll.dll!TppWorkpExecuteCallback() Unknown
ntdll.dll!TppWorkerThread() Unknown
kernel32.dll!BaseThreadInitThunk?() Unknown
ntdll.dll!RtlUserThreadStart?() Unknown
Any help would be greatly appreciated.