Removed more unsafe concurrent accesses to SimpleDateFormat instances.

SimpleDateFormat must not be used by concurrent threads without
synchronization for parsing or formating dates as it is not thread-safe
(internally holds a calendar instance that is not synchronized).

Prefer now DateTimeFormatter when possible as it is thread-safe without
concurrent access performance bottleneck (does not internally use
synchronization locks).
This commit is contained in:
luccioman 2018-06-29 15:49:55 +02:00
parent 5c6c61809a
commit f895745e1c
5 changed files with 144 additions and 16 deletions

View File

@ -24,7 +24,8 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
@ -34,6 +35,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import net.yacy.cora.date.GenericFormatter;
import net.yacy.cora.document.encoding.ASCII;
import net.yacy.cora.document.encoding.UTF8;
import net.yacy.cora.document.id.Punycode.PunycodeException;
@ -350,11 +352,17 @@ public class CrawlResults {
return prop;
}
private static SimpleDateFormat dayFormatter = new SimpleDateFormat("yyyy/MM/dd", Locale.US);
private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter
.ofPattern("uuuu/MM/dd", Locale.US).withZone(ZoneId.systemDefault());
/**
* @param date a date to render as a String
* @return the date formatted using the DAY_FORMATTER pattern.
*/
private static String daydate(final Date date) {
if (date == null) {
return "";
}
return dayFormatter.format(date);
return GenericFormatter.formatSafely(date.toInstant(), DAY_FORMATTER);
}
}

View File

