c++multithreadingcurlstackphilips-hue

Using c++ stack with two threads


I have searched the web, but cannot find a good example of what I am trying to accomplish. Using c++, I am working with a visualization for Kodi (an open source mediacenter application). This visualization sends data as an http client to a Philips Hue bridge, an http server, to change the colors and other attributes of lights to match the music. I am using cURL to handle the http requests.

Using one thread causes issues where cURL is taking time to do its job and receive responses from the Philips Hue bridge (http server). This blocks the visualization on screen and sometimes the audio output. You can see the delays here: YouTube video of the visualization.

The way add-ons are structured in Kodi means there is no "main" function. So for multithreading, I cannot figure out a good construct as most examples on the web create a thread in main and join it later in main. At the moment, I'm trying something like this:


I've tried multiple techniques for creating a thread, but while debugging in Windows they all result in errors like:

First-chance exception at 0x0856335A (visualization.wavforhue.dll) in Kodi.exe: 0xC0000005: Access violation reading location 0xFEEEFEF6.
First-chance exception at 0x76CDC52F in Kodi.exe: Microsoft C++ exception: access_violation at memory location 0x003BEC5C.
First-chance exception at 0x76CDC52F in Kodi.exe: Microsoft C++ exception: access_violation at memory location 0x003BEC5C.

or

Run-Time Check Failure #2 - Stack around the variable '_Now' was corrupted. at std::this_thread::sleep_for(std::chrono::seconds(2));.

or an abort(); called by Kodi after the start function completes.


With the curl calls enabled in the code below I receive a different error. It occurs on the second attempt to play a song. The first attempt is successful.

The error is The ordinal 4445 could not be located in the dynamic link library LIBEAY32.dll. This library is associated with SSL, which my code does not use. However, I must be affecting other instances of curl in the Kodi program somehow.

The interesting part is without the curl calls, the code appears to operate without error. I think if I can successfully troubleshoot the LIBEAY32.dll problem, this can be marked solved.

Here is the full code (GPL notice at the bottom to improve readability):

//--------------------------------------------------------------------------------------
#include <xbmc_vis_dll.h>
#include <stdio.h>
#ifdef _WIN32
#include <winsock2.h>
#endif
#include <curl/curl.h>
#include <string>
//------------------------------------------------------------------


//------------------------------------------------------------------
#include <atomic>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
//----------------------------------------------------------------------


// Thread initialization -------------------------------------------------
std::mutex gMutex;
std::condition_variable gThreadConditionVariable;
std::atomic<bool> gRunThread;
bool gReady;
std::thread gWorkerThread;
std::queue<int> gQueue;
// End thread initialization ---------------------------------------------


void workerThread()
{
  bool isEmpty;
  std::string json;
  // This thread comes alive when Create(), Start(), or AudioData() 
  // is invoked by the main program.
  // It runs until Destroy() or Stop() is invoked.
  while (gRunThread)
  {
    //check that an item is on the stack

    {
      std::lock_guard<std::mutex> lock(gMutex);
      isEmpty = gQueue.empty();
    }

    if (isEmpty)
    {
      //Wait until AudioData() sends data.
      std::unique_lock<std::mutex> lock(gMutex);
      gThreadConditionVariable.wait(lock, []{return gReady; });
    }
    else
    {
      std::lock_guard<std::mutex> lock(gMutex);
      int value = gQueue.front();
      gQueue.pop();
    }
    if (!isEmpty)
    {
      /*
      CURL *curl = curl_easy_init();
      CURLcode res;
      json = "{\"hue\":" + std::to_string(rand() % 60000) + "}";
      // Now specify we want to PUT data, but not using a file, so it has to be a CUSTOMREQUEST
      curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1);
      curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L);
      curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
      //curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, noop_cb);
      curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json.c_str());
      // Set the URL that is about to receive our POST. 
      curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.10.6/api/KodiVisWave/lights/3/state");
      // Perform the request, res will get the return code
      res = curl_easy_perform(curl);
      // always cleanup curl
      curl_easy_cleanup(curl);
      */
    }
  }
}




