ICU-10345 make formatting of a Formattable currency thread safe.. by cloning. makes unum_formatDoubleCurrency also slow.

X-SVN-Rev: 34200
This commit is contained in:
Steven R. Loomis 2013-09-05 05:32:56 +00:00
parent 7a9480becc
commit e5fac0714c
3 changed files with 177 additions and 29 deletions

View 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;
}
ArgExtractor::ArgExtractor(const NumberFormat& nf, const Formattable& obj, UErrorCode& status)
: ncnf((NumberFormat*) &nf), num(&obj), setCurr(FALSE) {
inline const UChar *
ArgExtractor::iso(void) const {
return save;
}
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

View 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");

View 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 */