@ -1,6 +1,7 @@
import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
@ -12,6 +13,7 @@ import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import net.yacy.cora.date.GenericFormatter;
import net.yacy.cora.document.encoding.ASCII;
import net.yacy.cora.document.id.DigestURL;
import net.yacy.cora.protocol.Domains;
@ -28,10 +30,18 @@ import net.yacy.server.serverSwitch;
public class IndexCreateQueues_p {
private static SimpleDateFormat dayFormatter = new SimpleDateFormat("yyyy/MM/dd", Locale.US);
private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter
.ofPattern("uuuu/MM/dd", Locale.US).withZone(ZoneId.systemDefault());
/**
* @param date a date to render as a String
* @return the date formatted using the DAY_FORMATTER pattern.
*/
private static String daydate(final Date date) {
if (date == null) return "";
return dayFormatter.format(date);
if (date == null) {
return "";
}
return GenericFormatter.formatSafely(date.toInstant(), DAY_FORMATTER);
}
private static final int INVALID = 0;

View File

@ -25,6 +25,7 @@
package net.yacy.cora.document.feed;
import java.text.ParseException;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@ -156,7 +157,7 @@ public class RSSMessage implements Hit, Comparable<RSSMessage>, Comparator<RSSMe
if (title.length() > 0) this.map.put(Token.title.name(), title);
if (description.length() > 0) this.map.put(Token.description.name(), description);
if (link.length() > 0) this.map.put(Token.link.name(), link);
this.map.put(Token.pubDate.name(), HeaderFramework.FORMAT_RFC1123.format(new Date()));
this.map.put(Token.pubDate.name(), HeaderFramework.formatNowRFC1123());
this.map.put(Token.guid.name(), artificialGuidPrefix + Integer.toHexString((title + description + link).hashCode()));
}
@ -165,7 +166,7 @@ public class RSSMessage implements Hit, Comparable<RSSMessage>, Comparator<RSSMe
if (title.length() > 0) this.map.put(Token.title.name(), title);
if (description.length() > 0) this.map.put(Token.description.name(), description);
this.map.put(Token.link.name(), link.toNormalform(true));
this.map.put(Token.pubDate.name(), HeaderFramework.FORMAT_RFC1123.format(new Date()));
this.map.put(Token.pubDate.name(), HeaderFramework.formatNowRFC1123());
if (guid.length() > 0) {
this.map.put(Token.guid.name(), guid);
}
@ -261,8 +262,8 @@ public class RSSMessage implements Hit, Comparable<RSSMessage>, Comparator<RSSMe
if (!dateString.isEmpty()) { // skip parse exception on empty string
Date date;
try {
date = HeaderFramework.FORMAT_RFC1123.parse(dateString);
} catch (final ParseException e) {
date = Date.from(ZonedDateTime.parse(dateString, HeaderFramework.RFC1123_FORMATTER).toInstant());
} catch (final RuntimeException e) {
try {
date = GenericFormatter.SHORT_SECOND_FORMATTER.parse(dateString, 0).getTime();
} catch (final ParseException e1) {
@ -401,7 +402,7 @@ public class RSSMessage implements Hit, Comparable<RSSMessage>, Comparator<RSSMe
@Override
public void setPubDate(final Date pubdate) {
setValue(Token.pubDate, HeaderFramework.FORMAT_RFC1123.format(pubdate));
setValue(Token.pubDate, HeaderFramework.formatNowRFC1123());
}
@Override

View File

@ -26,6 +26,10 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
@ -230,10 +234,33 @@ public class HeaderFramework extends TreeMap<String, String> implements Map<Stri
/** Date formatter/parser for standard compliant HTTP header dates (RFC 1123) */
private static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss Z"; // with numeric time zone indicator as defined in RFC5322
private static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz";
public static final SimpleDateFormat FORMAT_RFC1123 = new SimpleDateFormat(PATTERN_RFC1123, Locale.US);
public static final SimpleDateFormat FORMAT_RFC1036 = new SimpleDateFormat(PATTERN_RFC1036, Locale.US);
private static final SimpleDateFormat FORMAT_RFC1123 = new SimpleDateFormat(PATTERN_RFC1123, Locale.US);
private static final TimeZone TZ_GMT = TimeZone.getTimeZone("GMT");
private static final Calendar CAL_GMT = Calendar.getInstance(TZ_GMT, Locale.US);
/**
* A thread-safe date formatter using the
* {@link HeaderFramework#PATTERN_RFC1123} pattern with the US locale on the UTC
* time zone.
*/
public static final DateTimeFormatter RFC1123_FORMATTER = DateTimeFormatter
.ofPattern(PATTERN_RFC1123.replace("yyyy", "uuuu")).withLocale(Locale.US).withZone(ZoneOffset.UTC);
/**
* @return a new SimpleDateFormat instance using the
* {@link HeaderFramework#PATTERN_RFC1123} pattern with the US locale.
*/
public static SimpleDateFormat newRfc1123Format() {
return new SimpleDateFormat(HeaderFramework.PATTERN_RFC1123, Locale.US);
}
/**
* @return a new SimpleDateFormat instance using the
* {@link HeaderFramework#PATTERN_RFC1036} pattern with the US locale.
*/
public static SimpleDateFormat newRfc1036Format() {
return new SimpleDateFormat(HeaderFramework.PATTERN_RFC1036, Locale.US);
}
/**
* RFC 2616 requires that HTTP clients are able to parse all 3 different
@ -241,9 +268,9 @@ public class HeaderFramework extends TreeMap<String, String> implements Map<Stri
*/
private static final SimpleDateFormat[] FORMATS_HTTP = new SimpleDateFormat[] {
// RFC 1123/822 (Standard) "Mon, 12 Nov 2007 10:11:12 GMT"
FORMAT_RFC1123,
newRfc1123Format(),
// RFC 1036/850 (old) "Monday, 12-Nov-07 10:11:12 GMT"
FORMAT_RFC1036,
newRfc1036Format(),
// ANSI C asctime() "Mon Nov 12 10:11:12 2007"
GenericFormatter.newAnsicFormat(),
};
@ -265,6 +292,34 @@ public class HeaderFramework extends TreeMap<String, String> implements Map<Stri
return s;
}
}
/**
* @param epochMilli
* a time value as the number of milliseconds from Epoch
* (1970-01-01T00:00:00Z)
* @return the time formatted using the {@link HeaderFramework#PATTERN_RFC1123}
* pattern.
*/
public static final String formatRFC1123(final long epochMilli) {
try {
/* Prefer first using the thread-safe DateTimeFormatter shared instance */
return RFC1123_FORMATTER.format(Instant.ofEpochMilli(epochMilli));
} catch (final DateTimeException e) {
/*
* This should not happen, but rather than failing we prefer here to use
* formatting function using the synchronized SimpleDateFormat
*/
return formatRFC1123(new Date(epochMilli));
}
}
/**
* @return the current time formatted using the
* {@link HeaderFramework#PATTERN_RFC1123} pattern.
*/
public static final String formatNowRFC1123() {
return formatRFC1123(System.currentTimeMillis());
}
/** Initialization of static formats */
static {
@ -277,6 +332,9 @@ public class HeaderFramework extends TreeMap<String, String> implements Map<Stri
format.setTimeZone(TZ_GMT);
format.set2DigitYearStart(CAL_GMT.getTime());
}
FORMAT_RFC1123.setTimeZone(TZ_GMT);
FORMAT_RFC1123.set2DigitYearStart(CAL_GMT.getTime());
}
/**

View File

@ -1,6 +1,29 @@
// HeaderFrameworkTest.java
// Copyright 2006-2018 by theli, f1ori, Michael Peter Christen; mc@yacy.net, reger; reger18@arcor.de, luccioman; https://github.com/luccioman
//
// This is a part of YaCy, a peer-to-peer based web search engine
//
// LICENSE
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package net.yacy.cora.protocol;
import java.time.Instant;
import java.util.Date;
import junit.framework.TestCase;
import org.junit.Test;
@ -18,6 +41,22 @@ public class HeaderFrameworkTest extends TestCase {
// Print Result
System.out.println("testParseHTTPDate: " + parsedDate.toString());
parsedDate = HeaderFramework.parseHTTPDate("Monday, 12-Nov-07 10:11:12 GMT");
// returned date must not be null
assertNotNull(parsedDate);
// Print Result
System.out.println("testParseHTTPDate: " + parsedDate.toString());
parsedDate = HeaderFramework.parseHTTPDate("Mon Nov 12 10:11:12 2007");
// returned date must not be null
assertNotNull(parsedDate);
// Print Result
System.out.println("testParseHTTPDate: " + parsedDate.toString());
}
/**
@ -31,4 +70,16 @@ public class HeaderFrameworkTest extends TestCase {
assertEquals("utf-8", HeaderFramework.getCharacterEncoding("Text/HTML;Charset=\"utf-8\""));
assertEquals("utf-8", HeaderFramework.getCharacterEncoding("text/html; charset=\"utf-8\""));
}
/**
* Unit test for date formatting with RFC 1123 format
*/
@Test
public void testFormatRfc1123() {
assertEquals("", HeaderFramework.formatRFC1123(null));
final Instant time = Instant.parse("2018-06-29T13:04:55.00Z");
assertEquals("Fri, 29 Jun 2018 13:04:55 +0000", HeaderFramework.formatRFC1123(time.toEpochMilli()));
assertEquals("Fri, 29 Jun 2018 13:04:55 +0000", HeaderFramework.formatRFC1123(Date.from(time)));
}
}