1

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:

  • the start function creates a worker thread
  • the worker thread waits until the stack is populated by the audiodata function
  • once the stack is populated, the worker thread reads the top element, sends the cURL request, and pops off the element
  • the destroy function should join the thread?

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.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Tom
  • 35
  • 8
  • `Access violation reading location 0xFEEEFEF6` Most likely you are accessing freed heap memory. http://stackoverflow.com/a/127404/487892 – drescherjm Feb 17 '16 at 01:44
  • 1
    Not sure if this solves your woes but it appears your first call to `putStack.empty()` is not guarded by a mutex. – James Adkison Feb 17 '16 at 01:45
  • So `ADDON_Create` is called **after** the dynamic library is loaded and `ADDON_Destroy` is called **before** the dynamic library is loaded? – James Adkison Feb 18 '16 at 20:03
  • Should `ADDON_Create` have an `extern "C"` modifier? – James Adkison Feb 18 '16 at 20:05
  • @JamesAdkison: I added `extern "C"`. It didn't matter. I switched to using `std::condition_variable` thanks to your suggestion. Then I dummy-proofed the states. It seems to work sans curl. With curl I get a crash. I've updated the question with the latest code and error. – Tom Feb 19 '16 at 08:57
  • Unrelated: I see you're using `rand` but I don't see any call to `srand` also there are newer/better [pseudo random number generators](http://en.cppreference.com/w/cpp/numeric/random) (e.g., [Mersenne Twister](http://en.cppreference.com/w/cpp/numeric/random/mersenne_twister_engine)) – James Adkison Feb 19 '16 at 13:35
  • Does the `curl` code crash no matter where it occurs or only when executed in the thread? – James Adkison Feb 19 '16 at 13:36
  • @JamesAdkison: Yes. This is a general cURL dll loading/unloading problem now. The multithreading part appears to be 100% solved across platforms. Thanks! If you adjust your answer to include something like "safely account for **any** functions that could load or unload the library" I'll accept it. – Tom Feb 19 '16 at 17:59

1 Answers1

0

//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:

  • It appears there is a call that is not guarded by a mutex (i.e., if (putStack.empty())) at the beginning of the while loop in the workerThread function
  • You may also want to look into std::lock_guard to safely handle locking and unlocking the std::mutex
  • You may want to replace the hard-coded sleeping with proper condition variables (i.e., std::condition_variable to put the thread to sleep until it needs to start working again (i.e., instead of the polling method being used)

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.

James Adkison
  • 9,412
  • 2
  • 29
  • 43
  • Thanks James. That puts me in the right direction as far as the loop and busy waiting goes. But the problem remains with the start, audio data, destroy structure of Kodi addons. There is no single main function to execute the join in. Is it possible to pass the identity of the thread in a global variable from start to destroy and use that somehow? – Tom Feb 17 '16 at 03:20
  • @Tom The `main` in my example is intended to be like your start function. It shows the thread is started there but declared outside the function. In this case, the ending function can cancel the thread and wait for the thread to finish (i.e., because the thread object is global). – James Adkison Feb 17 '16 at 03:27
  • Yes, it makes sense now. After implementing using this method I get `Run-Time Check Failure #2 - Stack around the variable '_Now' was corrupted.` regarding `std::this_thread::sleep_for(std::chrono::seconds(2));` This is probably the queue interfering. Quick googling leads me to believe this problem is difficult to troubleshoot. Hmm... – Tom Feb 17 '16 at 05:05
  • Unfortunately, this solution does not work. When the destructor is called on "start" the main program throws an "abort();" If "gWorkerThread = std::thread(&workerThread);" is removed, the main program does not call abort. – Tom Feb 17 '16 at 06:37
  • @Tom What destructor is called in `start`? If it's the destructor for `gThread` then something is wrong because it should not be going out of scope unless it's declared in the `start` function. I have updated my answer to try to more closely follow the execution of your program, as I understand it but I don't know that I can take it any further without actually reproducing your setup. – James Adkison Feb 17 '16 at 13:43
  • I agree. It should not be going out of scope, but that is when/where the the main program throws the abort regarding addons that do not want to quit nicely. Other globals work fine. If you want to reproduce the setup and find a working solution you'd be my hero. I can give you a guide for Windows or Ubuntu. I'd prefer Windows because that's the platform I'm debugging on for now. – Tom Feb 17 '16 at 19:07
  • @Tom How is your code executed? For example, is it in a dynamic library that gets loaded and then the functions are called by Kodi? – James Adkison Feb 17 '16 at 19:20
  • Yes. The addon is loaded as a dynamic library when Kodi starts playing a song. Kodi calls the functions. Kodi destroys and reloads the addon with window changes in the interface. Kodi destroys the addon when the song stops playing. – Tom Feb 17 '16 at 20:29
  • @Tom Okay, that's the only way I was expecting the execution could occur. Is the library being loaded and unloaded? I'm trying to think of what could be causing the `std::thread` to go out of scope. Can you update your question with the latest code you are using? – James Adkison Feb 18 '16 at 14:53
  • The library does get loaded and unloaded frequently as described in my last comment. Values assigned to other global variables in `start` remain the same until a `destroy` is called. I've updated the code displayed above to the more basic structure I linked to. The link is to code I am actually using to solve this problem. – Tom Feb 18 '16 at 19:52
  • @Tom I updated the answer to state that the thread needs to be properly shutdown before the library is unloaded. – James Adkison Feb 19 '16 at 18:15