2017-09-25 14 views
0

J'implémente une classe qui utilise boost::asio pour implémenter une bibliothèque pour les connexions TLS.boost :: asio io_service :: run_one conduit à une erreur Segmentation

Je ne mets en œuvre que des opérations synchrones et certaines d'entre elles acceptent un timeout. je mets en œuvre les méthodes de délai d'attente en utilisant un deadline_timer et io_service.run_one, comme expliqué dans cet exemple: http://www.boost.org/doc/libs/1_45_0/doc/html/boost_asio/example/timeouts/async_tcp_client.cpp

Mon problème est une méthode qui lit octets exactement « n » de la prise et accepte un délai d'attente en tant que paramètre. Le problème est que le io_service.run_one() élève un SIGSEV et je ne sais pas pourquoi. Voici le code (il est si long, mais je ne sais pas d'autre meilleure façon d'expliquer cela):

Le code

ci-dessous sont les méthodes utilisés pour le test, je suis d'exécution:

void CMDRboostConnection::check_deadline() 
{ 
    // Check whether the deadline has passed. We compare the deadline against 
    // the current time since a new asynchronous operation may have moved the 
    // deadline before this actor had a chance to run. 
    if (m_timeoutOpsTimer->expires_at() <= boost::asio::deadline_timer::traits_type::now()) 
    { 
    // TODO do I need to cancel async operations? 
    m_timeoutOpsErrorCode = boost::asio::error::timed_out; 

    // There is no longer an active deadline. The expiry is set to positive 
    // infinity so that the actor takes no action until a new deadline is set. 
    m_timeoutOpsTimer->expires_at(boost::posix_time::pos_infin); 
    } 

    // Put the actor back to sleep. 
    m_timeoutOpsTimer->async_wait(
     boost::bind(&CMDRboostConnection::check_deadline, this)); 
} 

bool CMDRboostConnection::connect() 
{ 
    // TODO: This method already throws an exception, it should be void. 
    DEBUG("Connecting to " + m_url + " : " + m_port); 
    try 
    { 
    // If the socket is already connected, disconnect it before 
    // opening a new conneciont. 
    if (isConnected()) 
    { 
     disconnect(); 
    } 

    m_socket = new SSLSocket(m_ioService, m_context); 

    tcp::resolver resolver(m_ioService); 
    tcp::resolver::query query(m_url, m_port); 

    tcp::resolver::iterator end; 
    tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); 

    boost::asio::connect(m_socket->lowest_layer(), resolver.resolve(query)); 

    if (endpoint_iterator == end) 
    { 
     DEBUG("Endpoint cannot be resolved, disconnecting..."); 
     disconnect(); 
    } 
    else 
    { 
     m_timeoutOpsTimer = new boost::asio::deadline_timer(m_ioService); 
     m_timeoutOpsTimer->expires_at(boost::posix_time::pos_infin); 
     // Start the persistent actor that checks for deadline expiry. 
     check_deadline(); 

     DEBUG("Endpoint resolved, performing handshake"); 
     m_socket->set_verify_mode(boost::asio::ssl::verify_none); 
     m_socket->handshake(SSLSocket::client); 

     DEBUG("Handshake done, connected to " + m_url + " : " + m_port); 
     m_isConnected = true; 
    } 
    } 
    catch (boost::system::system_error &err) 
    { 
    disconnect(); 
    throw; 
    } 

    return m_isConnected; 
} 

std::streambuf& CMDRboostConnection::readNBytes(int n, unsigned int timeout) 
{ 
    try 
    { 
    if(!isConnected()) 
    { 
     std::string err = "Cannot read, not connected"; 
     ERROR(err); 
     throw std::logic_error(err); 
    } 

    if(n == 0) 
    { 
     return m_buffer; 
    } 

    m_timeoutOpsTimer->expires_from_now(
     boost::posix_time::milliseconds(timeout)); 

    m_timeoutOpsErrorCode = boost::asio::error::would_block; 

    boost::asio::async_read(
     *m_socket, 
     m_buffer, 
     boost::asio::transfer_exactly(n), 
     boost::bind(
      &CMDRboostConnection::timoutOpsCallback, 
      this, 
      boost::asio::placeholders::error, 
      boost::asio::placeholders::bytes_transferred) 
    ); 

    do 
    { 
     m_ioService.run_one(); 
    } while (m_timeoutOpsErrorCode == boost::asio::error::would_block); 

    if(m_timeoutOpsErrorCode) 
    { 
     throw boost::system::system_error(m_timeoutOpsErrorCode); 
    } 

    return m_buffer; 
    } 
    catch(boost::system::system_error &err) 
    { 
    ERROR("Timeout reached trying to read a message"); 
    disconnect(); 
    throw; 
    } 
} 