//-- Create -------------------------------------------------------------------
// Called on load. Addon should fully initalize or return error status
//-----------------------------------------------------------------------------
extern "C" ADDON_STATUS ADDON_Create(void* hdl, void* props)
{
  if (!props)
    return ADDON_STATUS_UNKNOWN;

  gRunThread = true;
  gReady = false;

  // Check if the thread is alive yet.
  if (!gWorkerThread.joinable())
  {
    gWorkerThread = std::thread(&workerThread);
  }

  // Must initialize libcurl before any threads are started.
  //curl_global_init(CURL_GLOBAL_ALL);

  return ADDON_STATUS_OK;
}

//-- Start --------------------------------------------------------------------
// Called when a new soundtrack is played
//-----------------------------------------------------------------------------
extern "C" void Start(int iChannels, int iSamplesPerSec, int iBitsPerSample, const char* szSongName)
{
  gRunThread = true;
  // Check if the thread is alive yet.
  if (!gWorkerThread.joinable())
  {
    gWorkerThread = std::thread(&workerThread);
  }
}

//-- Audiodata ----------------------------------------------------------------
// Called by XBMC to pass new audio data to the vis
//-----------------------------------------------------------------------------
extern "C" void AudioData(const float* pAudioData, int iAudioDataLength, float *pFreqData, int iFreqDataLength)
{
  // Processing audio data
  if (rand() % 7 == 3)
  {
    std::lock_guard<std::mutex> lock(gMutex);
    gQueue.push(1);
  }

  gRunThread = true;
  // Check if the thread is alive yet.
  if (!gWorkerThread.joinable())
  {
    gWorkerThread = std::thread(&workerThread);
  }

  // Send the curl calls to the worker thread
  {
    std::lock_guard<std::mutex> lock(gMutex);
    gReady = true;
  }
  gThreadConditionVariable.notify_one();

}

//-- Stop ---------------------------------------------------------------------
// This dll must stop all runtime activities
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" void ADDON_Stop()
{
  gRunThread = false;
  while (gWorkerThread.joinable())
  {
    gWorkerThread.join();
  }
}

//-- Detroy -------------------------------------------------------------------
// Do everything before unload of this add-on
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" void ADDON_Destroy()
{
  gRunThread = false;
  while (gWorkerThread.joinable())
  {
    gWorkerThread.join();
  }
}

//-- Render -------------------------------------------------------------------
// Called once per frame. Do all rendering here.
//-----------------------------------------------------------------------------
extern "C" void Render()
{

}

//-- GetInfo ------------------------------------------------------------------
// Tell XBMC our requirements
//-----------------------------------------------------------------------------
extern "C" void GetInfo(VIS_INFO* pInfo)
{
  pInfo->bWantsFreq = false;
  pInfo->iSyncDelay = 0;
}

//-- OnAction -----------------------------------------------------------------
// Handle XBMC actions such as next preset, lock preset, album art changed etc
//-----------------------------------------------------------------------------
extern "C" bool OnAction(long flags, const void *param)
{
  bool ret = false;
  return ret;
}

//-- GetPresets ---------------------------------------------------------------
// Return a list of presets to XBMC for display
//-----------------------------------------------------------------------------
extern "C" unsigned int GetPresets(char ***presets)
{
  return 0;
}

//-- GetPreset ----------------------------------------------------------------
// Return the index of the current playing preset
//-----------------------------------------------------------------------------
extern "C" unsigned GetPreset()
{
  return 0;
}

//-- IsLocked -----------------------------------------------------------------
// Returns true if this add-on use settings
//-----------------------------------------------------------------------------
extern "C" bool IsLocked()
{
  return false;
}

//-- GetSubModules ------------------------------------------------------------
// Return any sub modules supported by this vis
//-----------------------------------------------------------------------------
extern "C" unsigned int GetSubModules(char ***names)
{
  return 0; // this vis supports 0 sub modules
}

//-- HasSettings --------------------------------------------------------------
// Returns true if this add-on use settings
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" bool ADDON_HasSettings()
{
  return false;
}

//-- GetStatus ---------------------------------------------------------------
// Returns the current Status of this visualization
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" ADDON_STATUS ADDON_GetStatus()
{
  return ADDON_STATUS_OK;
}

//-- GetSettings --------------------------------------------------------------
// Return the settings for XBMC to display
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" unsigned int ADDON_GetSettings(ADDON_StructSetting ***sSet)
{
  return 0;
}

//-- FreeSettings --------------------------------------------------------------
// Free the settings struct passed from XBMC
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------

extern "C" void ADDON_FreeSettings()
{
}

