From 29abc5725eff0b9710c60ab717c468f8615f49fd Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 5 May 2026 09:24:59 +0800 Subject: [PATCH 1/2] fix(loader): fix thread-safety issue in DateUtil Signed-off-by: Jason --- .../hugegraph/loader/util/DateUtil.java | 17 +++---- .../loader/test/unit/DateUtilTest.java | 49 +++++++++++++++++++ 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/hugegraph-loader/src/main/java/org/apache/hugegraph/loader/util/DateUtil.java b/hugegraph-loader/src/main/java/org/apache/hugegraph/loader/util/DateUtil.java index 670c70a01..9707e3a84 100644 --- a/hugegraph-loader/src/main/java/org/apache/hugegraph/loader/util/DateUtil.java +++ b/hugegraph-loader/src/main/java/org/apache/hugegraph/loader/util/DateUtil.java @@ -18,16 +18,15 @@ package org.apache.hugegraph.loader.util; import java.util.Date; -import java.util.Map; import java.util.TimeZone; -import java.util.concurrent.ConcurrentHashMap; import org.apache.hugegraph.date.SafeDateFormat; import org.apache.hugegraph.loader.constant.Constants; public final class DateUtil { - private static final Map DATE_FORMATS = new ConcurrentHashMap<>(); + private static final ThreadLocal> DATE_FORMATS = + ThreadLocal.withInitial(java.util.HashMap::new); public static Date parse(String source, String df) { return parse(source, df, Constants.TIME_ZONE); @@ -41,13 +40,11 @@ public static Date parse(String source, String df, String timeZone) { } private static SafeDateFormat getDateFormat(String df) { - SafeDateFormat dateFormat = DATE_FORMATS.get(df); + java.util.HashMap formats = DATE_FORMATS.get(); + SafeDateFormat dateFormat = formats.get(df); if (dateFormat == null) { dateFormat = new SafeDateFormat(df); - SafeDateFormat previous = DATE_FORMATS.putIfAbsent(df, dateFormat); - if (previous != null) { - dateFormat = previous; - } + formats.put(df, dateFormat); } return dateFormat; } @@ -58,7 +55,9 @@ public static Object toPattern(String df) { } public static String now(String df) { - return getDateFormat(df).format(new Date()); + SafeDateFormat dateFormat = getDateFormat(df); + dateFormat.setTimeZone(Constants.TIME_ZONE); + return dateFormat.format(new Date()); } public static boolean checkTimeZone(String timeZone) { diff --git a/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/DateUtilTest.java b/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/DateUtilTest.java index 6370c8977..2f987f5af 100644 --- a/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/DateUtilTest.java +++ b/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/DateUtilTest.java @@ -17,6 +17,12 @@ package org.apache.hugegraph.loader.test.unit; +import java.util.Date; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + import org.apache.hugegraph.loader.util.DateUtil; import org.junit.Test; @@ -24,6 +30,15 @@ public class DateUtilTest { + @Test + public void testNowUsesDefaultTimeZone() { + String pattern = "Z"; + + DateUtil.parse("1970-01-01 +0000", "yyyy-MM-dd Z", "GMT"); + + Assert.assertEquals("+0800", DateUtil.now(pattern)); + } + @Test public void testCheckTimeZone() { Assert.assertTrue(DateUtil.checkTimeZone("JST")); @@ -70,4 +85,38 @@ public void testCheckTimeZone() { // minutes 00-59 only Assert.assertFalse(DateUtil.checkTimeZone("GMT+13:60")); } + + @Test + public void testConcurrentParseDateWithDifferentTimeZones() throws InterruptedException { + int threads = 10; + int iterations = 100; + ExecutorService executor = Executors.newFixedThreadPool(threads); + CountDownLatch latch = new CountDownLatch(threads); + AtomicInteger errors = new AtomicInteger(0); + + String dateStr = "2024-01-15 12:00:00"; + String pattern = "yyyy-MM-dd HH:mm:ss"; + + for (int i = 0; i < threads; i++) { + final int threadId = i; + executor.submit(() -> { + try { + String timeZone = threadId % 2 == 0 ? "GMT+8" : "GMT+0"; + for (int j = 0; j < iterations; j++) { + Date result = DateUtil.parse(dateStr, pattern, timeZone); + if (result == null) { + errors.incrementAndGet(); + } + } + } finally { + latch.countDown(); + } + }); + } + + latch.await(); + executor.shutdown(); + + Assert.assertEquals(0, errors.get()); + } } From 8ec257990cf105aeb614c979b76513e0dbc9169d Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 5 May 2026 14:06:38 +0800 Subject: [PATCH 2/2] fix: add valid assert statements and remove prefix import Hashmap Signed-off-by: Jason --- .../org/apache/hugegraph/loader/util/DateUtil.java | 8 ++++---- .../hugegraph/loader/test/unit/DateUtilTest.java | 13 +++++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/hugegraph-loader/src/main/java/org/apache/hugegraph/loader/util/DateUtil.java b/hugegraph-loader/src/main/java/org/apache/hugegraph/loader/util/DateUtil.java index 9707e3a84..caeada15a 100644 --- a/hugegraph-loader/src/main/java/org/apache/hugegraph/loader/util/DateUtil.java +++ b/hugegraph-loader/src/main/java/org/apache/hugegraph/loader/util/DateUtil.java @@ -18,6 +18,7 @@ package org.apache.hugegraph.loader.util; import java.util.Date; +import java.util.HashMap; import java.util.TimeZone; import org.apache.hugegraph.date.SafeDateFormat; @@ -25,8 +26,8 @@ public final class DateUtil { - private static final ThreadLocal> DATE_FORMATS = - ThreadLocal.withInitial(java.util.HashMap::new); + private static final ThreadLocal> DATE_FORMATS = + ThreadLocal.withInitial(HashMap::new); public static Date parse(String source, String df) { return parse(source, df, Constants.TIME_ZONE); @@ -34,13 +35,12 @@ public static Date parse(String source, String df) { public static Date parse(String source, String df, String timeZone) { SafeDateFormat dateFormat = getDateFormat(df); - // parse date with specified timezone dateFormat.setTimeZone(timeZone); return dateFormat.parse(source); } private static SafeDateFormat getDateFormat(String df) { - java.util.HashMap formats = DATE_FORMATS.get(); + HashMap formats = DATE_FORMATS.get(); SafeDateFormat dateFormat = formats.get(df); if (dateFormat == null) { dateFormat = new SafeDateFormat(df); diff --git a/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/DateUtilTest.java b/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/DateUtilTest.java index 2f987f5af..08d2ab2ab 100644 --- a/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/DateUtilTest.java +++ b/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/DateUtilTest.java @@ -33,9 +33,7 @@ public class DateUtilTest { @Test public void testNowUsesDefaultTimeZone() { String pattern = "Z"; - - DateUtil.parse("1970-01-01 +0000", "yyyy-MM-dd Z", "GMT"); - + DateUtil.parse("+0000", pattern, "GMT"); Assert.assertEquals("+0800", DateUtil.now(pattern)); } @@ -96,18 +94,24 @@ public void testConcurrentParseDateWithDifferentTimeZones() throws InterruptedEx String dateStr = "2024-01-15 12:00:00"; String pattern = "yyyy-MM-dd HH:mm:ss"; + long expectedEpochGMT8 = DateUtil.parse(dateStr, pattern, "GMT+8").getTime(); + long expectedEpochGMT0 = DateUtil.parse(dateStr, pattern, "GMT+0").getTime(); + long expectedDiff = 8 * 60 * 60 * 1000; for (int i = 0; i < threads; i++) { final int threadId = i; executor.submit(() -> { try { String timeZone = threadId % 2 == 0 ? "GMT+8" : "GMT+0"; + long expectedEpoch = threadId % 2 == 0 ? expectedEpochGMT8 : expectedEpochGMT0; for (int j = 0; j < iterations; j++) { Date result = DateUtil.parse(dateStr, pattern, timeZone); - if (result == null) { + if (result == null || result.getTime() != expectedEpoch) { errors.incrementAndGet(); } } + } catch (Exception e) { + errors.incrementAndGet(); } finally { latch.countDown(); } @@ -118,5 +122,6 @@ public void testConcurrentParseDateWithDifferentTimeZones() throws InterruptedEx executor.shutdown(); Assert.assertEquals(0, errors.get()); + Assert.assertEquals(expectedDiff, expectedEpochGMT0 - expectedEpochGMT8); } }