I read the SO articles if curl
is thread-safe. This very simple code crashes - not always but when I call the program several times in a row [not parallel] then it crashes either with a segmentation fault or with the below error.
That far I am convinced I follow the rules regarding curl
and threads as stated in the documentation.
In tests I could find out that it crashes in curl_easy_perform()
.
#include <curl/curl.h>
#include <stdio.h>
#include <thread>
class curlClass
{
private:
CURL * curl {};
CURLcode res;
const char * sUrl;
public:
auto loadDataFromUrl() -> void;
static auto initCurl() -> void;
static auto releaseCurl() -> void;
static auto callbackSaveData( void * content, size_t size, size_t nmemb, curlClass * classInstance ) -> size_t;
curlClass( const char * );
~curlClass();
};
auto curlClass::initCurl() -> void
{
curl_global_init(CURL_GLOBAL_SSL);
}
auto curlClass::releaseCurl() -> void
{
curl_global_cleanup();
}
curlClass::curlClass( const char * sUrl ) : sUrl( sUrl )
{
curl = curl_easy_init();
}
curlClass::~curlClass()
{
curl_easy_cleanup( curl );
}
auto curlClass::callbackSaveData( __attribute__ ((unused)) void *contents,
size_t size,
size_t nmemb,
__attribute__ ((unused)) curlClass * classInstance
) -> size_t
{
return size * nmemb;
}
auto curlClass::loadDataFromUrl() -> void
{
if ( curl )
{
curl_easy_setopt(curl, (CURLoption) CURLOPT_SSL_VERIFYPEER, nullptr);
curl_easy_setopt(curl, (CURLoption) CURLOPT_URL, sUrl);
curl_easy_setopt(curl, (CURLoption) CURLOPT_WRITEFUNCTION, callbackSaveData);
res = curl_easy_perform(curl);
printf( "Return: %d\n", res );
}
}
auto worker( const char * sUrl ) -> void
{
curlClass myInstance( sUrl );
myInstance.loadDataFromUrl();
}
int main(void)
{
curl_version_info_data * curl_version = curl_version_info(CURLVERSION_NOW);
printf( "Curl version=%s\n", curl_version->version );
curlClass::initCurl();
std::thread thread1( worker, "https://www.google.com");
std::thread thread2( worker, "https://www.google.com" );
std::thread thread3( worker, "https://www.google.com" );
std::thread thread4( worker, "https://www.google.com" );
std::thread thread5( worker, "https://www.google.com" );
std::thread thread6( worker, "https://www.google.com" );
std::thread thread7( worker, "https://www.google.com" );
thread1.join();
thread2.join();
thread3.join();
thread4.join();
thread5.join();
thread6.join();
thread7.join();
curlClass::releaseCurl();
}
UPDATE:
I compiled the new curl
version 7.46.0
. This is one of the possible error dumps:
Curl version=7.46.0
*** glibc detected *** ./curl_crash: double free or corruption (out): 0x00007fcd200056d0 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x76618)[0x7fcd2ff4a618]
/lib64/libc.so.6(cfree+0x6c)[0x7fcd2ff4f65c]
/usr/lib64/libcrypto.so.0.9.8(CRYPTO_free+0x19)[0x7fcd2f9f13f9]
/usr/lib64/libcrypto.so.0.9.8(+0xf407d)[0x7fcd2f9d307d]
/usr/lib64/libcrypto.so.0.9.8(ERR_clear_error+0xd)[0x7fcd2f9efd1d]
/usr/local/lib/libcurl.so.4(+0x4849c)[0x7fcd30a2d49c]
/usr/local/lib/libcurl.so.4(+0x4b970)[0x7fcd30a30970]
/usr/local/lib/libcurl.so.4(+0x1073d)[0x7fcd309f573d]
/usr/local/lib/libcurl.so.4(+0x20451)[0x7fcd30a05451]
/usr/local/lib/libcurl.so.4(+0x31e0e)[0x7fcd30a16e0e]
/usr/local/lib/libcurl.so.4(curl_multi_perform+0xdd)[0x7fcd30a1780d]
/usr/local/lib/libcurl.so.4(curl_easy_perform+0x10b)[0x7fcd30a1001b]
./curl_crash(_ZN9curlClass15loadDataFromUrlEv+0x88)[0x408002]
./curl_crash(_Z6workerPKc+0x2c)[0x408051]
./curl_crash(_ZNSt12_Bind_simpleIFPFvPKcES1_EE9_M_invokeIILm0EEEEvSt12_Index_tupleIIXspT_EEE+0x40)[0x409818]
./curl_crash(_ZNSt12_Bind_simpleIFPFvPKcES1_EEclEv+0x1d)[0x409711]
./curl_crash(_ZNSt6thread5_ImplISt12_Bind_simpleIFPFvPKcES3_EEE6_M_runEv+0x1c)[0x40968e]
/usr/local/lib64/libstdc++.so.6(+0xb5c10)[0x7fcd30790c10]
/lib64/libpthread.so.0(+0x77f6)[0x7fcd2e81c7f6]
/lib64/libc.so.6(clone+0x6d)[0x7fcd2ffaf09d]
======= Memory map: ========
I do not have really a clue what is going wrong. When I start the program 5 times then at least once it crashes.
What am I doing wrong or is it possible - according to the dump - that I am using an old SSL library?
Command line to compile:
g++ --std=c++11 -Wall -Werror -pedantic -Wextra curl_crash.cpp -o curl_crash -lcurl -rdynamic && ./curl_crash
I stopped too early reading the SSL library documentation. Thanks to Petesh
, who pointed out the issue while accessing openssl
, I could then fix fast the problem.
As stated in the curl
documentation Thread-safe --> TLS
--> OpenSSL
I had to use the functions. I adapted it to C++11 standard:
#include <mutex>
#include <openssl/err.h>
#include <vector>
class SslCurlWrapper
{
private:
static std::vector<std::mutex> vectorOfSslMutex;
static auto id_function() -> unsigned long { return ( pthread_self() ); }
static auto locking_function(int, int, const char *, int) -> void;
public:
SslCurlWrapper();
~SslCurlWrapper();
};
std::vector<std::mutex> SslCurlWrapper::vectorOfSslMutex( CRYPTO_num_locks() );
//----------------------------------------
auto SslCurlWrapper::locking_function( int mode,
int n,
__attribute__ ((unused)) const char * file,
__attribute__ ((unused)) int line
) -> void
//----------------------------------------
{
if ( mode & CRYPTO_LOCK ) vectorOfSslMutex [n].lock();
else vectorOfSslMutex [n].unlock();
}
//------------------------------
SslCurlWrapper::SslCurlWrapper()
//------------------------------
{
CRYPTO_set_id_callback( id_function );
CRYPTO_set_locking_callback( locking_function );
}
//-------------------------------
SslCurlWrapper::~SslCurlWrapper()
//-------------------------------
{
CRYPTO_set_id_callback( nullptr );
CRYPTO_set_locking_callback( nullptr );
}
It can be then used like this:
int main(void)
{
SslCurlWrapper sslObject; // hook is set up
// here it is safe to use the curl library in a multi-thread-environment
} // hook is released/uninstalled
Compile command:
g++ -std=c++11 -Wall -Werror -Wextra -pedantic -c SslCurlWrapper.cpp
The -lcrypto
library has to be added to the compiler command line in order the linker does not complain.
My personal remark is that I am a bit surprised that the default mode in the openssl
library does not implement the both mentioned function that way to avoid each programmer has to start from scratch. This could be the minimum-implementation...
The test shell script called the program 200 times without any crash. Before it crashed immediately. For me it is fixed.
I do not understand the very useless comment from SergeyA.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With