+U_NAMESPACE_END
+
+class UnifiedCacheThread: public SimpleThread {
+ public:
+ UnifiedCacheThread(
+ const UnifiedCache *cache,
+ const char *loc,
+ const char *loc2) : fCache(cache), fLoc(loc), fLoc2(loc2) {};
+ ~UnifiedCacheThread() {};
+ void run();
+ void exerciseByLocale(const Locale &);
+ const UnifiedCache *fCache;
+ Locale fLoc;
+ Locale fLoc2;
+};
+
+void UnifiedCacheThread::exerciseByLocale(const Locale &locale) {
+ UErrorCode status = U_ZERO_ERROR;
+ const UCTMultiThreadItem *origItem = NULL;
+ fCache->get(
+ LocaleCacheKey<UCTMultiThreadItem>(locale), fCache, origItem, status);
+ U_ASSERT(U_SUCCESS(status));
+ if (uprv_strcmp(locale.getLanguage(), origItem->value)) {
+ IntlTest::gTest->errln(
+ "%s:%d Expected %s, got %s", __FILE__, __LINE__,
+ locale.getLanguage(),
+ origItem->value);
+ }
+
+ // Fetch the same item again many times. We should always get the same
+ // pointer since this client is already holding onto it
+ for (int32_t i = 0; i < 1000; ++i) {
+ const UCTMultiThreadItem *item = NULL;
+ fCache->get(
+ LocaleCacheKey<UCTMultiThreadItem>(locale), fCache, item, status);
+ if (item != origItem) {
+ IntlTest::gTest->errln(
+ "%s:%d Expected to get the same pointer",
+ __FILE__,
+ __LINE__);
+ }
+ if (item != NULL) {
+ item->removeRef();
+ }
+ }
+ origItem->removeRef();
+}
+
+void UnifiedCacheThread::run() {
+ // Run the exercise with 2 different locales so that we can exercise
+ // eviction more. If each thread exercises just one locale, then
+ // eviction can't start until the threads end.
+ exerciseByLocale(fLoc);
+ exerciseByLocale(fLoc2);
+}
+
+void MultithreadTest::TestUnifiedCache() {
+
+ // Start with our own local cache so that we have complete control
+ // and set the eviction policy to evict starting with 2 unused
+ // values
+ UErrorCode status = U_ZERO_ERROR;
+ UnifiedCache::getInstance(status);
+ UnifiedCache cache(status);
+ cache.setEvictionPolicy(2, 0, status);
+ U_ASSERT(U_SUCCESS(status));
+
+ 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) {
+ // Each thread works with a pair of locales.
+ threads[i][j] = new UnifiedCacheThread(
+ &cache, gCacheLocales[j], gCacheLocales2[j]);
+ threads[i][j]->start();
+ }
+ }
+
+ for (int32_t i=0; i<CACHE_LOAD; ++i) {
+ for (int32_t j=0; j<UPRV_LENGTHOF(gCacheLocales); ++j) {
+ threads[i][j]->join();
+ }
+ }
+ // Because of cache eviction, we can't assert exactly how many
+ // distinct objects get created over the course of this run.
+ // However we know that at least 8 objects get created because that
+ // is how many distinct languages we have in our test.
+ if (gObjectsCreated < 8) {
+ errln("%s:%d Too few objects created.", __FILE__, __LINE__);
+ }
+ // We know that each thread cannot create more than 2 objects in
+ // the cache, and there are UPRV_LENGTHOF(gCacheLocales) pairs of
+ // objects fetched from the cache. If the threads run in series because
+ // of eviction, at worst case each thread creates two objects.
+ if (gObjectsCreated > 2 * CACHE_LOAD * UPRV_LENGTHOF(gCacheLocales)) {
+ errln("%s:%d Too many objects created, got %d, expected %d", __FILE__, __LINE__, gObjectsCreated, 2 * CACHE_LOAD * UPRV_LENGTHOF(gCacheLocales));
+
+ }
+
+ assertEquals("unused values", 2, cache.unusedCount());
+
+ // 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];
+ }
+ }
+}
+
+#if !UCONFIG_NO_TRANSLITERATION
+//
+// BreakTransliterator Threading Test
+// This is a test for bug #11603. Test verified to fail prior to fix.
+//
+
+static const Transliterator *gSharedTransliterator;
+static const UnicodeString *gTranslitInput;
+static const UnicodeString *gTranslitExpected;
+
+class BreakTranslitThread: public SimpleThread {
+ public:
+ BreakTranslitThread() {};
+ ~BreakTranslitThread() {};
+ void run();
+};
+
+void BreakTranslitThread::run() {
+ for (int i=0; i<10; i++) {
+ icu::UnicodeString s(*gTranslitInput);
+ gSharedTransliterator->transliterate(s);
+ if (*gTranslitExpected != s) {
+ IntlTest::gTest->errln("%s:%d Transliteration threading failure.", __FILE__, __LINE__);
+ break;
+ }
+ }
+}
+
+void MultithreadTest::TestBreakTranslit() {
+ UErrorCode status = U_ZERO_ERROR;
+ UnicodeString input(
+ "\\u0E42\\u0E14\\u0E22\\u0E1E\\u0E37\\u0E49\\u0E19\\u0E10\\u0E32\\u0E19\\u0E41\\u0E25\\u0E49\\u0E27,");
+ input = input.unescape();
+ gTranslitInput = &input;
+
+ gSharedTransliterator = Transliterator::createInstance(
+ UNICODE_STRING_SIMPLE("Any-Latin; Lower; NFD; [:Diacritic:]Remove; NFC; Latin-ASCII;"), UTRANS_FORWARD, status);
+ if (!gSharedTransliterator) {
+ return;
+ }
+ TSMTHREAD_ASSERT_SUCCESS(status);
+
+ UnicodeString expected(*gTranslitInput);
+ gSharedTransliterator->transliterate(expected);
+ gTranslitExpected = &expected;
+
+ BreakTranslitThread threads[4];
+ for (int i=0; i<UPRV_LENGTHOF(threads); ++i) {
+ threads[i].start();
+ }
+ for (int i=0; i<UPRV_LENGTHOF(threads); ++i) {
+ threads[i].join();
+ }
+
+ delete gSharedTransliterator;
+ gTranslitInput = NULL;
+ gTranslitExpected = NULL;
+}
+
+
+class TestIncDecThread : public SimpleThread {
+public:
+ TestIncDecThread() { };
+ virtual void run();
+};
+
+static u_atomic_int32_t gIncDecCounter;
+
+void TestIncDecThread::run() {
+ umtx_atomic_inc(&gIncDecCounter);
+ for (int32_t i=0; i<5000000; ++i) {
+ umtx_atomic_inc(&gIncDecCounter);
+ umtx_atomic_dec(&gIncDecCounter);
+ }
+}
+
+void MultithreadTest::TestIncDec()
+{
+ static constexpr int NUM_THREADS = 4;
+ gIncDecCounter = 0;
+ TestIncDecThread threads[NUM_THREADS];
+ for (auto &thread:threads) {
+ thread.start();
+ }
+ for (auto &thread:threads) {
+ thread.join();
+ }
+ assertEquals("TestIncDec", NUM_THREADS, gIncDecCounter);
+}
+
+
+#endif /* !UCONFIG_NO_TRANSLITERATION */