Skip to content

Commit

Permalink
Thread-local locale settings, fixes #244
Browse files Browse the repository at this point in the history
  • Loading branch information
TwinFan committed Nov 5, 2022
1 parent ba5d7ae commit f7e021e
Show file tree
Hide file tree
Showing 13 changed files with 155 additions and 57 deletions.
60 changes: 46 additions & 14 deletions Include/LiveTraffic.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@
#include <cstring>
#include <ctime>
#include <cassert>
#include <clocale>
#include <locale>
#if APL
#include <xlocale.h>
#endif

// Windows
#if IBM
Expand Down Expand Up @@ -398,6 +401,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 +434,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 */
11 changes: 3 additions & 8 deletions Src/LTADSBEx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,7 @@ std::string ADSBExchangeConnection::GetURL (const positionTy& pos)
else
snprintf(url, sizeof(url), ADSBEX_URL, pos.lat(), pos.lon(),
dataRefs.GetFdStdDistance_nm());
std::string sUrl(url);

// 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
str_correctDecimalPt(sUrl, sUrl.find("/lat/"));

return sUrl;
return std::string(url);
}

// update shared flight data structures with received flight data
Expand Down Expand Up @@ -617,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("LT_TestADSBEx", LC_ALL_MASK);

bool bResult = false;
char curl_errtxt[CURL_ERROR_SIZE];
Expand Down
4 changes: 2 additions & 2 deletions Src/LTApt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2378,8 +2378,8 @@ void AsyncReadApt (positionTy ctr, double radius)
{
static size_t lenSceneryLnBegin = strlen(APTDAT_SCENERY_LN_BEGIN);

// This is a thread main function, set thread's name
SET_THREAD_NAME("LT_ReadApt");
// This is a thread's main function, set thread's name and C locale
ThreadSettings("LT_ReadApt", LC_ALL_MASK);

// To avoid costly distance calculations we define a bounding box
// just by calculating lat/lon values north/east/south/west of given pos
Expand Down
8 changes: 5 additions & 3 deletions Src/LTChannel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -694,9 +694,9 @@ 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("LT_Channels", LC_ALL_MASK);
while ( !bFDMainStop )
{
// basis for determining when to be called next
Expand Down Expand Up @@ -774,6 +774,8 @@ void LTFlightDataSelectAc ()
lk.unlock();
}
}

// Restore previous locale and clean up
}

// called from main thread to start showing aircraft
Expand Down
4 changes: 2 additions & 2 deletions Src/LTFlightData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1316,8 +1316,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 thread's main function, set thread's name and C locale
ThreadSettings("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("LT_ForeFlight", LC_ALL_MASK);

//
// *** open the UDP socket ***
Expand Down
74 changes: 73 additions & 1 deletion Src/LTMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ 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 std::lconv* pL = localeconv();
const lconv* pL = localeconv();
if (pL && pL->decimal_point && std::strcmp(pL->decimal_point, "."))
{
str_replaceAll(str, pL->decimal_point, ".", start_pos);
Expand Down Expand Up @@ -680,6 +680,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
17 changes: 7 additions & 10 deletions Src/LTOpenGlider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,7 @@ std::string OpenGliderConnection::GetURL (const positionTy& pos)
box.se.lat(), // lamin
box.se.lon(), // lomax
box.nw.lon()); // lomin
std::string sUrl(url);
// 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
str_correctDecimalPt(sUrl, sUrl.find('?'));
return sUrl;
return std::string(url);
} else {
// otherwise (and by default) we are to use the direct APRS connection
APRSStartUpdate(pos, (unsigned)dataRefs.GetFdStdDistance_km());
Expand Down Expand Up @@ -335,9 +332,9 @@ 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("LT_OGN_APRS", LC_ALL_MASK);

try {
// open a TCP connection to APRS.glidernet.org
aprsLastData = NAN;
Expand Down Expand Up @@ -904,9 +901,9 @@ 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("LT_OGNAcList", LC_ALL_MASK);

bool bRet = false;
try {
char curl_errtxt[CURL_ERROR_SIZE];
Expand Down
7 changes: 1 addition & 6 deletions Src/LTOpenSky.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,7 @@ std::string OpenSkyConnection::GetURL (const positionTy& pos)
box.nw.lon(), // lomin
box.nw.lat(), // lamax
box.se.lon() ); // lomax
std::string sUrl(url);

// 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
str_correctDecimalPt(sUrl, sUrl.find('?'));

return sUrl;
return std::string(url);
}

// update shared flight data structures with received flight data
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("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("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("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("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 @@ -148,6 +148,11 @@ <h3>v3.1.2</h3>
refresh rate above a configured height AGL works no matter
which "Live Data Refresh" is configured.
</li>
<li>
Fixed <a href="https://github.com/TwinFan/LiveTraffic/issues/244">#244</a>,
making all network communications resistent to locales
specifying a non-dot decimal point, an issue that surfaced with XP12 only.
</li>
</ul>

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

0 comments on commit f7e021e

Please sign in to comment.