From 09819eab73e90a28e25cc4dfa31f49863421ad34 Mon Sep 17 00:00:00 2001 From: Andy Heninger Date: Thu, 7 Jan 2016 21:15:19 +0000 Subject: [PATCH] ICU-11669 Thread safety of DateIntervalFormat::format() X-SVN-Rev: 38157 --- .../com/ibm/icu/text/DateIntervalFormat.java | 22 ++++-- .../test/format/DateIntervalFormatTest.java | 75 ++++++++++++++++++- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DateIntervalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DateIntervalFormat.java index 8b8f7d997e7..0808052d4c1 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DateIntervalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DateIntervalFormat.java @@ -1,5 +1,5 @@ /* -* Copyright (C) 2008-2015, International Business Machines +* Copyright (C) 2008-2016, International Business Machines * Corporation and others. All Rights Reserved. */ @@ -247,6 +247,12 @@ import com.ibm.icu.util.ULocale.Category; * * * + *

Synchronization

+ * + * The format methods of DateIntervalFormat may be used concurrently from multiple threads. + * Functions that alter the state of a DateIntervalFormat object (setters) + * may not be used concurrently with any other functions. + * * @stable ICU 4.0 */ @@ -296,7 +302,9 @@ public class DateIntervalFormat extends UFormat { private DateIntervalInfo fInfo; /* - * The DateFormat object used to format single pattern + * The DateFormat object used to format single pattern. + * Because fDateFormat is modified during format operations, all + * access to it from logically const, thread safe functions must be synchronized. */ private SimpleDateFormat fDateFormat; @@ -304,6 +312,8 @@ public class DateIntervalFormat extends UFormat { * The 2 calendars with the from and to date. * could re-use the calendar in fDateFormat, * but keeping 2 calendars make it clear and clean. + * Because these Calendars are modified during format operations, all + * access to them from logically const, thread safe functions must be synchronized. */ private Calendar fFromCalendar; private Calendar fToCalendar; @@ -559,7 +569,7 @@ public class DateIntervalFormat extends UFormat { * @return A copy of the object. * @stable ICU 4.0 */ - public Object clone() + public synchronized Object clone() { DateIntervalFormat other = (DateIntervalFormat) super.clone(); other.fDateFormat = (SimpleDateFormat) fDateFormat.clone(); @@ -618,7 +628,7 @@ public class DateIntervalFormat extends UFormat { * @return Reference to 'appendTo' parameter. * @stable ICU 4.0 */ - public final StringBuffer format(DateInterval dtInterval, + public final synchronized StringBuffer format(DateInterval dtInterval, StringBuffer appendTo, FieldPosition fieldPosition) { @@ -686,7 +696,7 @@ public class DateIntervalFormat extends UFormat { * @throws IllegalArgumentException if the two calendars are not equivalent. * @stable ICU 4.0 */ - public final StringBuffer format(Calendar fromCalendar, + public final synchronized StringBuffer format(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, FieldPosition pos) @@ -1008,7 +1018,7 @@ public class DateIntervalFormat extends UFormat { * this date interval formatter. * @stable ICU 4.0 */ - public DateFormat getDateFormat() + public synchronized DateFormat getDateFormat() { return (DateFormat)fDateFormat.clone(); } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateIntervalFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateIntervalFormatTest.java index fe3fadda1f0..97f9d09a92b 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateIntervalFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateIntervalFormatTest.java @@ -1,6 +1,6 @@ /* ******************************************************************************* - * Copyright (C) 2001-2015, International Business Machines Corporation and * + * Copyright (C) 2001-2016, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ @@ -14,7 +14,10 @@ package com.ibm.icu.dev.test.format; import java.text.FieldPosition; import java.text.ParseException; +import java.util.ArrayList; import java.util.Date; +import java.util.List; +import java.util.ListIterator; import java.util.Locale; import com.ibm.icu.impl.Utility; @@ -1714,4 +1717,74 @@ public class DateIntervalFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } } + + // TestTicket11669 - Check the thread safety of DateIntervalFormat.format(). + // This test failed with ICU 56. + + public void TestTicket11669 () { + // These final variables are accessed directly by the concurrent threads. + final DateIntervalFormat formatter = DateIntervalFormat.getInstance(DateFormat.YEAR_MONTH_DAY, ULocale.US); + final ArrayList testIntervals = new ArrayList(); + final ArrayListexpectedResults = new ArrayList(); + + // Create and save the input test data. + TimeZone tz = TimeZone.getTimeZone("Americal/Los_Angeles"); + Calendar intervalStart = Calendar.getInstance(tz, ULocale.US); + Calendar intervalEnd = Calendar.getInstance(tz, ULocale.US); + intervalStart.set(2009, 6, 1); + intervalEnd.set(2009, 6, 2); + testIntervals.add(new DateInterval(intervalStart.getTimeInMillis(), intervalEnd.getTimeInMillis())); + intervalStart.set(2015, 2, 27); + intervalEnd.set(2015, 3, 1); + testIntervals.add(new DateInterval(intervalStart.getTimeInMillis(), intervalEnd.getTimeInMillis())); + + // Run the formatter single-threaded to create and save the expected results. + for (DateInterval interval: testIntervals) { + FieldPosition pos = new FieldPosition(0); + StringBuffer result = new StringBuffer(); + formatter.format(interval, result, pos); + expectedResults.add(result.toString()); + } + + class TestThread extends Thread { + public String errorMessage; + public void run() { + for (int loop=0; loop < 2000; ++loop) { + ListIterator expectedItr = expectedResults.listIterator(); + for (DateInterval interval: testIntervals) { + String expected = expectedItr.next(); + FieldPosition pos = new FieldPosition(0); + StringBuffer result = new StringBuffer(); + formatter.format(interval, result, pos); + if (!expected.equals(result.toString())) { + // Note: The ICU test framework doesn't support reporting failures from within a sub-thread. + // Save the failure for the main thread to pick up later. + errorMessage = String.format("Expected \"%s\", actual \"%s\"", expected, result); + return; + } + } + } + } + } + + List threads = new ArrayList(); + for (int i=0; i<4; ++i) { + threads.add(new TestThread()); + } + for (Thread t: threads) { + t.start(); + } + for (TestThread t: threads) { + try { + t.join(); + } catch (InterruptedException e) { + fail("Unexpected exception: " + e.toString()); + } + if (t.errorMessage != null) { + fail(t.errorMessage); + } + } + } + + } -- 2.40.0