//-- SetSetting ---------------------------------------------------------------
// Set a specific Setting value (called from XBMC)
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" ADDON_STATUS ADDON_SetSetting(const char *strSetting, const void* value)
{
  return ADDON_STATUS_OK;
}

//-- Announce -----------------------------------------------------------------
// Receive announcements from XBMC
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" void ADDON_Announce(const char *flag, const char *sender, const char *message, const void *data)
{
}

/*
 *      Copyright (C) 2008-2016 Team Kodi
 *      http://kodi.tv
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with XBMC; see the file COPYING.  If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */

Is there a better way to do this? If not, what do I do in the destroy function to mitigate the access violation?


Here is a link to an extremely bare-bones version of the source code without the complicated sound data processing, graphics rendering, and curl calls. It still fails with Run-Time Check Failure #2 - Stack around the variable '_Now' was corrupted. at std::this_thread::sleep_for(std::chrono::seconds(2));.


Edit: The code linked above should work now. I still experience separate problems with DirectX 11 and cURL on Windows 7 and 8.1, but OpenELEC, Ubuntu, and Android are happy with that construct. The full implementation is linked in the video or switch to the master branch in GitHub.


Solution

  • //do what here to ensure the thread ends??

    It seems here the right thing to do is to join the thread. Obviously, the way the code is currently structured you don't have access to the thread object.

    void workerThread()
    {
        CurlData workerCurlData;
        while (true)
        {
            ....
        }
    }
    

    Here, I think you would want to replace the hard-coded true with a variable (e.g., perhaps of type std::atomic<bool>) which could be initialized to true and would be set to false when the thread needs to be shutdown.

    The function responsible for shutting down the thread would assign false and then call join to wait for the thread to finish its execution.


    Note:


    Example Code

    #include <atomic>
    #include <chrono>
    #include <iostream>
    #include <mutex>
    #include <queue>
    #include <thread>
    
    std::mutex gMutex;
    std::queue<int> gQueue;
    std::atomic<bool> gRunThread(true);
    std::thread gWorkerThread;
    
    void workerThread();
    
    void driver();
    
    void start();
    
    void addData(int i);
    
    void end();
    
    int main()
    {
        std::thread driverThread(&driver);
        driverThread.join();
    
        return 0;
    }
    
    void driver()
    {
        std::cout << "Starting ...\n";
        std::this_thread::sleep_for(std::chrono::seconds(1));
        start();
    
        std::this_thread::sleep_for(std::chrono::seconds(1));
        for (auto i = 0; i < 5; ++i)
        {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            addData(i);
        }
    
        std::cout << "Ending ...\n";
        std::this_thread::sleep_for(std::chrono::seconds(1));
        end();
    }
    
    void workerThread()
    {
        while (gRunThread)
        {
            bool isEmpty;
    
            {
                std::lock_guard<std::mutex> lock(gMutex);
                isEmpty = gQueue.empty();
            }
    
            if (isEmpty)
            {
                std::cout << "Waiting for the queue to fill ...\n";
                std::this_thread::sleep_for(std::chrono::seconds(2));
            }
            else
            {
                std::lock_guard<std::mutex> lock(gMutex);
    
                int value = gQueue.front();
                gQueue.pop();
    
                std::cout << "Dequeued: " << value << "\n";
            }
        }
    }
    
    void start()
    {
        gWorkerThread = std::thread(&workerThread);
    }
    
    void addData(int i)
    {
        {
            std::lock_guard<std::mutex> lock(gMutex);
            gQueue.push(i);
        }
    
        std::cout << "Queued: " << i << "\n";
    }
    
    void end()
    {
        gRunThread = false;
        gWorkerThread.join();
    }
    

    Example Code Output

    Starting ...
    Waiting for the queue to fill ...
    Waiting for the queue to fill ...
    Queued: 0
    Queued: 1
    Dequeued: 0
    Dequeued: 1
    Waiting for the queue to fill ...
    Queued: 2
    Queued: 3
    Dequeued: 2
    Dequeued: 3
    Waiting for the queue to fill ...
    Queued: 4
    Ending ...
    

    Live Example


    Edit

    From the comments I now understand that your code is in a library that is dynamically loaded and unloaded. Unloading the library will result in destroying the global thread object. Therefore, any actions which will unload the library need to be handled in order to guarantee the thread is shutdown properly.