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

ArrayIndexOutOfBoundsException with postgresql driver version 42.5.1 #2723

Closed
sz-liva opened this issue Jan 11, 2023 · 6 comments · Fixed by #2726
Closed

ArrayIndexOutOfBoundsException with postgresql driver version 42.5.1 #2723

sz-liva opened this issue Jan 11, 2023 · 6 comments · Fixed by #2726

Comments

@sz-liva
Copy link
Contributor

sz-liva commented Jan 11, 2023

Please read https://stackoverflow.com/help/minimal-reproducible-example

Describe the issue
ArrayIndexOutOfBoundsException with postgresql driver version 42.5.1.

sun.util.calendar.BaseCalendar in getCalendarDateFromFixedDate at line 457
java.util.GregorianCalendar in computeFields at line 2358
java.util.GregorianCalendar in computeTime at line 2779
java.util.Calendar in updateTime at line 3411
java.util.Calendar in getTimeInMillis at line 1805
org.postgresql.jdbc.TimestampUtils in toTimestamp at line 406
org.postgresql.jdbc.PgResultSet in getTimestamp at line 638
org.postgresql.jdbc.PgResultSet in getTimestamp at line 2857

Driver Version?
42.5.1

Java Version?
openjdk 17.0.2 2022-01-18
OpenJDK Runtime Environment (build 17.0.2+8-86)
OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing)

OS Version?
Linux fa30c4efe5b1 5.4.0-1063-aws #66~18.04.1-Ubuntu SMP Thu Jan 13 19:45:37 UTC 2022 x86_64 GNU/Linux

PostgreSQL Version?
PostgreSQL 14.3 on aarch64-unknown-linux-gnu, compiled by aarch64-unknown-linux-gnu-gcc (GCC) 7.4.0, 64-bit

Test Code
The code below should (eventually) exit with the output:

expected: 2017; found: 7777

when run against any PostgreSQL JDBC driver version 42.5.1

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class PgPreparedStatementTest {

    // org.postgresql:postgresql-42.5.1            fails

    static final String HOST = "127.0.0.1";
    static final int PORT = 5432;
    static final String DATABASE = "testdb";
    static final String USER = "postgres";
    static final String PASSWORD = "postgres";

    static final int ROWS_PER_RUN = 500;

    static abstract class RunnableWithConnection implements Runnable {

        protected RunnableWithConnection(Connection connection) {
            this.connection = connection;
        }

        final Connection connection;

    }

    static class A extends RunnableWithConnection {

        public A(Connection connection) {
            super(connection);
        }

        @Override
        public void run() {
            try (PreparedStatement statement = connection.prepareStatement(
                String.format("SELECT unnest(array_fill('12/31/7777'::timestamp, ARRAY[%d]))", ROWS_PER_RUN))) {
                for (int i = 0; i < 10; i++) {
                    try (ResultSet resultSet = statement.executeQuery()) {
                        resultSet.next();
                        resultSet.getTimestamp(1);
                        while (resultSet.next()) {
                            // resultSet.getTimestamp(1);
                        }
                    }
                }
            } catch (SQLException sqlException) {
                sqlException.printStackTrace(System.err);
                System.err.flush();
            }
        }

    }

    static class B extends RunnableWithConnection {

        public B(Connection connection) {
            super(connection);
        }

        @Override
        public void run() {
            final int YEAR = 2017;
            try (Statement statement = connection.createStatement()) {
                for (int i = 0; i < 10; i++) {
                    try (ResultSet resultSet = statement.executeQuery(
                        String.format("SELECT unnest(array_fill('8/10/%d'::timestamp, ARRAY[%d]))", YEAR,
                            ROWS_PER_RUN))) {
                        while (resultSet.next()) {
                            Timestamp d = resultSet.getTimestamp(1);
                            int year = 1900 + d.getYear();
                            if (year != YEAR) {
                                System.err.printf("expected: %d; found: %d%n", YEAR, year);
                                System.err.flush();
                                System.exit(-1);
                            }
                        }
                    }
                }
            } catch (SQLException sqlException) {
                sqlException.printStackTrace(System.err);
                System.err.flush();
            }
        }

    }

    static Connection newConnection() throws SQLException {
        final String WORKAROUND = false ? "?binaryTransfer=false" : "";
        Properties properties = new Properties();
        properties.put("user", USER);
        properties.put("password", PASSWORD);
        return DriverManager.getConnection(
            String.format("jdbc:postgresql://%s:%d/%s%s", HOST, PORT, DATABASE, WORKAROUND), properties);
    }

    public static void main(String[] ignored) throws Exception {
        try (Connection connection = newConnection()) {
            List<Runnable> runnables = Arrays.asList(new A(connection), new B(connection));
            for (int i = 0; i < 5000; ++i) {
                System.out.println(i);
                ExecutorService e = Executors.newFixedThreadPool(runnables.size());
                for (Runnable runnable : runnables) {
                    e.submit(runnable);
                }
                e.shutdown();
                e.awaitTermination(1, TimeUnit.HOURS);
            }
        }
    }

}
@davecramer
Copy link
Member

Well the driver is not thread safe so it's understandable that you would have this problem.

@davecramer
Copy link
Member

@sz-liva
Copy link
Contributor Author

sz-liva commented Jan 11, 2023

Can this be related to #2291? That pull-request replaced connection.getTimestampUtils() with call of private method getTimestampUtils() but some of these changes were reverted later. connection.getTimestampUtils() is again used in PgResultSet class making it not thread-safe.

@davecramer
Copy link
Member

Could be, but without a way to replicate this problem I'd be guessing.

@sz-liva
Copy link
Contributor Author

sz-liva commented Jan 11, 2023

I have added test code to the description.

@davecramer
Copy link
Member

Unfortunately PostgreSQL connections cannot be shared. I'm surprised this works at all.

@vlsi vlsi linked a pull request Jan 12, 2023 that will close this issue
vlsi pushed a commit that referenced this issue Jan 12, 2023
Previously, PgResultSet#getTimestamp used a connection-shared TimestampUtils,
so PgResultSet#getTimestamp produced wrong results on concurrent access to resultests derived
from different statements of the same connection.

fixes #2723
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants