Skip to content

Commit

Permalink
Fix: Thread-local locale, fixes #244
Browse files Browse the repository at this point in the history
Makes LT's threads robust against change of the process locale by system or other ill-behaving plugins
  • Loading branch information
TwinFan committed Dec 30, 2022
1 parent e9b53de commit b6fb7bb
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 42 deletions.
65 changes: 51 additions & 14 deletions Include/LiveTraffic.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
#include <cstring>
#include <ctime>
#include <cassert>
#include <locale>
#if APL
#include <xlocale.h>
#endif

// Windows
#if IBM
Expand Down Expand Up @@ -217,7 +221,11 @@ inline std::string strAtMost(const std::string s, size_t m) {
}

/// Replace all occurences of one string with another
void str_replaceAll(std::string& str, const std::string& from, const std::string& to);
void str_replaceAll(std::string& str, const std::string& from, const std::string& to, size_t start_pos = 0);

/// @brief Replace a potentially wrong decimal point
/// @returns if `true` if locale defines other decimal point than `.`
bool str_correctDecimalPt (std::string& str, size_t start_pos = 0);

// trimming of string
// https://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring
Expand Down Expand Up @@ -398,6 +406,26 @@ inline struct tm *localtime_s(struct tm * result, const time_t * time)

#endif

#if IBM
#define locale_t _locale_t
#define freelocale _free_locale

#define LC_GLOBAL_LOCALE ((locale_t)-1)
#define LC_ALL_MASK LC_ALL
#define LC_COLLATE_MASK LC_COLLATE
#define LC_CTYPE_MASK LC_CTYPE
#define LC_MONETARY_MASK LC_MONETARY
#define LC_NUMERIC_MASK LC_NUMERIC
#define LC_TIME_MASK LC_TIME

// Base locale is ignored and mixing of masks is not supported
#define newlocale(mask, locale, base) _create_locale(mask, locale)

/// @brief Simulation of Linux' `uselocale()` function on Windows
/// @see https://stackoverflow.com/a/17173977
locale_t uselocale(locale_t new_locale);
#endif

/// Simpler access to strncpy_s if dest is a char array (not a pointer!)
#define STRCPY_S(dest,src) strncpy_s(dest,sizeof(dest),src,sizeof(dest)-1)
#define STRCPY_ATMOST(dest,src) strncpy_s(dest,sizeof(dest),strAtMost(src,sizeof(dest)-1).c_str(),sizeof(dest)-1)
Expand All @@ -411,18 +439,27 @@ inline int strerror_s( char *buf, size_t bufsz, int errnum )
{ strerror_r(errnum, buf, bufsz); return 0; }
#endif

// MARK: Thread names
#ifdef DEBUG
// This might not work on older Windows version, which is why we don't publish it in release builds
#if IBM
#define SET_THREAD_NAME(sName) SetThreadDescription(GetCurrentThread(), L##sName)
#elif APL
#define SET_THREAD_NAME(sName) pthread_setname_np(sName)
#elif LIN
#define SET_THREAD_NAME(sName) pthread_setname_np(pthread_self(),sName)
#endif
#else
#define SET_THREAD_NAME(sName)
#endif
// MARK: Thread and Locale

/// Begin a thread and set a thread-local locale
/// @details In the communication with servers we must use internal standards,
/// ie. C locale, so that for example the decimal point is `.`
/// Hence we set a thread-local locale in all threads as they deal with communication.
/// See https://stackoverflow.com/a/17173977
class ThreadSettings {
protected:
locale_t threadLocale = locale_t(0);
locale_t prevLocale = locale_t(0);
public:
/// @brief Defines thread's name and sets the thread's locale
/// @param sThreadName Thread's name, max 16 chars
/// @param localeMask One of the LC_*_MASK constants. If `0` then locale is not changed.
/// @param sLocaleName New locale to set
ThreadSettings (const char* sThreadName,
int localeMask = 0,
const char* sLocaleName = "C");
/// Restores and cleans up locale
~ThreadSettings();
};