void CMDRboostConnection::disconnect() 
{ 
    try 
    { 
    DEBUG("Disconnecting..."); 
    if(isConnected()) 
    { 
     m_socket->shutdown(); 

     DEBUG("Closing socket..."); 
     m_socket->lowest_layer().close(); 

     if(m_socket != NULL) 
     { 
     delete m_socket; 
     m_socket = NULL; 
     } 
    } 

    if(m_timeoutOpsTimer != NULL) 
    { 
     delete m_timeoutOpsTimer; 
     m_timeoutOpsTimer = NULL; 
    } 

    DEBUG("Disconnection performed properly"); 
    m_isConnected = false; 
    } 
    catch (boost::system::system_error &err) 
    { 
    ERROR("Exception thrown, error = " << err.code() << 
     ", category: " << err.code().category().name() << std::endl); 

    m_isConnected = false; 

    throw; 
    } 
} 

le test

En dessous est le test que je suis en cours d'exécution pour tester la méthode:

TEST(CMDRboostConnection, readNbytesTimeoutDoesNotMakeTheProgramCrashWhenTmeout) 
{ 
    std::auto_ptr<CMDR::SSL::ICMDRsslConnection> m_connection = 
      std::auto_ptr<CMDR::SSL::ICMDRsslConnection>(
       new CMDR::SSL::CMDRboostConnection("localhost", "9999")); 

    unsigned int sleepInterval = 0; // seconds 
    unsigned int timeout = 10; // milliseconds 
    unsigned int numIterations = 10; 
    std::string msg("delay 500000"); // microseconds 

    if(!m_connection->isConnected()) 
    { 
    m_connection->connect(); 
    } 


    for(unsigned int i = 0; i < numIterations; i++) 
    { 

    if(!m_connection->isConnected()) 
    { 
     m_connection->connect(); 
    } 

    ASSERT_NO_THROW(m_connection->write(msg)); 

    ASSERT_THROW (
     m_connection->readNBytes(msg.size(), timeout), 
     boost::system::system_error); 

    ASSERT_FALSE(m_connection->isConnected()); 

    ASSERT_NO_THROW(m_connection->connect()); 

    sleep(sleepInterval); 
    } 
} 

Le problème

Dans le test ci-dessus, la première itération de la boucle va bien, c'est-à-dire que la première fois que la méthode readNBytes est appelée, elle fonctionne (lève une exception comme prévu). La deuxième fois qu'il est exécuté, il soulève le SIGSEV.

EDIT

Je suis le test ci-dessus exécutais entre autres qui testent d'autres fonctionnalités. J'ai réalisé que si j'exécute le test ci-dessus seulement, cela fonctionne. Mais, si je l'exécute en plus d'autres, alors le programme se bloque avec le SIGSEV mentionné.

Ceci est l'un des tests qui cause le problème:

TEST(CMDRboostConnection, canConnectDisconnect) 
{ 
    std::auto_ptr<CMDR::SSL::ICMDRsslConnection> m_connection = 
      std::auto_ptr<CMDR::SSL::ICMDRsslConnection>(
       new CMDR::SSL::CMDRboostConnection("localhost", "9999")); 

    unsigned int sleepInterval = 0; // seconds 
    unsigned int timeout = 1000; // milliseconds 
    unsigned int numIterations = 10; 
    std::string msg("normally"); 


    if(!m_connection->isConnected()) 
    { 
    ASSERT_NO_THROW (m_connection->connect()); 
    } 

    for(unsigned int i = 0; i < numIterations; i++) 
    { 
    ASSERT_NO_THROW(m_connection->disconnect()); 
    sleep(sleepInterval); 
    ASSERT_NO_THROW(m_connection->connect()); 
    } 
} 

En conclusion, si j'exécute les tests ci-dessus, la première plante. Mais si j'exécute seulement le premier, cela fonctionne.

EDIT 2 Correction du bug mentionné dans les commentaires.

Répondre

0

J'ai remplacé tous les attributs membres par des pointeurs, et maintenant cela fonctionne (c'est-à-dire que je peux passer tous les tests que j'ai écrits). Les méthodes de déconnexion/connexion sont maintenant comme suit:

