]> granicus.if.org Git - icu/commitdiff
ICU-10345 make formatting of a Formattable currency thread safe.. by cloning. makes...
authorSteven R. Loomis <srl@icu-project.org>
Thu, 5 Sep 2013 05:32:56 +0000 (05:32 +0000)
committerSteven R. Loomis <srl@icu-project.org>
Thu, 5 Sep 2013 05:32:56 +0000 (05:32 +0000)
X-SVN-Rev: 34200

icu4c/source/i18n/numfmt.cpp
icu4c/source/test/cintltst/cnumtst.c
icu4c/source/test/intltest/tsmthred.cpp

index 35fc7723faa7b33e751a27254427ccfbc5bdff87..0e2aa64b2e374b73d28f68aaf807c345a1466eba 100644 (file)
@@ -433,24 +433,30 @@ NumberFormat::format(const StringPiece &decimalNum,
     return toAppendTo;
 }
 
-// -------------------------------------
+/**
+ *
 // Formats the number object and save the format
 // result in the toAppendTo string buffer.
 
 // utility to save/restore state, used in two overloads
 // of format(const Formattable&...) below.
-
+*
+* Old purpose of ArgExtractor was to avoid const. Not thread safe!
+*
+* keeping it around as a shim.
+*/
 class ArgExtractor {
-  NumberFormat *ncnf;
   const Formattable* num;
-  UBool setCurr;
   UChar save[4];
+  UBool fWasCurrency;
 
  public:
   ArgExtractor(const NumberFormat& nf, const Formattable& obj, UErrorCode& status);
   ~ArgExtractor();
 
   const Formattable* number(void) const;
+  const UChar *iso(void) const;
+  const UBool wasCurrency(void) const;
 };
 
 inline const Formattable*
@@ -458,33 +464,34 @@ ArgExtractor::number(void) const {
   return num;
 }
 
-// TODO: Thread safety problem? the function of ArgExtractor appears to be to temporarily
-//       setCurrency() on the number formatter, then restore it after the format() is complete.
-//       Another thread could be using the formatter.
+inline const UBool
+ArgExtractor::wasCurrency(void) const {
+  return fWasCurrency;
+}
+
+inline const UChar *
+ArgExtractor::iso(void) const {
+  return save;
+}
 
