Skip to content

Commit

Permalink
Fixed DateFormat reentrant bug (#213)
Browse files Browse the repository at this point in the history
* Fixed DateFormat reentrant bug.  Thanks to Bob Lacatena.

* Added some attempt at threaded tests.  Can't reproduce the error tho.
  • Loading branch information
j256 authored Jun 22, 2021
1 parent 493cf40 commit 0074264
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 26 deletions.
27 changes: 3 additions & 24 deletions src/main/java/com/j256/ormlite/field/types/BaseDateType.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.j256.ormlite.field.FieldType;
Expand Down Expand Up @@ -54,26 +53,6 @@ protected static String normalizeDateString(DateStringFormatConfig formatConfig,
return dateFormat.format(date);
}

protected static class DateStringFormatConfig {
private final String dateFormatStr;
// used with clone
private final DateFormat dateFormat;

public DateStringFormatConfig(String dateFormatStr) {
this.dateFormatStr = dateFormatStr;
this.dateFormat = new SimpleDateFormat(dateFormatStr);
}

public DateFormat getDateFormat() {
return (DateFormat) dateFormat.clone();
}

@Override
public String toString() {
return dateFormatStr;
}
}

@Override
public boolean isValidForVersion() {
return true;
Expand Down Expand Up @@ -106,13 +85,13 @@ protected DateStringFormatConfig getDefaultDateFormatConfig() {
/**
* Bit of a hack here. If they aren't specifying a custom format then we check the date-string to see if it has a
* period. If it has no period then we switch to the no-millis pattern. This is necessary because most databases
* support the .SSSSSS format but h2 dropped the millis because of SQL compliance in 1.4.something.
* support the .SSSSSS format but H2 dropped the millis because of SQL compliance in 1.4.something.
*/
private static DateFormat conditionalFormat(DateStringFormatConfig formatConfig, String dateStr) {
if (formatConfig == DEFAULT_DATE_FORMAT_CONFIG && dateStr.indexOf('.') < 0) {
return NO_MILLIS_DATE_FORMAT_CONFIG.dateFormat;
return NO_MILLIS_DATE_FORMAT_CONFIG.getDateFormat();
} else {
return formatConfig.dateFormat;
return formatConfig.getDateFormat();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.j256.ormlite.field.types;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

/**
* Date string format config that is it's own class to force the hiding of the DateFormat.
*
* @author graywatson
*/
public class DateStringFormatConfig {

private final String dateFormatStr;
// used with clone
private final DateFormat dateFormat;

public DateStringFormatConfig(String dateFormatStr) {
this.dateFormatStr = dateFormatStr;
this.dateFormat = new SimpleDateFormat(dateFormatStr);
}

public DateFormat getDateFormat() {
return (DateFormat) dateFormat.clone();
}

@Override
public String toString() {
return dateFormatStr;
}
}
3 changes: 2 additions & 1 deletion src/main/javadoc/doc-files/changelog.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
5.6: ?/?/2021
* CORE: Added support for @Entity storing of Serializable types. Thanks to arn-cpu.
* ANDROID: Removed the reflection hack support for pre-ice-cream-sandwich annotation performance hacks.
* CORE: Fixed a bug with nonreentrant handling of DateFormat. Thanks to Bob Lacatena.
* ANDROID: Removed the reflection hack support for pre-ice-cream-sandwich annotation performance.

5.5: 5/23/2021
* CORE: Added support for java.util.Currency. Thanks to juur.
Expand Down
98 changes: 98 additions & 0 deletions src/test/java/com/j256/ormlite/LockedConnectionSource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.j256.ormlite;

import java.io.IOException;
import java.sql.SQLException;

import com.j256.ormlite.db.DatabaseType;
import com.j256.ormlite.misc.IOUtils;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.support.DatabaseConnection;

/**
* Connection source that has a simple lock around it the delegate.
*
* @author graywatson
*/
public class LockedConnectionSource implements ConnectionSource {

protected ConnectionSource delegate;

public LockedConnectionSource(ConnectionSource cs) {
this.delegate = cs;
}

@Override
public DatabaseConnection getReadOnlyConnection(String tableName) throws SQLException {
synchronized (delegate) {
return delegate.getReadOnlyConnection(tableName);
}
}

@Override
public DatabaseConnection getReadWriteConnection(String tableName) throws SQLException {
synchronized (delegate) {
return delegate.getReadWriteConnection(tableName);
}
}

@Override
public void releaseConnection(DatabaseConnection connection) throws SQLException {
synchronized (delegate) {
delegate.releaseConnection(connection);
}
}

@Override
public void close() throws IOException {
synchronized (delegate) {
delegate.close();
}
}

@Override
public void closeQuietly() {
IOUtils.closeQuietly(this);
}

@Override
public boolean saveSpecialConnection(DatabaseConnection connection) throws SQLException {
synchronized (delegate) {
return delegate.saveSpecialConnection(connection);
}
}

@Override
public void clearSpecialConnection(DatabaseConnection connection) {
synchronized (delegate) {
delegate.clearSpecialConnection(connection);
}
}

@Override
public DatabaseConnection getSpecialConnection(String tableName) {
synchronized (delegate) {
return delegate.getSpecialConnection(tableName);
}
}

@Override
public DatabaseType getDatabaseType() {
synchronized (delegate) {
return delegate.getDatabaseType();
}
}

@Override
public boolean isOpen(String tableName) {
synchronized (delegate) {
return delegate.isOpen(tableName);
}
}

@Override
public boolean isSingleConnection(String tableName) {
synchronized (delegate) {
return delegate.isSingleConnection(tableName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,24 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

import com.j256.ormlite.LockedConnectionSource;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.field.DataType;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.field.SqlType;
import com.j256.ormlite.h2.H2ConnectionSource;
import com.j256.ormlite.stmt.StatementBuilder.StatementType;
import com.j256.ormlite.support.CompiledStatement;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.support.DatabaseResults;
import com.j256.ormlite.table.DatabaseTable;
Expand Down Expand Up @@ -158,10 +166,52 @@ public void testDateStringBackwardsCompatibility() throws Exception {
assertTrue(newVersionDate.date.after(date));
}

@Test
public void testThreads() throws Exception {
ExecutorService pool = Executors.newCachedThreadPool();

ConnectionSource connectionSource = new LockedConnectionSource(new H2ConnectionSource());
final Dao<LocalDateString, Object> dao1 = createDao(connectionSource, LocalDateString.class, true);
final Dao<DateStringFormat, Object> dao2 = createDao(connectionSource, DateStringFormat.class, true);

final Random random = new Random();
for (int i = 0; i < 100; i++) {
pool.submit(new Callable<Void>() {
@Override
public Void call() throws SQLException {
for (int i = 0; i < 10000; i++) {
if (i % 2 == 0) {
DateStringFormat dsf = new DateStringFormat();
dsf.date = new Date(random.nextLong());
assertEquals(1, dao2.create(dsf));
DateStringFormat result = dao2.queryForId(dsf.id);
assertNotNull(result);
assertEquals(dsf.date, result.date);
} else {
LocalDateString lds = new LocalDateString();
lds.date = new Date(random.nextLong());
assertEquals(1, dao1.create(lds));
LocalDateString result = dao1.queryForId(lds.id);
assertNotNull(result);
assertEquals(lds.date, result.date);
}
}
return null;
}
});
}

pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
connectionSource.close();
}

/* ============================================================================================ */

@DatabaseTable(tableName = TABLE_NAME)
protected static class LocalDateString {
@DatabaseField(generatedId = true)
int id;
@DatabaseField(columnName = DATE_COLUMN, dataType = DataType.DATE_STRING)
Date date;
}
Expand All @@ -188,7 +238,9 @@ protected static class VersionDate {

@DatabaseTable(tableName = TABLE_NAME)
protected static class DateStringFormat {
@DatabaseField(columnName = DATE_COLUMN, dataType = DataType.DATE_STRING, format = "yyyy-MM-dd")
@DatabaseField(generatedId = true)
int id;
@DatabaseField(columnName = DATE_COLUMN, dataType = DataType.DATE_STRING, format = "dd-MM-yyyy")
Date date;
}

Expand Down

0 comments on commit 0074264

Please sign in to comment.