Index: source/test/intltest/tsmthred.cpp |
diff --git a/source/test/intltest/tsmthred.cpp b/source/test/intltest/tsmthred.cpp |
index e253235fc69f8456500cc291f610449851fbd675..f63990118a1d5451c53f31f16e61146749ff085f 100644 |
--- a/source/test/intltest/tsmthred.cpp |
+++ b/source/test/intltest/tsmthred.cpp |
@@ -1,6 +1,6 @@ |
/******************************************************************** |
* COPYRIGHT: |
- * Copyright (c) 1999-2013, International Business Machines Corporation and |
+ * Copyright (c) 1999-2014, International Business Machines Corporation and |
* others. All Rights Reserved. |
********************************************************************/ |
@@ -27,7 +27,10 @@ |
#include "intltest.h" |
#include "tsmthred.h" |
#include "unicode/ushape.h" |
- |
+#include "unicode/translit.h" |
+#include "sharedobject.h" |
+#include "unifiedcache.h" |
+#include "uassert.h" |
#if U_PLATFORM_USES_ONLY_WIN32_API |
/* Prefer native Windows APIs even if POSIX is implemented (i.e., on Cygwin). */ |
@@ -38,8 +41,6 @@ |
# undef POSIX |
#endif |
- |
-#define LENGTHOF(array) (int32_t)(sizeof(array)/sizeof((array)[0])) |
/* Needed by z/OS to get usleep */ |
#if U_PLATFORM == U_PF_OS390 |
#define __DOT1 1 |
@@ -188,14 +189,32 @@ void MultithreadTest::runIndexedTest( int32_t index, UBool exec, |
} |
break; |
- case 5: |
+ case 5: |
name = "TestArabicShapingThreads"; |
if (exec) { |
TestArabicShapingThreads(); |
} |
break; |
- |
+ case 6: |
+ name = "TestAnyTranslit"; |
+ if (exec) { |
+ TestAnyTranslit(); |
+ } |
+ break; |
+ |
+ case 7: |
+ name = "TestConditionVariables"; |
+ if (exec) { |
+ TestConditionVariables(); |
+ } |
+ break; |
+ case 8: |
+ name = "TestUnifiedCache"; |
+ if (exec) { |
+ TestUnifiedCache(); |
+ } |
+ break; |
default: |
name = ""; |
break; //needed to end loop |
@@ -257,7 +276,7 @@ private: |
IntlTest inteltst = IntlTest(); |
status = U_ZERO_ERROR; |
- length = u_shapeArabic(src, -1, dst, LENGTHOF(dst), |
+ length = u_shapeArabic(src, -1, dst, UPRV_LENGTHOF(dst), |
U_SHAPE_LETTERS_SHAPE|U_SHAPE_SEEN_TWOCELL_NEAR, &status); |
if(U_FAILURE(status)) { |
inteltst.errln("Fail: status %s\n", u_errorName(status)); |
@@ -265,7 +284,7 @@ private: |
} else if(length!=2) { |
inteltst.errln("Fail: len %d expected 3\n", length); |
return FALSE; |
- } else if(u_strncmp(dst,dst_old,LENGTHOF(dst))) { |
+ } else if(u_strncmp(dst,dst_old,UPRV_LENGTHOF(dst))) { |
inteltst.errln("Fail: got U+%04X U+%04X expected U+%04X U+%04X\n", |
dst[0],dst[1],dst_old[0],dst_old[1]); |
return FALSE; |
@@ -274,7 +293,7 @@ private: |
//"Trying new tail |
status = U_ZERO_ERROR; |
- length = u_shapeArabic(src, -1, dst, LENGTHOF(dst), |
+ length = u_shapeArabic(src, -1, dst, UPRV_LENGTHOF(dst), |
U_SHAPE_LETTERS_SHAPE|U_SHAPE_SEEN_TWOCELL_NEAR|U_SHAPE_TAIL_NEW_UNICODE, &status); |
if(U_FAILURE(status)) { |
inteltst.errln("Fail: status %s\n", u_errorName(status)); |
@@ -282,7 +301,7 @@ private: |
} else if(length!=2) { |
inteltst.errln("Fail: len %d expected 3\n", length); |
return FALSE; |
- } else if(u_strncmp(dst,dst_new,LENGTHOF(dst))) { |
+ } else if(u_strncmp(dst,dst_new,UPRV_LENGTHOF(dst))) { |
inteltst.errln("Fail: got U+%04X U+%04X expected U+%04X U+%04X\n", |
dst[0],dst[1],dst_new[0],dst_new[1]); |
return FALSE; |
@@ -629,7 +648,6 @@ UnicodeString showDifference(const UnicodeString& expected, const UnicodeString& |
const int kFormatThreadIterations = 100; // # of iterations per thread |
const int kFormatThreadThreads = 10; // # of threads to spawn |
-const int kFormatThreadPatience = 60; // time in seconds to wait for all threads |
#if !UCONFIG_NO_FORMATTING |
@@ -961,7 +979,7 @@ public: |
countryToCheck= Locale("","BF"); |
currencyToCheck= 2.32; |
expected= CharsToUnicodeString( |
- "1:A customer in Burkina Faso is receiving a #8 error - U_INDEX_OUTOFBOUNDS_ERROR. Their telephone call is costing 2,32\\u00A0DEM."); |
+ "1:A customer in Burkina Faso is receiving a #8 error - U_INDEX_OUTOFBOUNDS_ERROR. Their telephone call is costing 2,32\\u00A0DM."); |
break; |
case 2: |
statusToCheck= U_MEMORY_ALLOCATION_ERROR; |
@@ -1521,4 +1539,266 @@ cleanupAndReturn: |
} |
} |
+ |
+// Test for ticket #10673, race in cache code in AnyTransliterator. |
+// It's difficult to make the original unsafe code actually fail, but |
+// this test will fairly reliably take the code path for races in |
+// populating the cache. |
+ |
+#if !UCONFIG_NO_TRANSLITERATION |
+class TxThread: public SimpleThread { |
+ private: |
+ Transliterator *fSharedTranslit; |
+ public: |
+ UBool fSuccess; |
+ TxThread(Transliterator *tx) : fSharedTranslit(tx), fSuccess(FALSE) {}; |
+ ~TxThread(); |
+ void run(); |
+}; |
+ |
+TxThread::~TxThread() {} |
+void TxThread::run() { |
+ UnicodeString greekString("\\u03B4\\u03B9\\u03B1\\u03C6\\u03BF\\u03C1\\u03B5\\u03C4\\u03B9\\u03BA\\u03BF\\u03CD\\u03C2"); |
+ greekString = greekString.unescape(); |
+ fSharedTranslit->transliterate(greekString); |
+ fSuccess = greekString[0] == 0x64; // 'd'. The whole transliterated string is "diaphoretikous" (accented u). |
+} |
+#endif |
+ |
+ |
+void MultithreadTest::TestAnyTranslit() { |
+#if !UCONFIG_NO_TRANSLITERATION |
+ UErrorCode status = U_ZERO_ERROR; |
+ LocalPointer<Transliterator> tx(Transliterator::createInstance("Any-Latin", UTRANS_FORWARD, status)); |
+ if (U_FAILURE(status)) { |
+ dataerrln("File %s, Line %d: Error, status = %s", __FILE__, __LINE__, u_errorName(status)); |
+ return; |
+ } |
+ TxThread * threads[4]; |
+ int32_t i; |
+ for (i=0; i<4; i++) { |
+ threads[i] = new TxThread(tx.getAlias()); |
+ } |
+ for (i=0; i<4; i++) { |
+ threads[i]->start(); |
+ } |
+ int32_t patience = 100; |
+ UBool success; |
+ UBool someThreadRunning; |
+ do { |
+ someThreadRunning = FALSE; |
+ success = TRUE; |
+ for (i=0; i<4; i++) { |
+ if (threads[i]->isRunning()) { |
+ someThreadRunning = TRUE; |
+ SimpleThread::sleep(10); |
+ break; |
+ } else { |
+ if (threads[i]->fSuccess == FALSE) { |
+ success = FALSE; |
+ } |
+ } |
+ } |
+ } while (someThreadRunning && --patience > 0); |
+ |
+ if (patience <= 0) { |
+ errln("File %s, Line %d: Error, one or more threads did not complete.", __FILE__, __LINE__); |
+ } |
+ if (success == FALSE) { |
+ errln("File %s, Line %d: Error, transliteration result incorrect.", __FILE__, __LINE__); |
+ } |
+ |
+ for (i=0; i<4; i++) { |
+ delete threads[i]; |
+ } |
+#endif // !UCONFIG_NO_TRANSLITERATION |
+} |
+ |
+ |
+// Condition Variables Test |
+// Create a swarm of threads. |
+// Using a mutex and a condition variables each thread |
+// Increments a global count of started threads. |
+// Broadcasts that it has started. |
+// Waits on the condition that all threads have started. |
+// Increments a global count of finished threads. |
+// Waits on the condition that all threads have finished. |
+// Exits. |
+ |
+class CondThread: public SimpleThread { |
+ public: |
+ CondThread() :fFinished(false) {}; |
+ ~CondThread() {}; |
+ void run(); |
+ bool fFinished; |
+}; |
+ |
+static UMutex gCTMutex = U_MUTEX_INITIALIZER; |
+static UConditionVar gCTConditionVar = U_CONDITION_INITIALIZER; |
+int gConditionTestOne = 1; // Value one. Non-const, extern linkage to inhibit |
+ // compiler assuming a known value. |
+int gStartedThreads; |
+int gFinishedThreads; |
+static const int NUMTHREADS = 10; |
+ |
+static MultithreadTest *gThisTest = NULL; // Make test frame work functions available to |
+ // non-member functions. |
+ |
+// Worker thread function. |
+void CondThread::run() { |
+ umtx_lock(&gCTMutex); |
+ gStartedThreads += gConditionTestOne; |
+ umtx_condBroadcast(&gCTConditionVar); |
+ |
+ while (gStartedThreads < NUMTHREADS) { |
+ if (gFinishedThreads != 0) { |
+ gThisTest->errln("File %s, Line %d: Error, gStartedThreads = %d, gFinishedThreads = %d", |
+ __FILE__, __LINE__, gStartedThreads, gFinishedThreads); |
+ } |
+ umtx_condWait(&gCTConditionVar, &gCTMutex); |
+ } |
+ |
+ gFinishedThreads += gConditionTestOne; |
+ fFinished = true; |
+ umtx_condBroadcast(&gCTConditionVar); |
+ |
+ while (gFinishedThreads < NUMTHREADS) { |
+ umtx_condWait(&gCTConditionVar, &gCTMutex); |
+ } |
+ umtx_unlock(&gCTMutex); |
+} |
+ |
+void MultithreadTest::TestConditionVariables() { |
+ gThisTest = this; |
+ gStartedThreads = 0; |
+ gFinishedThreads = 0; |
+ int i; |
+ |
+ umtx_lock(&gCTMutex); |
+ CondThread *threads[NUMTHREADS]; |
+ for (i=0; i<NUMTHREADS; ++i) { |
+ threads[i] = new CondThread; |
+ threads[i]->start(); |
+ } |
+ |
+ while (gStartedThreads < NUMTHREADS) { |
+ umtx_condWait(&gCTConditionVar, &gCTMutex); |
+ } |
+ |
+ while (gFinishedThreads < NUMTHREADS) { |
+ umtx_condWait(&gCTConditionVar, &gCTMutex); |
+ } |
+ |
+ umtx_unlock(&gCTMutex); |
+ |
+ for (i=0; i<NUMTHREADS; ++i) { |
+ if (!threads[i]->fFinished) { |
+ errln("File %s, Line %d: Error, threads[%d]->fFinished == false", __FILE__, __LINE__, i); |
+ } |
+ delete threads[i]; |
+ } |
+} |
+ |
+static const char *gCacheLocales[] = {"en_US", "en_GB", "fr_FR", "fr"}; |
+static int32_t gObjectsCreated = 0; |
+static const int32_t CACHE_LOAD = 3; |
+ |
+class UCTMultiThreadItem : public SharedObject { |
+ public: |
+ char *value; |
+ UCTMultiThreadItem(const char *x) : value(NULL) { |
+ value = uprv_strdup(x); |
+ } |
+ virtual ~UCTMultiThreadItem() { |
+ uprv_free(value); |
+ } |
+}; |
+ |
+U_NAMESPACE_BEGIN |
+ |
+template<> U_EXPORT |
+const UCTMultiThreadItem *LocaleCacheKey<UCTMultiThreadItem>::createObject( |
+ const void * /*unused*/, UErrorCode & /* status */) const { |
+ // Since multiple threads are hitting the cache for the first time, |
+ // no objects should be created yet. |
+ umtx_lock(&gCTMutex); |
+ if (gObjectsCreated != 0) { |
+ gThisTest->errln("Expected no objects to be created yet."); |
+ } |
+ umtx_unlock(&gCTMutex); |
+ |
+ // Big, expensive object that takes 1 second to create. |
+ SimpleThread::sleep(1000); |
+ |
+ // Log that we created an object. |
+ umtx_lock(&gCTMutex); |
+ ++gObjectsCreated; |
+ umtx_unlock(&gCTMutex); |
+ UCTMultiThreadItem *result = new UCTMultiThreadItem(fLoc.getName()); |
+ result->addRef(); |
+ return result; |
+} |
+ |
+U_NAMESPACE_END |
+ |
+class UnifiedCacheThread: public SimpleThread { |
+ public: |
+ UnifiedCacheThread(const char *loc) : fLoc(loc) {}; |
+ ~UnifiedCacheThread() {}; |
+ void run(); |
+ const char *fLoc; |
+}; |
+ |
+void UnifiedCacheThread::run() { |
+ UErrorCode status = U_ZERO_ERROR; |
+ const UnifiedCache *cache = UnifiedCache::getInstance(status); |
+ U_ASSERT(status == U_ZERO_ERROR); |
+ const UCTMultiThreadItem *item = NULL; |
+ cache->get(LocaleCacheKey<UCTMultiThreadItem>(fLoc), item, status); |
+ U_ASSERT(item != NULL); |
+ if (uprv_strcmp(fLoc, item->value)) { |
+ gThisTest->errln("Expected %s, got %s", fLoc, item->value); |
+ } |
+ item->removeRef(); |
+ |
+ // Mark this thread as finished |
+ umtx_lock(&gCTMutex); |
+ ++gFinishedThreads; |
+ umtx_condBroadcast(&gCTConditionVar); |
+ umtx_unlock(&gCTMutex); |
+} |
+ |
+void MultithreadTest::TestUnifiedCache() { |
+ UErrorCode status = U_ZERO_ERROR; |
+ const UnifiedCache *cache = UnifiedCache::getInstance(status); |
+ U_ASSERT(cache != NULL); |
+ cache->flush(); |
+ gThisTest = this; |
+ gFinishedThreads = 0; |
+ gObjectsCreated = 0; |
+ |
+ UnifiedCacheThread *threads[CACHE_LOAD][UPRV_LENGTHOF(gCacheLocales)]; |
+ for (int32_t i=0; i<CACHE_LOAD; ++i) { |
+ for (int32_t j=0; j<UPRV_LENGTHOF(gCacheLocales); ++j) { |
+ threads[i][j] = new UnifiedCacheThread(gCacheLocales[j]); |
+ threads[i][j]->start(); |
+ } |
+ } |
+ // Wait on all the threads to complete verify that LENGTHOF(gCacheLocales) |
+ // objects were created. |
+ umtx_lock(&gCTMutex); |
+ while (gFinishedThreads < CACHE_LOAD*UPRV_LENGTHOF(gCacheLocales)) { |
+ umtx_condWait(&gCTConditionVar, &gCTMutex); |
+ } |
+ assertEquals("Objects created", UPRV_LENGTHOF(gCacheLocales), gObjectsCreated); |
+ umtx_unlock(&gCTMutex); |
+ |
+ // clean up threads |
+ for (int32_t i=0; i<CACHE_LOAD; ++i) { |
+ for (int32_t j=0; j<UPRV_LENGTHOF(gCacheLocales); ++j) { |
+ delete threads[i][j]; |
+ } |
+ } |
+} |
+ |
#endif // ICU_USE_THREADS |