#endif /* LiveTraffic_h */
4 changes: 2 additions & 2 deletions Src/LTADSBEx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,8 @@ bool ADSBExchangeConnection::TestADSBExAPIKeyResult (bool& bIsKeyValid)
// actual test, blocks, should by called via std::async
bool ADSBExchangeConnection::DoTestADSBExAPIKey (const std::string newKey)
{
// This is a thread main function, set thread's name
SET_THREAD_NAME("LT_TestADSBEx");
// This is a communication thread's main function, set thread's name and C locale
ThreadSettings TS ("LT_TestADSBEx", LC_ALL_MASK);

bool bResult = false;
char curl_errtxt[CURL_ERROR_SIZE];
Expand Down
6 changes: 3 additions & 3 deletions Src/LTApt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2390,11 +2390,11 @@ void PurgeApt (const boundingBoxTy& _box)
/// @param radius Search radius around center position in meter
void AsyncReadApt (positionTy ctr, double radius)
{
// This is a communication thread's main function, set thread's name and C locale
ThreadSettings TS ("LT_ReadApt", LC_ALL_MASK);

static size_t lenSceneryLnBegin = strlen(APTDAT_SCENERY_LN_BEGIN);

// This is a thread main function, set thread's name
SET_THREAD_NAME("LT_ReadApt");

// To avoid costly distance calculations we define a bounding box
// just by calculating lat/lon values north/east/south/west of given pos
// and include all airports with coordinates falling into it
Expand Down
4 changes: 2 additions & 2 deletions Src/LTChannel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -694,8 +694,8 @@ void LTFlightDataStop()
// and it runs in a loop until LTFlightDataHideAircraft stops it
void LTFlightDataSelectAc ()
{
// This is a thread main function, set thread's name
SET_THREAD_NAME("LT_Channels");
// This is a communication thread's main function, set thread's name and C locale
ThreadSettings TS ("LT_Channels", LC_ALL_MASK);

while ( !bFDMainStop )
{
Expand Down
4 changes: 2 additions & 2 deletions Src/LTFlightData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1318,8 +1318,8 @@ dequeKeyTimeTy dequeKeyPosCalc;
// the CalcNextPos function on the respective flight data objects
void LTFlightData::CalcNextPosMain ()
{
// This is a thread main function, set thread's name
SET_THREAD_NAME("LT_CalcPos");
// This is a communication thread's main function, set thread's name and C locale
ThreadSettings TS ("LT_CalcPos", LC_ALL_MASK);

// loop till said to stop
while ( !bFDMainStop ) {
Expand Down
4 changes: 2 additions & 2 deletions Src/LTForeFlight.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ bool ForeFlightSender::StopConnection ()
// thread main function
void ForeFlightSender::udpSend()
{
// This is a thread main function, set thread's name
SET_THREAD_NAME("LT_ForeFlight");
// This is a communication thread's main function, set thread's name and C locale
ThreadSettings TS ("LT_ForeFlight", LC_ALL_MASK);

//
// *** open the UDP socket ***
Expand Down
96 changes: 92 additions & 4 deletions Src/LTMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,16 @@ void LTOpenURL (const std::string& _url, const std::string& addon)
{
// Transiently, we allow to add the current camera position into the URL
std::string url(_url);
if (_url.find('%') != std::string::npos) {
const size_t posPrct = _url.find('%');
if (posPrct != std::string::npos) {
char buf[256];
const positionTy camPos = dataRefs.GetViewPos();
snprintf (buf, sizeof(buf), _url.c_str(),
camPos.lat(), camPos.lon());
url = buf;
str_correctDecimalPt(url, posPrct);
}

// If an addon is sepcified it is just added to the end
if (!addon.empty())
url += addon;
Expand Down Expand Up @@ -311,17 +313,31 @@ bool str_isalnum(const std::string& s)
}

// Replace all occurences of one string with another
void str_replaceAll(std::string& str, const std::string& from, const std::string& to)
void str_replaceAll(std::string& str, const std::string& from, const std::string& to, size_t start_pos)
{
if (from.empty() || str.empty())
return;
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // Continue search only _after_ the replacement position
}
}

// @brief Replace a potentially wrong decimal point
bool str_correctDecimalPt (std::string& str, size_t start_pos)
{
// If the locale is not set to using a decimal point we might end up with a comma in the coordinates, which OpenSky doesn't like
// Setting the locale is no option due to immediate impact on all threads,
// so we retrospectively replace the wrong delimiter
const lconv* pL = localeconv();
if (pL && pL->decimal_point && std::strcmp(pL->decimal_point, "."))
{
str_replaceAll(str, pL->decimal_point, ".", start_pos);
return true;
}
return false;
}

// Cut off everything after `from` from `s`, `from` including
std::string& cut_off(std::string& s, const std::string& from)
{
Expand Down Expand Up @@ -663,6 +679,78 @@ bool dequal ( const double d1, const double d2 )
((d1 + epsilon) > d2);
}

//
// MARK: Thread Handling
//

#if IBM
// Simulation of Linux' `uselocale()` function on Windows
locale_t uselocale(locale_t new_locale)
{
// Retrieve the current per thread locale setting
bool bIsPerThread = (_configthreadlocale(0) == _ENABLE_PER_THREAD_LOCALE);

// Retrieve the current thread-specific locale
locale_t old_locale = bIsPerThread ? _get_current_locale() : LC_GLOBAL_LOCALE;

if(new_locale == LC_GLOBAL_LOCALE)
{
// Restore the global locale
_configthreadlocale(_DISABLE_PER_THREAD_LOCALE);
}
else if(new_locale != NULL)
{
// Configure the thread to set the locale only for this thread
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);

// Set all locale categories
for(int i = LC_MIN; i <= LC_MAX; i++)
setlocale(i, new_locale->locinfo->lc_category[i].locale);
}

return old_locale;
}
#endif

// Defines thread's name and sets the thread's locale
ThreadSettings::ThreadSettings ([[maybe_unused]] const char* sThreadName,
int localeMask,
const char* sLocaleName)
{
// --- Set thread's name ---
#if IBM
// This might not work on older Windows version, which is why we don't publish it in release builds
#ifdef DEBUG
wchar_t swThreadName[100];
std::mbstowcs(swThreadName, sThreadName, sizeof(swThreadName));
SetThreadDescription(GetCurrentThread(), swThreadName);
#endif

#elif APL
pthread_setname_np(sThreadName);
#elif LIN
pthread_setname_np(pthread_self(),sThreadName);
#endif

// --- Set thread's locale ---
if (localeMask && sLocaleName)
{
threadLocale = newlocale(localeMask, sLocaleName, NULL);
prevLocale = uselocale(threadLocale);
}
}

// Restores and cleans up locale
ThreadSettings::~ThreadSettings()
{
if (prevLocale)
uselocale(prevLocale);
if (threadLocale) {
freelocale(threadLocale);
threadLocale = locale_t(0);
}
}

//
//MARK: Callbacks
//
Expand Down
8 changes: 4 additions & 4 deletions Src/LTOpenGlider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,8 @@ std::string OpenGliderConnection::GetStatusText () const
// Main function for APRS connection, expected to be started in a thread
void OpenGliderConnection::APRSMain (const positionTy& pos, unsigned dist_km)
{
// This is a thread main function, set thread's name
SET_THREAD_NAME("LT_OGN_APRS");
// This is a communication thread's main function, set thread's name and C locale
ThreadSettings TS ("LT_OGN_APRS", LC_ALL_MASK);

try {
// open a TCP connection to APRS.glidernet.org
Expand Down Expand Up @@ -901,8 +901,8 @@ static size_t OGNAcListNetwCB(char *ptr, size_t, size_t nmemb, void* userdata)
/// @see http://ddb.glidernet.org/download/
static bool OGNAcListDoDownload ()
{
// This is a thread main function, set thread's name
SET_THREAD_NAME("LT_OGNAcList");
// This is a communication thread's main function, set thread's name and C locale
ThreadSettings TS ("LT_OGNAcList", LC_ALL_MASK);

bool bRet = false;
try {
Expand Down
8 changes: 4 additions & 4 deletions Src/LTRealTraffic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,8 @@ bool RealTrafficConnection::StopConnections()

void RealTrafficConnection::tcpConnection ()
{
// This is a thread main function, set thread's name
SET_THREAD_NAME("LT_RT_TCP");
// This is a communication thread's main function, set thread's name and C locale
ThreadSettings TS ("LT_RT_TCP", LC_ALL_MASK);

// sanity check: return in case of wrong status
if (!IsConnecting()) {
Expand Down Expand Up @@ -471,8 +471,8 @@ void RealTrafficConnection::SendUsersPlanePos()
// forwards that to the flight data
void RealTrafficConnection::udpListen ()
{
// This is a thread main function, set thread's name
SET_THREAD_NAME("LT_RT_UDP");
// This is a communication thread's main function, set thread's name and C locale
ThreadSettings TS ("LT_RT_UDP", LC_ALL_MASK);

// sanity check: return in case of wrong status
if (!IsConnecting()) {
Expand Down
4 changes: 2 additions & 2 deletions Src/LTVersion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ size_t FetchVersionCB(char *ptr, size_t, size_t nmemb, void* userdata)
// This function would block. Idea is to call it in a thread like with std::async
bool FetchXPlaneOrgVersion ()
{
// This is a thread main function, set thread's name
SET_THREAD_NAME("LT_Version");
// This is a communication thread's main function, set thread's name and C locale
ThreadSettings TS ("LT_Version", LC_ALL_MASK);

char curl_errtxt[CURL_ERROR_SIZE];
std::string readBuf;
Expand Down
6 changes: 3 additions & 3 deletions Src/LTWeather.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,9 @@ size_t WeatherFetchCB(char *ptr, size_t, size_t nmemb, void* userdata)
// This function would block. Idea is to call it in a thread like with std::async
bool WeatherFetch (float _lat, float _lon, float _radius_nm)
{
// This is a thread main function, set thread's name
SET_THREAD_NAME("LT_Weather");
// This is a communication thread's main function, set thread's name and C locale
ThreadSettings TS ("LT_Weather", LC_ALL_MASK);

bool bRet = false;
try {
char curl_errtxt[CURL_ERROR_SIZE];
Expand Down
5 changes: 5 additions & 0 deletions docs/readme.html
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ <h3>v3.3.0</h3>
<i>Note 2:</i> Looks like particles, hence contrails, don't show beyond a certain distance from the camera,
probably some <a href="https://en.wikipedia.org/wiki/Level_of_detail_(computer_graphics)">LoD</a> optimization by X-Plane; in my tests around 13nm.
</li>
<li>
Fixed <a href="https://github.com/TwinFan/LiveTraffic/issues/244">#244</a>:
URL formatting and JSON parsing robust against changes to
locale/formatting settings in system or by other plugins.
</li>
</ul>

<h3>v3.2.2</h3>
Expand Down

0 comments on commit b6fb7bb

Please sign in to comment.