-ArgExtractor::ArgExtractor(const NumberFormat& nf, const Formattable& obj, UErrorCode& status)
-    : ncnf((NumberFormat*) &nf), num(&obj), setCurr(FALSE) {
+ArgExtractor::ArgExtractor(const NumberFormat& nf, const Formattable& obj, UErrorCode& /*status*/)
+  : num(&obj), fWasCurrency(FALSE) {
 
     const UObject* o = obj.getObject(); // most commonly o==NULL
     const CurrencyAmount* amt;
     if (o != NULL && (amt = dynamic_cast<const CurrencyAmount*>(o)) != NULL) {
         // getISOCurrency() returns a pointer to internal storage, so we
         // copy it to retain it across the call to setCurrency().
-        const UChar* curr = amt->getISOCurrency();
-        u_strcpy(save, nf.getCurrency());
-        setCurr = (u_strcmp(curr, save) != 0);
-        if (setCurr) {
-            ncnf->setCurrency(curr, status);
-        }
+        //const UChar* curr = amt->getISOCurrency();
+        u_strcpy(save, amt->getISOCurrency());
         num = &amt->getNumber();
+        fWasCurrency=TRUE;
+    } else {
+      save[0]=0;
     }
 }
 
 ArgExtractor::~ArgExtractor() {
-    if (setCurr) {
-        UErrorCode ok = U_ZERO_ERROR;
-        ncnf->setCurrency(save, ok); // always restore currency
-    }
 }
 
 UnicodeString& NumberFormat::format(const DigitList &number,
@@ -530,6 +537,16 @@ NumberFormat::format(const Formattable& obj,
 
     ArgExtractor arg(*this, obj, status);
     const Formattable *n = arg.number();
+    const UChar *iso = arg.iso();
+
+    if(arg.wasCurrency() && u_strcmp(iso, getCurrency())) {
+      // trying to format a different currency.
+      // Right now, we clone.
+      LocalPointer<NumberFormat> cloneFmt((NumberFormat*)this->clone());
+      cloneFmt->setCurrency(iso, status);
+      // next line should NOT recurse, because n is numeric whereas obj was a wrapper around currency amount.
+      return cloneFmt->format(*n, appendTo, pos, status);
+    }
 
     if (n->isNumeric() && n->getDigitList() != NULL) {
         // Decimal Number.  We will have a DigitList available if the value was
@@ -575,6 +592,16 @@ NumberFormat::format(const Formattable& obj,
 
     ArgExtractor arg(*this, obj, status);
     const Formattable *n = arg.number();
+    const UChar *iso = arg.iso();
+
+    if(arg.wasCurrency() && u_strcmp(iso, getCurrency())) {
+      // trying to format a different currency.
+      // Right now, we clone.
+      LocalPointer<NumberFormat> cloneFmt((NumberFormat*)this->clone());
+      cloneFmt->setCurrency(iso, status);
+      // next line should NOT recurse, because n is numeric whereas obj was a wrapper around currency amount.
+      return cloneFmt->format(*n, appendTo, posIter, status);
+    }
 
     if (n->isNumeric() && n->getDigitList() != NULL) {
         // Decimal Number
index 1398dc976328de3d2fcb6436d832be24aba3d077..8e6cfda084e43877751529d40e301fa6c6bddfdf 100644 (file)
@@ -290,8 +290,10 @@ static void TestNumberFormat()
     }
     if(result && u_strcmp(result, temp1)==0)
         log_verbose("Pass: Number Formatting using unum_formatDouble() Successful\n");
-    else
-        log_err("FAIL: Error in number formatting using unum_formatDouble()\n");
+    else {
+      log_err("FAIL: Error in number formatting using unum_formatDouble() - got '%s' expected '%s'\n",
+              aescstrdup(result, -1), aescstrdup(temp1, -1));
+    }
     if(pos2.beginIndex == 9 && pos2.endIndex == 11)
         log_verbose("Pass: Complete number formatting using unum_format() successful\n");
     else
@@ -336,12 +338,13 @@ static void TestNumberFormat()
         unum_formatDoubleCurrency(cur_def, a, temp, result, resultlength, &pos2, &status);
     }
     if (U_FAILURE(status)) {
-        log_err("Error in formatting using unum_formatDouble(.....): %s\n", myErrorName(status));
+        log_err("Error in formatting using unum_formatDoubleCurrency(.....): %s\n", myErrorName(status));
     }
     if (result && u_strcmp(result, temp1)==0) {
-        log_verbose("Pass: Number Formatting using unum_formatDouble() Successful\n");
+        log_verbose("Pass: Number Formatting using unum_formatDoubleCurrency() Successful\n");
     } else {
-        log_err("FAIL: Error in number formatting using unum_formatDouble()\n");
+        log_err("FAIL: Error in number formatting using unum_formatDoubleCurrency() - got '%s' expected '%s'\n",
+                aescstrdup(result, -1), aescstrdup(temp1, -1));
     }
     if (pos2.beginIndex == 1 && pos2.endIndex == 6) {
         log_verbose("Pass: Complete number formatting using unum_format() successful\n");
@@ -364,7 +367,7 @@ static void TestNumberFormat()
         if (d1!=a1) {
             log_err("Fail: Error in parsing currency, got %f, expected %f\n", d1, a1);
         } else {
-            log_verbose("Pass: parsed currency ammount successfully\n");
+            log_verbose("Pass: parsed currency amount successfully\n");
         }
         if (u_strcmp(temp2, temp)==0) {
             log_verbose("Pass: parsed correct currency\n");
index af221bab411344de9fa7a8cb915eead67e93324d..f469cfd66628e2cfd51ba7593d43784dddc165cd 100644 (file)
@@ -627,7 +627,7 @@ UnicodeString showDifference(const UnicodeString& expected, const UnicodeString&
 //
 //-------------------------------------------------------------------------------------------
 
-const int kFormatThreadIterations = 20;  // # of iterations per thread
+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
 
@@ -645,7 +645,7 @@ struct FormatThreadTestData
 
 // "Someone from {2} is receiving a #{0} error - {1}. Their telephone call is costing {3 number,currency}."
 
-void formatErrorMessage(UErrorCode &realStatus, const UnicodeString& pattern, const Locale& theLocale,
+static void formatErrorMessage(UErrorCode &realStatus, const UnicodeString& pattern, const Locale& theLocale,
                      UErrorCode inStatus0, /* statusString 1 */ const Locale &inCountry2, double currency3, // these numbers are the message arguments.
                      UnicodeString &result)
 {
@@ -679,6 +679,102 @@ void formatErrorMessage(UErrorCode &realStatus, const UnicodeString& pattern, co
     delete fmt;
 }
 
+/**
+ * Class for thread-safe (theoretically) format.
+ * 
+ *
+ * Its constructor, destructor, and init/fini are NOT thread safe.
+ */
+class ThreadSafeFormat {
+public:
+  /* give a unique offset to each thread */
+  ThreadSafeFormat();
+  UBool doStuff(int32_t offset, UnicodeString &appendErr, UErrorCode &status);
+private:
+  LocalPointer<NumberFormat> fFormat; // formtter - default constructed currency
+  Formattable  fYDDThing;    // Formattable currency - YDD
+  Formattable  fBBDThing;   // Formattable currency - BBD
+
+  // statics
+private:
+  static LocalPointer<NumberFormat> gFormat;
+  static NumberFormat *createFormat(UErrorCode &status);
+  static Formattable gYDDThing, gBBDThing;
+public:
+  static void init(UErrorCode &status); // avoid static init.
+  static void fini(UErrorCode &status); // avoid static fini
+};
+
+LocalPointer<NumberFormat> ThreadSafeFormat::gFormat;
+Formattable ThreadSafeFormat::gYDDThing;
+Formattable ThreadSafeFormat::gBBDThing;
+UnicodeString gYDDStr, gBBDStr;
+NumberFormat *ThreadSafeFormat::createFormat(UErrorCode &status) {
+  LocalPointer<NumberFormat> fmt(NumberFormat::createCurrencyInstance(Locale::getUS(), status));
+  return fmt.orphan();
+}
+
+
+static const UChar kYDD[] = { 0x59, 0x44, 0x44, 0x00 };
+static const UChar kBBD[] = { 0x42, 0x42, 0x44, 0x00 };
+static const UChar kUSD[] = { 0x55, 0x53, 0x44, 0x00 };
+
+void ThreadSafeFormat::init(UErrorCode &status) {
+  gFormat.adoptInstead(createFormat(status));
+  gYDDThing.adoptObject(new CurrencyAmount(123.456, kYDD, status));
+  gBBDThing.adoptObject(new CurrencyAmount(987.654, kBBD, status));
+  gFormat->format(gYDDThing, gYDDStr, NULL, status);
+  gFormat->format(gBBDThing, gBBDStr, NULL, status);
+}
+
+void ThreadSafeFormat::fini(UErrorCode &status) {
+  gFormat.orphan();
+}
+
+ThreadSafeFormat::ThreadSafeFormat() {
+}
+
+UBool ThreadSafeFormat::doStuff(int32_t offset, UnicodeString &appendErr, UErrorCode &status) {
+  UBool okay = TRUE;
+  if(fFormat.isNull()) {
+    fFormat.adoptInstead(createFormat(status));
+  }
+
+  if(u_strcmp(fFormat->getCurrency(), kUSD)) {
+    appendErr.append("fFormat currency != ")
+      .append(kUSD)
+      .append(", =")
+      .append(fFormat->getCurrency())
+      .append("! ");
+    okay = FALSE;
+  }
+
+  if(u_strcmp(gFormat->getCurrency(), kUSD)) {
+    appendErr.append("gFormat currency != ")
+      .append(kUSD)
+      .append(", =")
+      .append(gFormat->getCurrency())
+      .append("! ");
+    okay = FALSE;
+  }
+  UnicodeString str;
+  const UnicodeString *o=NULL;
+  Formattable f;
+  const NumberFormat *nf = NULL; // only operate on it as const.
+  switch(offset%4) {
+  case 0:  f = gYDDThing;  o = &gYDDStr;  nf = gFormat.getAlias();  break;
+  case 1:  f = gBBDThing;  o = &gBBDStr;  nf = gFormat.getAlias();  break;
+  case 2:  f = gYDDThing;  o = &gYDDStr;  nf = fFormat.getAlias();  break;
+  case 3:  f = gBBDThing;  o = &gBBDStr;  nf = fFormat.getAlias();  break;
+  }
+  nf->format(f, str, NULL, status);
+
+  if(*o != str) {
+    appendErr.append(showDifference(*o, str));
+    okay = FALSE;
+  }
+  return okay;
+}
 
 UBool U_CALLCONV isAcceptable(void *, const char *, const char *, const UDataInfo *) {
     return TRUE;
@@ -694,6 +790,8 @@ public:
     int     fNum;
     int     fTraceInfo;
 
+    ThreadSafeFormat fTSF;
+
     FormatThreadTest() // constructor is NOT multithread safe.
         : ThreadWithStatus(),
         fNum(0),
@@ -894,8 +992,16 @@ public:
                 error("PatternFormat: \n" + showDifference(expected,result));
                 goto cleanupAndReturn;
             }
+            // test the Thread Safe Format
+            UnicodeString appendErr;
+            if(!fTSF.doStuff(fNum, appendErr, status)) {
+              error(appendErr);
+              goto cleanupAndReturn;
+            }
         }   /*  end of for loop */
-        
+
+
+
 cleanupAndReturn:
         //  while (fNum == 4) {SimpleThread::sleep(10000);}   // Force a failure by preventing thread from finishing
         fTraceInfo = 2;
@@ -914,6 +1020,11 @@ void MultithreadTest::TestThreadedIntl()
     UBool   haveDisplayedInfo[kFormatThreadThreads];
     static const int32_t PATIENCE_SECONDS = 45;
 
+    UErrorCode threadSafeErr = U_ZERO_ERROR;
+
+    ThreadSafeFormat::init(threadSafeErr);
+    assertSuccess("initializing ThreadSafeFormat", threadSafeErr);
+
     //
     //  Create and start the test threads
     //
@@ -936,13 +1047,18 @@ void MultithreadTest::TestThreadedIntl()
     UBool   stillRunning;
     UDate startTime, endTime;
     startTime = Calendar::getNow();
+    double lastComplaint = 0;
     do {
         /*  Spin until the test threads  complete. */
         stillRunning = FALSE;
         endTime = Calendar::getNow();
-        if (((int32_t)(endTime - startTime)/U_MILLIS_PER_SECOND) > PATIENCE_SECONDS) {
+        double elapsedSeconds =  ((int32_t)(endTime - startTime)/U_MILLIS_PER_SECOND);
+        if (elapsedSeconds > PATIENCE_SECONDS) {
             errln("Patience exceeded. Test is taking too long.");
             return;
+        } else if((elapsedSeconds-lastComplaint) > 2.0) {
+            infoln("%.1f seconds elapsed (still waiting..)", elapsedSeconds);
+            lastComplaint = elapsedSeconds;
         }
         /*
          The following sleep must be here because the *BSD operating systems
@@ -967,6 +1083,8 @@ void MultithreadTest::TestThreadedIntl()
     //
     //  All threads have finished.
     //
+    ThreadSafeFormat::fini(threadSafeErr);
+    assertSuccess("finalizing ThreadSafeFormat", threadSafeErr);
 }
 #endif /* #if !UCONFIG_NO_FORMATTING */