ICU-11908 NumberingSystem, fix the memory management of static cache of numsys names.

Add thread safe cache initialization.
This commit is contained in:
Andy Heninger 2019-03-06 18:43:59 -08:00
parent 3a28fb7216
commit f7b36d39bc
5 changed files with 110 additions and 95 deletions

View file

@ -26,6 +26,8 @@
#include "unicode/numsys.h"
#include "cstring.h"
#include "uassert.h"
#include "ucln_in.h"
#include "umutex.h"
#include "uresimp.h"
#include "numsys_impl.h"
@ -266,75 +268,82 @@ UBool NumberingSystem::isAlgorithmic() const {
return ( algorithmic );
}
StringEnumeration* NumberingSystem::getAvailableNames(UErrorCode &status) {
// TODO(ticket #11908): Init-once static cache, with u_cleanup() callback.
static StringEnumeration* availableNames = nullptr;
namespace {
if (U_FAILURE(status)) {
return nullptr;
}
UVector* gNumsysNames = nullptr;
UInitOnce gNumSysInitOnce = U_INITONCE_INITIALIZER;
if ( availableNames == nullptr ) {
// TODO: Simple array of UnicodeString objects, based on length of table resource?
LocalPointer<UVector> numsysNames(new UVector(uprv_deleteUObject, nullptr, status), status);
if (U_FAILURE(status)) {
return nullptr;
}
UErrorCode rbstatus = U_ZERO_ERROR;
UResourceBundle *numberingSystemsInfo = ures_openDirect(nullptr, "numberingSystems", &rbstatus);
numberingSystemsInfo = ures_getByKey(numberingSystemsInfo, "numberingSystems", numberingSystemsInfo, &rbstatus);
if (U_FAILURE(rbstatus)) {
// Don't stomp on the catastrophic failure of OOM.
if (rbstatus == U_MEMORY_ALLOCATION_ERROR) {
status = rbstatus;
} else {
status = U_MISSING_RESOURCE_ERROR;
}
ures_close(numberingSystemsInfo);
return nullptr;
}
while ( ures_hasNext(numberingSystemsInfo) && U_SUCCESS(status) ) {
LocalUResourceBundlePointer nsCurrent(ures_getNextResource(numberingSystemsInfo, nullptr, &rbstatus));
if (rbstatus == U_MEMORY_ALLOCATION_ERROR) {
status = rbstatus; // we want to report OOM failure back to the caller.
break;
}
const char *nsName = ures_getKey(nsCurrent.getAlias());
LocalPointer<UnicodeString> newElem(new UnicodeString(nsName, -1, US_INV), status);
if (U_SUCCESS(status)) {
numsysNames->addElement(newElem.getAlias(), status);
if (U_SUCCESS(status)) {
newElem.orphan(); // on success, the numsysNames vector owns newElem.
}
}
}
ures_close(numberingSystemsInfo);
if (U_FAILURE(status)) {
return nullptr;
}
availableNames = new NumsysNameEnumeration(numsysNames.getAlias(), status);
if (availableNames == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
numsysNames.orphan(); // The names got adopted.
}
return availableNames;
U_CFUNC UBool U_CALLCONV numSysCleanup() {
delete gNumsysNames;
gNumsysNames = nullptr;
gNumSysInitOnce.reset();
return true;
}
NumsysNameEnumeration::NumsysNameEnumeration(UVector *numsysNames, UErrorCode& /*status*/) {
pos=0;
fNumsysNames = numsysNames;
U_CFUNC void initNumsysNames(UErrorCode &status) {
U_ASSERT(gNumsysNames == nullptr);
ucln_i18n_registerCleanup(UCLN_I18N_NUMSYS, numSysCleanup);
// TODO: Simple array of UnicodeString objects, based on length of table resource?
LocalPointer<UVector> numsysNames(new UVector(uprv_deleteUObject, nullptr, status), status);
if (U_FAILURE(status)) {
return;
}
UErrorCode rbstatus = U_ZERO_ERROR;
UResourceBundle *numberingSystemsInfo = ures_openDirect(nullptr, "numberingSystems", &rbstatus);
numberingSystemsInfo =
ures_getByKey(numberingSystemsInfo, "numberingSystems", numberingSystemsInfo, &rbstatus);
if (U_FAILURE(rbstatus)) {
// Don't stomp on the catastrophic failure of OOM.
if (rbstatus == U_MEMORY_ALLOCATION_ERROR) {
status = rbstatus;
} else {
status = U_MISSING_RESOURCE_ERROR;
}
ures_close(numberingSystemsInfo);
return;
}
while ( ures_hasNext(numberingSystemsInfo) && U_SUCCESS(status) ) {
LocalUResourceBundlePointer nsCurrent(ures_getNextResource(numberingSystemsInfo, nullptr, &rbstatus));
if (rbstatus == U_MEMORY_ALLOCATION_ERROR) {
status = rbstatus; // we want to report OOM failure back to the caller.
break;
}
const char *nsName = ures_getKey(nsCurrent.getAlias());
LocalPointer<UnicodeString> newElem(new UnicodeString(nsName, -1, US_INV), status);
if (U_SUCCESS(status)) {
numsysNames->addElement(newElem.getAlias(), status);
if (U_SUCCESS(status)) {
newElem.orphan(); // on success, the numsysNames vector owns newElem.
}
}
}
ures_close(numberingSystemsInfo);
if (U_SUCCESS(status)) {
gNumsysNames = numsysNames.orphan();
}
return;
}
} // end anonymous namespace
StringEnumeration* NumberingSystem::getAvailableNames(UErrorCode &status) {
umtx_initOnce(gNumSysInitOnce, &initNumsysNames, status);
LocalPointer<StringEnumeration> result(new NumsysNameEnumeration(status), status);
return result.orphan();
}
NumsysNameEnumeration::NumsysNameEnumeration(UErrorCode& status) : pos(0) {
(void)status;
}
const UnicodeString*
NumsysNameEnumeration::snext(UErrorCode& status) {
if (U_SUCCESS(status) && (fNumsysNames != nullptr) && (pos < fNumsysNames->size())) {
return (const UnicodeString*)fNumsysNames->elementAt(pos++);
if (U_SUCCESS(status) && (gNumsysNames != nullptr) && (pos < gNumsysNames->size())) {
return (const UnicodeString*)gNumsysNames->elementAt(pos++);
}
return nullptr;
}
@ -346,11 +355,10 @@ NumsysNameEnumeration::reset(UErrorCode& /*status*/) {
int32_t
NumsysNameEnumeration::count(UErrorCode& /*status*/) const {
return (fNumsysNames==nullptr) ? 0 : fNumsysNames->size();
return (gNumsysNames==nullptr) ? 0 : gNumsysNames->size();
}
NumsysNameEnumeration::~NumsysNameEnumeration() {
delete fNumsysNames;
}
U_NAMESPACE_END

View file

@ -26,18 +26,16 @@ U_NAMESPACE_BEGIN
class NumsysNameEnumeration : public StringEnumeration {
public:
// NumsysNameEnumeration instance adopts numsysNames
NumsysNameEnumeration(UVector *numsysNames, UErrorCode& status);
NumsysNameEnumeration(UErrorCode& status);
virtual ~NumsysNameEnumeration();
static UClassID U_EXPORT2 getStaticClassID(void);
virtual UClassID getDynamicClassID(void) const;
virtual const UnicodeString* snext(UErrorCode& status);
virtual void reset(UErrorCode& status);
virtual int32_t count(UErrorCode& status) const;
virtual UClassID getDynamicClassID(void) const override;
virtual const UnicodeString* snext(UErrorCode& status) override;
virtual void reset(UErrorCode& status) override;
virtual int32_t count(UErrorCode& status) const override;
private:
int32_t pos;
UVector *fNumsysNames = nullptr;
};
U_NAMESPACE_END

View file

@ -60,6 +60,7 @@ typedef enum ECleanupI18NType {
UCLN_I18N_CDFINFO,
UCLN_I18N_REGION,
UCLN_I18N_LIST_FORMATTER,
UCLN_I18N_NUMSYS,
UCLN_I18N_COUNT /* This must be last */
} ECleanupI18NType;

View file

@ -109,6 +109,10 @@ public:
/**
* Return a StringEnumeration over all the names of numbering systems known to ICU.
* The numbering system names will be in alphabetical (invariant) order.
*
* The returned StringEnumeration is owned by the caller, who must delete it when
* finished with it.
*
* @stable ICU 4.2
*/
static StringEnumeration * U_EXPORT2 getAvailableNames(UErrorCode& status);

View file

@ -2500,33 +2500,37 @@ static void TestUNumberingSystem(void) {
}
}
status = U_ZERO_ERROR;
uenum = unumsys_openAvailableNames(&status);
if ( U_SUCCESS(status) ) {
int32_t numsysCount = 0;
// sanity check for a couple of number systems that must be in the enumeration
UBool foundLatn = FALSE;
UBool foundArab = FALSE;
while ( (numsys = uenum_next(uenum, NULL, &status)) != NULL && U_SUCCESS(status) ) {
status = U_ZERO_ERROR;
unumsys = unumsys_openByName(numsys, &status);
if ( U_SUCCESS(status) ) {
numsysCount++;
if ( uprv_strcmp(numsys, "latn") ) foundLatn = TRUE;
if ( uprv_strcmp(numsys, "arab") ) foundArab = TRUE;
unumsys_close(unumsys);
} else {
log_err("unumsys_openAvailableNames includes %s but unumsys_openByName on it fails with status %s\n",
numsys, myErrorName(status));
for (int i=0; i<3; ++i) {
// Run the test of unumsys_openAvailableNames() multiple times.
// Helps verify the management of the internal cache of the names.
status = U_ZERO_ERROR;
uenum = unumsys_openAvailableNames(&status);
if ( U_SUCCESS(status) ) {
int32_t numsysCount = 0;
// sanity check for a couple of number systems that must be in the enumeration
UBool foundLatn = FALSE;
UBool foundArab = FALSE;
while ( (numsys = uenum_next(uenum, NULL, &status)) != NULL && U_SUCCESS(status) ) {
status = U_ZERO_ERROR;
unumsys = unumsys_openByName(numsys, &status);
if ( U_SUCCESS(status) ) {
numsysCount++;
if ( uprv_strcmp(numsys, "latn") ) foundLatn = TRUE;
if ( uprv_strcmp(numsys, "arab") ) foundArab = TRUE;
unumsys_close(unumsys);
} else {
log_err("unumsys_openAvailableNames includes %s but unumsys_openByName on it fails with status %s\n",
numsys, myErrorName(status));
}
}
uenum_close(uenum);
if ( numsysCount < 40 || !foundLatn || !foundArab ) {
log_err("unumsys_openAvailableNames results incomplete: numsysCount %d, foundLatn %d, foundArab %d\n",
numsysCount, foundLatn, foundArab);
}
} else {
log_data_err("unumsys_openAvailableNames fails with status %s\n", myErrorName(status));
}
uenum_close(uenum);
if ( numsysCount < 40 || !foundLatn || !foundArab ) {
log_err("unumsys_openAvailableNames results incomplete: numsysCount %d, foundLatn %d, foundArab %d\n",
numsysCount, foundLatn, foundArab);
}
} else {
log_data_err("unumsys_openAvailableNames fails with status %s\n", myErrorName(status));
}
}