]> granicus.if.org Git - icu/commitdiff
ICU-13606 Fixing race condition in MeasureFormat.
authorShane Carr <shane@unicode.org>
Thu, 1 Mar 2018 00:58:47 +0000 (00:58 +0000)
committerShane Carr <shane@unicode.org>
Thu, 1 Mar 2018 00:58:47 +0000 (00:58 +0000)
X-SVN-Rev: 41025

icu4c/source/i18n/measfmt.cpp
icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitThreadTest.java

index 0d9d2f8b8e00b532304f54c44357199583e202b4..5970262de1a51815a47b3bbea11d7ae28ef501fd 100644 (file)
@@ -1062,9 +1062,13 @@ UnicodeString &MeasureFormat::formatNumeric(
     }
 
     // Format time. draft becomes something like '5:30:45'
+    // #13606: DateFormat is not thread-safe, but MeasureFormat advertises itself as thread-safe.
     FieldPosition smallestFieldPosition(smallestField);
     UnicodeString draft;
+    static UMutex dateFmtMutex = U_MUTEX_INITIALIZER;
+    umtx_lock(&dateFmtMutex);
     dateFmt.format(date, draft, smallestFieldPosition, status);
+    umtx_unlock(&dateFmtMutex);
 
     // If we find field for smallest amount replace it with the formatted
     // smallest amount from above taking care to replace the integer part
index 500ee5dc3b737c16c0839cf45b1d01af0194c08e..9ae8eb69f789c70f409de26f2f13874ca9a4afaf 100644 (file)
@@ -933,10 +933,15 @@ public class MeasureFormat extends UFormat {
         if (intFieldPosition.getBeginIndex() == 0 && intFieldPosition.getEndIndex() == 0) {
             throw new IllegalStateException();
         }
+
         // Format our duration as a date, but keep track of where the smallest field is
         // so that we can use it to replace the integer portion of the smallest value.
+        // #13606: DateFormat is not thread-safe, but MeasureFormat advertises itself as thread-safe.
         FieldPosition smallestFieldPosition = new FieldPosition(smallestField);
-        String draft = formatter.format(duration, new StringBuffer(), smallestFieldPosition).toString();
+        String draft;
+        synchronized (formatter) {
+            draft = formatter.format(duration, new StringBuffer(), smallestFieldPosition).toString();
+        }
 
         try {
             // If we find the smallest field
index e86ed12fe3a42cf3cc6cd0033ce58c5216a2764e..05fb18f03b2696ef39a83502b5410f2c04cb8178 100644 (file)
@@ -8,7 +8,10 @@ import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 import com.ibm.icu.dev.test.TestFmwk;
+import com.ibm.icu.impl.DontCareFieldPosition;
+import com.ibm.icu.text.MeasureFormat;
 import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.Measure;
 import com.ibm.icu.util.MeasureUnit;
 import com.ibm.icu.util.ULocale;
 
@@ -30,5 +33,61 @@ public class MeasureUnitThreadTest extends TestFmwk {
         Currency.getInstance(ULocale.ENGLISH);
         try {thread.join();} catch(InterruptedException e) {};
     }
+
+    static class NumericMeasureThread extends Thread {
+        final MeasureFormat mf;
+        final Measure[] arg;
+        final String expected;
+        volatile boolean running = true;
+        AssertionError error;
+
+        NumericMeasureThread(Measure[] arg, String expected) {
+            this.mf = MeasureFormat.getInstance(ULocale.ENGLISH, MeasureFormat.FormatWidth.NUMERIC);
+            this.arg = arg;
+            this.expected = expected;
+            this.error = null;
+        }
+
+        @Override
+        public void run() {
+            while (running) {
+                try {
+                    StringBuilder sb = new StringBuilder();
+                    mf.formatMeasures(sb, DontCareFieldPosition.INSTANCE, arg);
+                    org.junit.Assert.assertEquals(expected, sb.toString());
+                } catch (AssertionError e) {
+                    error = e;
+                    break;
+                }
+            }
+        }
+    }
+
+    // Race in formatMeasures with width NUMERIC:
+    // http://bugs.icu-project.org/trac/ticket/13606
+    @Test
+    public void NumericRaceTest() throws InterruptedException {
+        NumericMeasureThread t1 = new NumericMeasureThread(new Measure[] {
+          new Measure(3, MeasureUnit.MINUTE),
+          new Measure(4, MeasureUnit.SECOND)
+        }, "3:04");
+        NumericMeasureThread t2 = new NumericMeasureThread(new Measure[] {
+          new Measure(5, MeasureUnit.MINUTE),
+          new Measure(6, MeasureUnit.SECOND)
+        }, "5:06");
+        t1.start();
+        t2.start();
+        Thread.sleep(5);
+        t1.running = false;
+        t2.running = false;
+        t1.join();
+        t2.join();
+        if (t1.error != null) {
+            throw new AssertionError("Failure in thread 1", t1.error);
+        }
+        if (t2.error != null) {
+            throw new AssertionError("Failure in thread 2", t2.error);
+        }
+    }
 }