bool CMDRboostConnection::connect() 
{ 
    // TODO: This method already throws an exception, it should be void. 
    DEBUG("Connecting to " + m_url + " : " + m_port); 
    try 
    { 
    // If the socket is already connected, disconnect it before 
    // opening a new conneciont. 
    if (isConnected()) 
    { 
     disconnect(); 
    } 

    m_ioService = new boost::asio::io_service(); 
    m_timeoutOpsTimer = new boost::asio::deadline_timer(*m_ioService); 
    m_context = new boost::asio::ssl::context(boost::asio::ssl::context::sslv23); 
    m_socket = new SSLSocket(*m_ioService, *m_context); 

    tcp::resolver resolver(*m_ioService); 
    tcp::resolver::query query(m_url, m_port); 

    tcp::resolver::iterator end; 
    tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); 

    boost::asio::connect(m_socket->lowest_layer(), resolver.resolve(query)); 

    if (endpoint_iterator == end) 
    { 
     DEBUG("Endpoint cannot be resolved, disconnecting..."); 
     disconnect(); 
    } 
    else 
    { 
     m_timeoutOpsTimer->expires_at(boost::posix_time::pos_infin); 
     // Start the persistent actor that checks for deadline expiry. 
     check_deadline(); 

     DEBUG("Endpoint resolved, performing handshake"); 
     m_socket->set_verify_mode(boost::asio::ssl::verify_none); 
     m_socket->handshake(SSLSocket::client); 

     DEBUG("Handshake done, connected to " + m_url + " : " + m_port); 
     m_isConnected = true; 
    } 
    } 
    catch (boost::system::system_error &err) 
    { 
    disconnect(); 
    throw; 
    } 

    return m_isConnected; 
} 

void CMDRboostConnection::disconnect() 
{ 
    try 
    { 
    DEBUG("Disconnecting..."); 
    if(isConnected()) 
    { 
     m_socket->shutdown(); 

     DEBUG("Closing socket..."); 
     m_socket->lowest_layer().close(); 

     if(m_socket != NULL) 
     { 
     delete m_socket; 
     m_socket = NULL; 
     } 
    } 

    if(m_timeoutOpsTimer != NULL) 
    { 
     delete m_timeoutOpsTimer; 
     m_timeoutOpsTimer = NULL; 
    } 

    if(m_context != NULL) 
    { 
     delete m_context; 
     m_context = NULL; 
    } 

    if(m_ioService != NULL) 
    { 
     delete m_ioService; 
     m_ioService = NULL; 
    } 

    DEBUG("Disconnection performed properly"); 
    m_isConnected = false; 
    } 
    catch (boost::system::system_error &err) 
    { 
    ERROR("Exception thrown, error = " << err.code() << 
     ", category: " << err.code().category().name() << std::endl); 

    if(m_timeoutOpsTimer != NULL) 
    { 
     delete m_timeoutOpsTimer; 
     m_timeoutOpsTimer = NULL; 
    } 

    if(m_context != NULL) 
    { 
     delete m_context; 
     m_context = NULL; 
    } 

    if(m_ioService != NULL) 
    { 
     delete m_ioService; 
     m_ioService = NULL; 
    } 

    m_isConnected = false; 

    throw; 
    } 
} 

Comme vous pouvez le voir, maintenant socket, le io_service, le deadline_timer et context sont créés sur la connexion et la déconnexion libérés sur.Je ne comprends toujours pas ce qui se passe, laissez-moi vous expliquer:

J'ai essayé de ré-écrire les variables ci-dessus l'un d'un côté, qui est, d'abord le socket, le timer, le context et enfin le io_service.

Les tests ont passé seulement lorsque le io_service est un ptr, mais je ne peux pas comprendre pourquoi. Si io_service est une variable de portée de classe, elle doit être supprimée chaque fois que l'instance de classe est hors de portée, c'est-à-dire à chaque fois que l'un de mes TEST est terminé.

Il semble que, avant de l'implémenter en tant que ptr, cela ne se produisait pas. Je soupçonne que peut-être, lorsque le readNBytes déclenche une exception en raison d'un délai d'attente, l'appel read_async reste dans la file d'attente d'action io_service, et peut-être que cela a causé le problème.

1

Vous avez foiré les pointeurs et géré la gestion de l'objet. Si la méthode connect est appelée lorsque vous êtes déjà connecté, vous remplacez l'ancienne socket par new et seulement ensuite vérifiez si elle était déjà connectée ou utilisée quelque part. De plus, auto_ptr est obsolète. Vous devez utiliser unique_ptr pour gérer les pointeurs propriétaires.

+0

Je peux ' t d'utiliser ni C++ 11 ni stimuler smart ptrs pour l'instant, mais oui je me suis rendu compte que je préfère les ptrs bruts que 'auto_ptr' – Dan