curl / Mailing Lists / curl-library / Single Mail

curl-library

Problem/Crash with libCurl Daily Snapshot and Option CURLSHOPT_SHARE and Parameter CURL_LOCK_DATA_CONNECT

From: Dawson, Patrick <Patrick.Dawson_at_zwick.de>
Date: Tue, 28 Nov 2017 15:36:26 +0000

Hi all,

I tried the new option CURLSHOPT_SHARE with parameter CURL_LOCK_DATA_CONNECT today that will be available in libcurl 7.57.0 according to https://curl.haxx.se/dev/release-notes.html.
This option would be very useful for us as we want to use libcurl in a multithreaded environment. Sadly I discovered random crashes with different callstacks when using this option.

I wonder if I configured something wrong or forgot something. Or maybe I misunderstood the use case for this option completely.

I have put together a small example that leads to the same error as I encountered in our software.

#include <windows.h>
#include <iostream>
#include <tchar.h>
#include <strsafe.h>
#include <curl/curl.h>

using namespace std;

const size_t MAX_THREADS = 3;
const size_t ITERATIONS = 100;

#define CHECK_CURL(cmd) \
    if ((cmd) != CURLE_OK) \
    { \
        throw runtime_error( "Error executing: "#cmd ); \
    }

DWORD WINAPI MyThreadFunction( LPVOID lpParam );

HANDLE ghMutex;

static void ShareLockFunc( CURL* pHandle, curl_lock_data Data, curl_lock_access Access, void* pUseptr )
{
    (void)pHandle;
    (void)Data;
    (void)Access;
    (void)pUseptr;
    WaitForSingleObject(
        ghMutex, // handle to mutex
        INFINITE ); // no time-out interval
}

static void ShareUnlockFunc( CURL* pHandle, curl_lock_data Data, void* pUseptr )
{
    (void)pHandle;
    (void)Data;
    (void)pUseptr;
    ReleaseMutex( ghMutex );
}

typedef struct MyData {
    CURLSH* pShare;
} MYDATA, *PMYDATA;

int CurlPerformGet( CURL* pHandle )
{
    CHECK_CURL( curl_easy_setopt( pHandle, CURLOPT_FOLLOWLOCATION, 1L ) );
    CHECK_CURL( curl_easy_setopt( pHandle, CURLOPT_URL, "http://127.0.0.1:3000" ) );
    CHECK_CURL( curl_easy_perform( pHandle ) );
    int StatusCode = 0;
    CHECK_CURL( curl_easy_getinfo( pHandle, CURLINFO_RESPONSE_CODE, &StatusCode ) );
    return StatusCode;
}

int _tmain()
{
    PMYDATA pDataArray[MAX_THREADS];
    DWORD dwThreadIdArray[MAX_THREADS];
    HANDLE hThreadArray[MAX_THREADS];

    ghMutex = CreateMutex(
        NULL, // default security attributes
        FALSE, // initially not owned
        NULL ); // unnamed mutex

    if (ghMutex == NULL)
    {
        printf( "CreateMutex error: %d\n", GetLastError() );
        return 1;
    }

    (void)curl_global_init( CURL_GLOBAL_ALL );

    CURLSH *pShare = curl_share_init();
    curl_share_setopt( pShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT );

    // Create MAX_THREADS worker threads.
    for (int i = 0; i < MAX_THREADS; i++)
    {
        pDataArray[i] = (PMYDATA)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY,
            sizeof( MYDATA ) );

        pDataArray[i]->pShare = pShare;

        if (pDataArray[i] == NULL)
        {
            ExitProcess( 2 );
        }

        hThreadArray[i] = CreateThread(
            NULL, // default security attributes
            0, // use default stack size
            MyThreadFunction, // thread function name
            pDataArray[i],
            0, // use default creation flags
            &dwThreadIdArray[i] ); // returns the thread identifier

        if (hThreadArray[i] == NULL)
        {
            cout << "Create thread failed" << endl;
            ExitProcess( 3 );
        }
    }

    WaitForMultipleObjects( MAX_THREADS, hThreadArray, TRUE, INFINITE );

    for (int i = 0; i < MAX_THREADS; i++)
    {
        CloseHandle( hThreadArray[i] );
    }

    curl_share_cleanup( pShare );
    curl_global_cleanup();

    CloseHandle( ghMutex );

    return 0;
}

DWORD WINAPI MyThreadFunction( LPVOID lpParam )
{
    PMYDATA pData = (PMYDATA)lpParam;
    CURLSH* pShare = pData->pShare;

    for (size_t i = 0; i < ITERATIONS; ++i)
    {
        CURL* pHandle = curl_easy_init();
        curl_easy_setopt( pHandle, CURLOPT_SHARE, pShare );
        cout << "Status: " << CurlPerformGet( pHandle ) << endl;
        curl_easy_cleanup( pHandle );
    }

    return 0;
}

As a backend server I used a simple nodejs express server:
const express = require('express');

const app = express();
let requestsCount = 0;

process.on('uncaughtException', function (err) {
    console.log("uncaught exception");
    console.log(err);
});

app.get('/', (req, res) => {
    console.log(req.headers);
    requestsCount += 1;
    setTimeout(() => {
        res.send('Hello World!');
    }, 1);
});

app.use(function (err, req, res, next) {
    console.error("Express error catched!", err);
    res.status(500).send("Error happened");
});

const server = app.listen(3000, () => {
    console.log('Example app listening on port 3000!');
});

let count = 0;
server.on("connection", (socket) => {
    console.log(`GOT CONNECTION ${count++} on addr: ${JSON.stringify(socket.address())}`);
    socket.setKeepAlive(true);
});

const interval = 2000;
setInterval(() => {
    console.log(`got ${requestsCount} requests in ${interval/1000}s`);
    requestsCount = 0;
}, interval);

--
Best regards
Patrick Dawson
-------------------------------------------------------------------
Unsubscribe: https://cool.haxx.se/list/listinfo/curl-library
Etiquette:   https://curl.haxx.se/mail/etiquette.html
Received on 2017-11-28