Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cleanups of DateCache #10176

Merged
merged 20 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
468f00e
cleanups of DateCache
lachlan-roberts Jul 31, 2023
9bb5eae
cleanups of DateCache
lachlan-roberts Jul 31, 2023
7ddba90
Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-…
lachlan-roberts Jul 31, 2023
24c7ec9
Merge branch 'jetty-10.0.x' into jetty-10.0.x-datecache
lachlan-roberts Aug 1, 2023
93c2faa
improve the formatting for precise ms in DateCache
lachlan-roberts Aug 11, 2023
7e383ed
return original format string with DateCache.getFormatString
lachlan-roberts Aug 11, 2023
c4912de
calculate index in tick constructor because format strings can be dif…
lachlan-roberts Aug 11, 2023
d9be313
use two ticks so that switching between seconds is less likely going …
lachlan-roberts Aug 11, 2023
78ee980
use boolean instead of index to denote if sub second is needed
lachlan-roberts Aug 11, 2023
4c71aed
remove formatWithoutCache and replace with doFormat as it doesn't wor…
lachlan-roberts Aug 11, 2023
f500351
allow the option of not having sub second precision
lachlan-roberts Aug 11, 2023
d4302a1
use two separate formatters for the prefix/suffix around the SSS form…
lachlan-roberts Aug 11, 2023
9bcc41c
fix checkstyle error
lachlan-roberts Aug 11, 2023
996ca8e
Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-…
lachlan-roberts Aug 11, 2023
57bee29
Merge branch 'jetty-10.0.x' into jetty-10.0.x-datecache
lachlan-roberts Aug 16, 2023
83a8ccb
changes to DateCache from review
lachlan-roberts Aug 16, 2023
2aac2fe
use a simple class to store both ticks in DateCache
lachlan-roberts Aug 18, 2023
8f44b39
rename DateCache.Tick.getString(long) to format()
lachlan-roberts Aug 22, 2023
0bfab09
Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-…
lachlan-roberts Aug 22, 2023
d5f0300
Merge branch 'jetty-10.0.x' into jetty-10.0.x-datecache
lachlan-roberts Aug 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ protected void log(String format, Object... arg)
long now = System.currentTimeMillis();
long ms = now % 1000;
if (_out != null)
_out.printf("%s.%03d:%s%n", __date.formatNow(now), ms, s);
_out.printf("%s.%03d:%s%n", __date.format(now), ms, s);
gregw marked this conversation as resolved.
Show resolved Hide resolved
if (LOG.isDebugEnabled())
LOG.debug(s);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
private void print(String name, String message)
{
long now = System.currentTimeMillis();
final String d = _date.formatNow(now);
final String d = _date.format(now);
final int ms = (int)(now % 1000);

_print.println(d + (ms > 99 ? "." : (ms > 9 ? ".0" : ".00")) + ms + ":" + name + " " + message);
Expand Down
233 changes: 123 additions & 110 deletions jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,64 +15,91 @@

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

/**
* Date Format Cache.
* Computes String representations of Dates and caches
* the results so that subsequent requests within the same second
* will be fast.
*
* Only format strings that contain either "ss". Sub second formatting is
* not handled.
*
* The timezone of the date may be included as an ID with the "zzz"
* format string or as an offset with the "ZZZ" format string.
*
* Computes String representations of Dates then caches the results so
* that subsequent requests within the same second will be fast.
* <p>
* If consecutive calls are frequently very different, then this
* may be a little slower than a normal DateFormat.
* <p>
* @see DateTimeFormatter for date formatting patterns.
*/
public class DateCache
{
public static final String DEFAULT_FORMAT = "EEE MMM dd HH:mm:ss zzz yyyy";

private final String _formatString;
private final String _tzFormatString;
private final DateTimeFormatter _tzFormat;
private final Locale _locale;
private final DateTimeFormatter _tzFormat1;
private final DateTimeFormatter _tzFormat2;
private final ZoneId _zoneId;

private volatile Tick _tick;
private volatile TickHolder _tickHolder;

private static class TickHolder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be a record?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, this is jetty-10 so we can't use records

{
public TickHolder(Tick t1, Tick t2)
{
tick1 = t1;
tick2 = t2;
}

final Tick tick1;
final Tick tick2;
}

public static class Tick
{
final long _seconds;
final String _string;
private final long _seconds;
private final String _prefix;
private final String _suffix;

public Tick(long seconds, String string)
public Tick(long seconds, String prefix, String suffix)
{
_seconds = seconds;
_string = string;
_prefix = prefix;
_suffix = suffix;
}

public long getSeconds()
{
return _seconds;
}

public String format(long inDate)
{
if (_suffix == null)
return _prefix;

long ms = inDate % 1000;
StringBuilder sb = new StringBuilder();
sb.append(_prefix);
if (ms < 10)
sb.append("00").append(ms);
else if (ms < 100)
sb.append('0').append(ms);
else
sb.append(ms);
sb.append(_suffix);
return sb.toString();
}
}

/**
* Constructor.
* Make a DateCache that will use a default format. The default format
* generates the same results as Date.toString().
* Make a DateCache that will use a default format.
* The default format generates the same results as Date.toString().
*/
public DateCache()
{
this(DEFAULT_FORMAT);
}

/**
* Constructor.
* Make a DateCache that will use the given format
* Make a DateCache that will use the given format.
*
* @param format the format to use
*/
Expand All @@ -93,56 +120,44 @@ public DateCache(String format, Locale l, String tz)

public DateCache(String format, Locale l, TimeZone tz)
{
this(format, l, tz, true);
}

public DateCache(String format, Locale l, TimeZone tz, boolean subSecondPrecision)
{
format = format.replaceFirst("S+", "SSS");
_formatString = format;
_locale = l;
_zoneId = tz.toZoneId();

int zIndex = _formatString.indexOf("ZZZ");
if (zIndex >= 0)
String format1 = format;
String format2 = null;
boolean subSecond;
if (subSecondPrecision)
{
final String ss1 = _formatString.substring(0, zIndex);
final String ss2 = _formatString.substring(zIndex + 3);
int tzOffset = tz.getRawOffset();

StringBuilder sb = new StringBuilder(_formatString.length() + 10);
sb.append(ss1);
sb.append("'");
if (tzOffset >= 0)
sb.append('+');
else
int msIndex = format.indexOf("SSS");
subSecond = (msIndex >= 0);
if (subSecond)
{
tzOffset = -tzOffset;
sb.append('-');
format1 = format.substring(0, msIndex);
format2 = format.substring(msIndex + 3);
}

int raw = tzOffset / (1000 * 60); // Convert to seconds
int hr = raw / 60;
int min = raw % 60;

if (hr < 10)
sb.append('0');
sb.append(hr);
if (min < 10)
sb.append('0');
sb.append(min);
sb.append('\'');

sb.append(ss2);
_tzFormatString = sb.toString();
}
else
_tzFormatString = _formatString;

if (_locale != null)
{
_tzFormat = DateTimeFormatter.ofPattern(_tzFormatString, _locale);
subSecond = false;
format1 = format.replace("SSS", "000");
}

_tzFormat1 = createFormatter(format1, l, _zoneId);
_tzFormat2 = subSecond ? createFormatter(format2, l, _zoneId) : null;
}

private DateTimeFormatter createFormatter(String format, Locale locale, ZoneId zoneId)
{
if (locale == null)
return DateTimeFormatter.ofPattern(format).withZone(zoneId);
else
{
_tzFormat = DateTimeFormatter.ofPattern(_tzFormatString);
}
_zoneId = tz.toZoneId();
_tzFormat.withZone(_zoneId);
_tick = null;
return DateTimeFormatter.ofPattern(format, locale).withZone(zoneId);
}

public TimeZone getTimeZone()
Expand All @@ -152,92 +167,90 @@ public TimeZone getTimeZone()

/**
* Format a date according to our stored formatter.
* If it happens to be in the same second as the last
* formatNow call, then the format is reused.
*
* @param inDate the Date
* @return Formatted date
* @param inDate the Date.
* @return Formatted date.
*/
public String format(Date inDate)
{
long seconds = inDate.getTime() / 1000;

Tick tick = _tick;

// Is this the cached time
if (tick == null || seconds != tick._seconds)
{
return ZonedDateTime.ofInstant(inDate.toInstant(), _zoneId).format(_tzFormat);
}

return tick._string;
return format(inDate.getTime());
}

/**
* Format a date according to our stored formatter.
* If it happens to be in the same second as the last formatNow
* call, then the format is reused.
* If it happens to be in the same second as the last
* formatNow call, then the format is reused.
*
* @param inDate the date in milliseconds since unix epoch
* @return Formatted date
* @param inDate the date in milliseconds since unix epoch.
* @return Formatted date.
*/
public String format(long inDate)
{
long seconds = inDate / 1000;

Tick tick = _tick;

// Is this the cached time
if (tick == null || seconds != tick._seconds)
{
// It's a cache miss
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(inDate), _zoneId).format(_tzFormat);
}
return formatTick(inDate).format(inDate);
}

return tick._string;
/**
* Format a date according to supplied formatter.
*
* @param inDate the date in milliseconds since unix epoch.
* @return Formatted date.
*/
protected String doFormat(long inDate, DateTimeFormatter formatter)
{
if (formatter == null)
return null;
return formatter.format(Instant.ofEpochMilli(inDate));
}

/**
* Format a date according to our stored formatter.
* The passed time is expected to be close to the current time, so it is
* compared to the last value passed and if it is within the same second,
* the format is reused. Otherwise a new cached format is created.
* the format is reused. Otherwise, a new cached format is created.
*
* @param now the milliseconds since unix epoch
* @return Formatted date
* @deprecated use {@link #format(long)}
*/
@Deprecated
public String formatNow(long now)
{
long seconds = now / 1000;

Tick tick = _tick;

// Is this the cached time
if (tick != null && tick._seconds == seconds)
return tick._string;
return formatTick(now)._string;
return format(now);
}

@Deprecated
public String now()
{
return formatNow(System.currentTimeMillis());
}

@Deprecated
public Tick tick()
{
return formatTick(System.currentTimeMillis());
}

protected Tick formatTick(long now)
protected Tick formatTick(long inDate)
{
long seconds = now / 1000;
long seconds = inDate / 1000;

lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
Tick tick = _tick;
// recheck the tick, to save multiple formats
if (tick == null || tick._seconds != seconds)
// Two Ticks are cached so that for monotonically increasing times to not see any jitter from multiple cores.
// The ticks are kept in a volatile field, so there a small risk of inconsequential multiple recalculations
TickHolder holder = _tickHolder;
if (holder != null)
{
sbordet marked this conversation as resolved.
Show resolved Hide resolved
String s = ZonedDateTime.ofInstant(Instant.ofEpochMilli(now), _zoneId).format(_tzFormat);
_tick = new Tick(seconds, s);
tick = _tick;
if (holder.tick1 != null && holder.tick1.getSeconds() == seconds)
return holder.tick1;
if (holder.tick2 != null && holder.tick2.getSeconds() == seconds)
return holder.tick2;
}

String prefix = doFormat(inDate, _tzFormat1);
String suffix = doFormat(inDate, _tzFormat2);
Tick tick = new Tick(seconds, prefix, suffix);
_tickHolder = new TickHolder(tick, (holder == null) ? null : holder.tick1);
return tick;
}

Expand Down
Loading