Repository: pgjdbc/pgjdbc
Pull request #844 (2017-06-13 16:41:16)

fix: improve multihost connection for preferSlave case (verify expired hosts before connecting to cached master)
Hi,all

The current MultiHostChooser implementation in the following two scenarios is not very reasonable.

1. jdbc:postgresql://slave1,master1/accounting?targetServerType=master

Each time the cached host state expires(control by hostRecheckSeconds), need to try 2 times to connect to the correct server.


improvement：
Adjust the connecting order, first try to connect master1

currently: slave1 -> master1
after    : master1 -> slave1


2. jdbc:postgresql://master1,slave1/accounting?targetServerType=preferSlave

The first connection or the first connection after cached host state expires, will be connected to the master1.

But that's not what we expected. Reasonable result is only in the absence of any available slave, will be connected to the Master.


improvement：
When targetServerType is preferSlave, internal attempts to make connection twice.
The first time internally set targetServerType to 'slave' and connect. If the connecting fails, then set the targetServerType to 'any' and do a second attempt.


This PR include the following major changes, please take a look
1. Modify the above two problems
2. Simplify the implementation of loadBalance
3. modify test cases
   3.1 Adjust test case for above modification
   3.2 Add test cases for 1 master + 2 slave 
   3.3 Add test cases for `targetServerType=preferSlave&loadBalanceHosts=true`

codecov-io: # [Codecov](https://codecov.io/gh/pgjdbc/pgjdbc/pull/844?src=pr&el=h1) Report
> Merging [#844](https://codecov.io/gh/pgjdbc/pgjdbc/pull/844?src=pr&el=desc) into [master](https://codecov.io/gh/pgjdbc/pgjdbc/commit/464a2d43519004174f1b530a595ee0ad9ffda870?src=pr&el=desc) will **decrease** coverage by `0.08%`.
> The diff coverage is `13.55%`.



```diff
@@             Coverage Diff              @@
##             master     #844      +/-   ##
============================================
- Coverage     65.57%   65.49%   -0.09%     
  Complexity     3549     3549              
============================================
  Files           166      166              
  Lines         15254    15288      +34     
  Branches       2475     2482       +7     
============================================
+ Hits          10003    10013      +10     
- Misses         4070     4089      +19     
- Partials       1181     1186       +5
```

davecramer: The checkstyle failed for this.

You can run this with mvn checkstyle:check

Thanks!

Dave Cramer

On 13 June 2017 at 12:41, Chen Huajun <notifications@github.com> wrote:

> Hi,all
>
> The current MultiHostChooser implementation in the following two scenarios
> is not very reasonable.
>
>    1. jdbc:postgresql://slave1,master1/accounting?targetServerType=master
>
> Each time the cached host state expires(control by hostRecheckSeconds),
> need to try 2 times to connect to the correct server.
>
> improvement：
> Adjust the connecting order, first try to connect master1
>
> currently: slave1 -> master1
> after : master1 -> slave1
>
>    1. jdbc:postgresql://master1,slave1/accounting?
>    targetServerType=preferSlave
>
> The first connection or the first connection after cached host state
> expires, will be connected to the master1.
>
> But that's not what we expected. Reasonable result is only in the absence
> of any available slave, will be connected to the Master.
>
> improvement：
> When targetServerType is preferSlave, internal attempts to make connection
> twice.
> The first time internally set targetServerType to 'slave' and connect. If
> the connecting fails, then set the targetServerType to 'any' and do a
> second attempt.
>
> This PR include the following major changes, please take a look
>
>    1. Modify the above two problems
>    2. Simplify the implementation of loadBalance
>    3. modify test cases
>    3.1 Adjust test case for above modification
>    3.2 Add test cases for 1 master + 2 slave
>    3.3 Add test cases for targetServerType=preferSlave&
>    loadBalanceHosts=true
>
> ------------------------------
> You can view, comment on, or merge this pull request online at:
>
>   https://github.com/pgjdbc/pgjdbc/pull/844
> Commit Summary
>
>    - The current MultiHostChooser implementation in the following two
>    scenarios is not very reasonable.
>
> File Changes
>
>    - *M* pgjdbc/src/main/java/org/postgresql/core/v3/
>    ConnectionFactoryImpl.java
>    <https://github.com/pgjdbc/pgjdbc/pull/844/files#diff-0> (48)
>    - *M* pgjdbc/src/main/java/org/postgresql/hostchooser/
>    GlobalHostStatusTracker.java
>    <https://github.com/pgjdbc/pgjdbc/pull/844/files#diff-1> (7)
>    - *M* pgjdbc/src/main/java/org/postgresql/hostchooser/
>    MultiHostChooser.java
>    <https://github.com/pgjdbc/pgjdbc/pull/844/files#diff-2> (69)
>    - *M* pgjdbc/src/test/java/org/postgresql/test/hostchooser/
>    MultiHostTestSuite.java
>    <https://github.com/pgjdbc/pgjdbc/pull/844/files#diff-3> (29)
>    - *M* pgjdbc/src/test/java/org/postgresql/test/hostchooser/
>    MultiHostsConnectionTest.java
>    <https://github.com/pgjdbc/pgjdbc/pull/844/files#diff-4> (103)
>
> Patch Links:
>
>    - https://github.com/pgjdbc/pgjdbc/pull/844.patch
>    - https://github.com/pgjdbc/pgjdbc/pull/844.diff
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/pgjdbc/pgjdbc/pull/844>, or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAYz9irouQs8xsZidACv-zFUBuCZCOFDks5sDrutgaJpZM4N4wiO>
> .
>

ChenHuajun: I have solved these problems checkstyle found, please check again. 

And i added two 1 master + 2 slaves test cases in this patch,  so the test environment also needs to add an additional slave with port number 5434.
davecramer: Thanks! I am wondering if we can get someone else besides @vlsi to review this ?
ChenHuajun: @kato-sho @vlsi @davecramer Can anyone review this patch?
davecramer: @ChenHuajun does this change the current behaviour or just fix it ?

Would it be possible to elaborate some more on the documentation https://jdbc.postgresql.org/documentation/head/connect.html#connection-parameters search for Connection failover
vlsi: @ChenHuajun , can you please clarify how "3.2 Add test cases for 1 master + 2 slave" works in Travis environment?
It looks like it requires to have a secondary slave URL, and I do not see where that slave is created.
vlsi: As far as I can see, MultiHostsConnectionTest is included via MultiHostTestSuite, and the test just gets ignored at Travis. Is it something that can be tested?
ChenHuajun: @davecramer  This patch just a  fix , does not change the behaviour described  on the documentation behaviourhttps://jdbc.postgresql.org/documentation/head/connect.html#connection-parameters 
ChenHuajun: @vlsi  
MultiHostsConnectionTest  will be executed only when slaveServer and slaveServer2 had been setted in build.local.properties or build.properties. Of  course it's needed to prepared two slave servers first. Could you configure build.local.properties on Travis

```server=192.168.107.211
port=5432
database=postgres
username=test
password=test
slaveServer=192.168.107.211
slavePort=5433
slaveServer2=192.168.107.211
slavePort2=5434
```
davecramer: @ChenHuajun the problem is having two servers on travis. not configuring it.
ChenHuajun: @davecramer @vlsi 
I am not familiar with Travis before . Do you mean should modify Travis's script to create two hot standby servers ?
If so, i think the two extra servers could be created and MultiHostTestSuite would be run when the environment variable REPLICATION=Y . If you agree with that, i will modified this patch.
davecramer: @ChenHuajun yes, I agree, please modify so that the test can run. Thanks!
ChenHuajun: @vlsi the MultiHostsConnectionTest has  been added to  Travis, Please check again.
vlsi: Just a note: slaves are configured for `REPLICATION=Y` jobs, and multihost tests get executed
```
Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.185 sec - in org.postgresql.test.hostchooser.MultiHostTestSuite
```
ChenHuajun: @vlsi I had rebase this path to resolve confict with #1028 and refactor to avoid  two many repeated code in ConnectionFactoryImpl.java as you pointed, Please check again.

Patch:
diff --git a/.travis.yml b/.travis.yml
index b0ee930a..d94a4248 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,8 @@ dist: trusty
 before_script:
   - test $(grep "after_n_builds" codecov.yml | tr -d '[:space:]' | cut -d":" -f2) -eq $(grep -e "COVERAGE=Y$" .travis.yml | wc -l) || exit 1
   - export PG_DATADIR="/etc/postgresql/${PG_VERSION}/main"
+  - export PG_SLAVE1_DATADIR="/etc/postgresql/${PG_VERSION}/slave1"
+  - export PG_SLAVE2_DATADIR="/etc/postgresql/${PG_VERSION}/slave2"
   -  ./.travis/travis_install_postgres.sh
   - test "x$XA" == 'x' || ./.travis/travis_configure_xa.sh
   - test "x$REPLICATION" == 'x' || ./.travis/travis_configure_replication.sh
@@ -13,6 +15,7 @@ before_script:
   - test "x$PG_VERSION" = 'xHEAD' || psql -U postgres -c "create user test with password 'test';"
   - test "x$REPLICATION" == 'x' || psql -U postgres -c "alter user test with replication;"
   - psql -c 'create database test owner test;' -U postgres
+  - test "x$REPLICATION" == 'x' || ./.travis/travis_create_slaves.sh
   - echo "MAVEN_OPTS='-Xmx1g -Dgpg.skip=true'" > ~/.mavenrc
   - test "x$PG_VERSION" == 'x' || test "x$NO_HSTORE" == 'xY' || psql test -c 'CREATE EXTENSION hstore;' -U postgres
   - test "x$PG_VERSION" == 'x' || test "x$CREATE_PLPGSQL" == 'x' || createlang -U postgres plpgsql test
diff --git a/.travis/travis_create_slaves.sh b/.travis/travis_create_slaves.sh
new file mode 100755
index 00000000..2eba2450
--- /dev/null
+++ b/.travis/travis_create_slaves.sh
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+set -x -e
+
+if [ -z "$PG_VERSION" ]
+then
+    echo "env PG_VERSION not define";
+elif [ "x${PG_VERSION}" = "xHEAD" ]
+then
+    PG_CTL="/usr/local/pgsql/bin/pg_ctl"
+    PG_BASEBACKUP="/usr/local/pgsql/bin/pg_basebackup"
+else 
+    PG_CTL="/usr/lib/postgresql/${PG_VERSION}/bin/pg_ctl"
+    PG_BASEBACKUP="/usr/lib/postgresql/${PG_VERSION}/bin/pg_basebackup"
+fi
+
+#Create Slave 1
+sudo rm -rf ${PG_SLAVE1_DATADIR}
+sudo mkdir -p ${PG_SLAVE1_DATADIR}
+sudo chmod 700 ${PG_SLAVE1_DATADIR}
+sudo chown -R postgres:postgres ${PG_SLAVE1_DATADIR}
+
+sudo su postgres -c "$PG_BASEBACKUP -Upostgres -D ${PG_SLAVE1_DATADIR} -X stream"
+
+sudo su postgres -c "echo standby_mode = \'on\' >${PG_SLAVE1_DATADIR}/recovery.conf"
+sudo su postgres -c "echo primary_conninfo = \'host=localhost port=5432 user=test password=test\' >>${PG_SLAVE1_DATADIR}/recovery.conf"
+sudo su postgres -c "echo recovery_target_timeline = \'latest\' >>${PG_SLAVE1_DATADIR}/recovery.conf"
+
+if [[ "x${PG_VERSION}" != "xHEAD" ]]
+then
+    sudo su postgres -c "echo 'local all all trust' > ${PG_SLAVE1_DATADIR}/pg_hba.conf"
+    sudo su postgres -c "echo 'host all all 127.0.0.1/32 trust' >> ${PG_SLAVE1_DATADIR}/pg_hba.conf"
+    sudo su postgres -c "echo 'host all all ::1/128 trust' >> ${PG_SLAVE1_DATADIR}/pg_hba.conf"
+
+    sudo su postgres -c "touch ${PG_SLAVE1_DATADIR}/pg_ident.conf"
+
+    sudo su postgres -c "cp -f ${PG_DATADIR}/postgresql.conf ${PG_SLAVE1_DATADIR}/postgresql.conf"
+    sudo sed -i -e "/^[ \t]*data_directory.*/d" ${PG_SLAVE1_DATADIR}/postgresql.conf
+    sudo sed -i -e "/^[ \t]*hba_file.*/d" ${PG_SLAVE1_DATADIR}/postgresql.conf
+    sudo sed -i -e "/^[ \t]*ident_file.*/d" ${PG_SLAVE1_DATADIR}/postgresql.conf
+    sudo sed -i -e "s/^#\?hot_standby.*/hot_standby = on/g" ${PG_SLAVE1_DATADIR}/postgresql.conf
+fi
+
+#Start Slave 1
+sudo su postgres -c "$PG_CTL -D ${PG_SLAVE1_DATADIR} -w -t 300 -c -o '-p 5433' -l /tmp/postgres_slave1.log start" || (sudo tail /tmp/postgres_slave1.log ; exit 1)
+sudo tail /tmp/postgres_slave1.log
+
+#Create Slave 2
+sudo rm -rf ${PG_SLAVE2_DATADIR}
+sudo mkdir -p ${PG_SLAVE2_DATADIR}
+sudo chmod 700 ${PG_SLAVE2_DATADIR}
+sudo chown -R postgres:postgres ${PG_SLAVE2_DATADIR}
+
+sudo su postgres -c "$PG_BASEBACKUP -Upostgres -D ${PG_SLAVE2_DATADIR} -X stream"
+
+sudo su postgres -c "echo standby_mode = \'on\' >${PG_SLAVE2_DATADIR}/recovery.conf"
+sudo su postgres -c "echo primary_conninfo = \'host=localhost port=5432 user=test password=test\' >>${PG_SLAVE2_DATADIR}/recovery.conf"
+sudo su postgres -c "echo recovery_target_timeline = \'latest\' >>${PG_SLAVE2_DATADIR}/recovery.conf"
+
+if [[ "x${PG_VERSION}" != "xHEAD" ]]
+then
+    sudo su postgres -c "echo 'local all all trust' > ${PG_SLAVE2_DATADIR}/pg_hba.conf"
+    sudo su postgres -c "echo 'host all all 127.0.0.1/32 trust' >> ${PG_SLAVE2_DATADIR}/pg_hba.conf"
+    sudo su postgres -c "echo 'host all all ::1/128 trust' >> ${PG_SLAVE2_DATADIR}/pg_hba.conf"
+
+    sudo su postgres -c "touch ${PG_SLAVE2_DATADIR}/pg_ident.conf"
+
+    sudo su postgres -c "cp -f ${PG_DATADIR}/postgresql.conf ${PG_SLAVE2_DATADIR}/postgresql.conf"
+    sudo sed -i -e "/^[ \t]*data_directory.*/d" ${PG_SLAVE2_DATADIR}/postgresql.conf
+    sudo sed -i -e "/^[ \t]*hba_file.*/d" ${PG_SLAVE2_DATADIR}/postgresql.conf
+    sudo sed -i -e "/^[ \t]*ident_file.*/d" ${PG_SLAVE2_DATADIR}/postgresql.conf
+    sudo sed -i -e "s/^#\?hot_standby.*/hot_standby = on/g" ${PG_SLAVE2_DATADIR}/postgresql.conf
+fi
+
+#Start Slave 2
+sudo su postgres -c "$PG_CTL -D ${PG_SLAVE2_DATADIR} -w -t 300 -c -o '-p 5434' -l /tmp/postgres_slave2.log start" || (sudo tail /tmp/postgres_slave2.log ; exit 1)
+sudo tail /tmp/postgres_slave2.log
diff --git a/README.md b/README.md
index a932c49b..9fc9f505 100644
--- a/README.md
+++ b/README.md
@@ -141,7 +141,7 @@ In addition to the standard connection parameters the driver supports a number o
 | assumeMinServerVersion        | String  | null    | Assume the server is at least that version |
 | currentSchema                 | String  | null    | Specify the schema to be set in the search-path |
 | targetServerType              | String  | any     | Specifies what kind of server to connect, possible values: any, master, slave, preferSlave |
-| hostRecheckSeconds            | Integer | 10      | Specifies period (seconds) after host statuses are checked again in case they have changed |
+| hostRecheckSeconds            | Integer | 10      | Specifies period (seconds) after which the host status is checked again in case it has changed |
 | loadBalanceHosts              | Boolean | false   | If disabled hosts are connected in the given order. If enabled hosts are chosen randomly from the set of suitable candidates |
 | socketFactory                 | String  | null    | Specify a socket factory for socket creation |
 | socketFactoryArg              | String  | null    | Argument forwarded to constructor of SocketFactory class. |
diff --git a/build.properties b/build.properties
index 6317389f..d41f4071 100644
--- a/build.properties
+++ b/build.properties
@@ -5,6 +5,10 @@
 
 server=localhost
 port=5432
+slaveServer=localhost
+slavePort=5433
+slaveServer2=localhost
+slavePort2=5434
 database=test
 username=test
 password=test
diff --git a/pgjdbc/src/main/java/org/postgresql/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
index 3fa9654a..92ecbfa9 100644
--- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java
+++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
@@ -359,7 +359,7 @@ public enum PGProperty {
       "If disabled hosts are connected in the given order. If enabled hosts are chosen randomly from the set of suitable candidates"),
 
   HOST_RECHECK_SECONDS("hostRecheckSeconds", "10",
-      "Specifies period (seconds) after host statuses are checked again in case they have changed"),
+      "Specifies period (seconds) after which the host status is checked again in case it has changed"),
 
   /**
    * Specifies which mode is used to execute queries to database: simple means ('Q' execute, no parse, no bind, text mode only),
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
index 9dcc42b8..7fc3eaf8 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
@@ -15,6 +15,7 @@ import org.postgresql.core.SetupQueryRunner;
 import org.postgresql.core.SocketFactoryFactory;
 import org.postgresql.core.Utils;
 import org.postgresql.core.Version;
+import org.postgresql.hostchooser.CandidateHost;
 import org.postgresql.hostchooser.GlobalHostStatusTracker;
 import org.postgresql.hostchooser.HostChooser;
 import org.postgresql.hostchooser.HostChooserFactory;
@@ -32,11 +33,14 @@ import java.io.IOException;
 import java.net.ConnectException;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 import java.util.TimeZone;
 import java.util.logging.Level;
+import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
 import javax.net.SocketFactory;
@@ -121,15 +125,30 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
           PSQLState.CONNECTION_UNABLE_TO_CONNECT);
     }
 
+    long lastKnownTime = System.currentTimeMillis() - PGProperty.HOST_RECHECK_SECONDS.getInt(info) * 1000;
+
     SocketFactory socketFactory = SocketFactoryFactory.getSocketFactory(info);
 
     HostChooser hostChooser =
         HostChooserFactory.createHostChooser(hostSpecs, targetServerType, info);
-    Iterator<HostSpec> hostIter = hostChooser.iterator();
+    Iterator<CandidateHost> hostIter = hostChooser.iterator();
+    Map<HostSpec, HostStatus> knownStates = new HashMap<HostSpec, HostStatus>();
     while (hostIter.hasNext()) {
-      HostSpec hostSpec = hostIter.next();
+      CandidateHost candidateHost = hostIter.next();
+      HostSpec hostSpec = candidateHost.hostSpec;
       LOGGER.log(Level.FINE, "Trying to establish a protocol version 3 connection to {0}", hostSpec);
 
+      // Note: per-connect-attempt status map is used here instead of GlobalHostStatusTracker
+      // for the case when "no good hosts" match (e.g. all the hosts are known as "connectfail")
+      // In that case, the system tries to connect to each host in order, thus it should not look into
+      // GlobalHostStatusTracker
+      HostStatus knownStatus = knownStates.get(hostSpec);
+      if (knownStatus != null && !candidateHost.targetServerType.allowConnectingTo(knownStatus)) {
+        LOGGER.log(Level.FINER, "Known status of host {0} is {1}, and required status was {2}. Will try next host",
+            new Object[]{hostSpec, knownStatus, candidateHost.targetServerType});
+        continue;
+      }
+
       //
       // Establish a connection.
       //
@@ -195,19 +214,14 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
 
         // Check Master or Slave
         HostStatus hostStatus = HostStatus.ConnectOK;
-        if (targetServerType != HostRequirement.any) {
+        if (candidateHost.targetServerType != HostRequirement.any) {
           hostStatus = isMaster(queryExecutor) ? HostStatus.Master : HostStatus.Slave;
         }
         GlobalHostStatusTracker.reportHostStatus(hostSpec, hostStatus);
-        if (!targetServerType.allowConnectingTo(hostStatus)) {
+        knownStates.put(hostSpec, hostStatus);
+        if (!candidateHost.targetServerType.allowConnectingTo(hostStatus)) {
           queryExecutor.close();
-          if (hostIter.hasNext()) {
-            // still more addresses to try
-            continue;
-          }
-          throw new PSQLException(GT
-              .tr("Could not find a server with specified targetServerType: {0}", targetServerType),
-              PSQLState.CONNECTION_UNABLE_TO_CONNECT);
+          continue;
         }
 
         runInitialQueries(queryExecutor, info);
@@ -216,7 +230,7 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
         return queryExecutor;
       } catch (UnsupportedProtocolException upe) {
         // Swallow this and return null so ConnectionFactory tries the next protocol.
-        LOGGER.log(Level.SEVERE, "Protocol not supported, abandoning connection.");
+        LOGGER.log(Level.SEVERE, "Protocol not supported, abandoning connection.", upe);
         closeStream(newStream);
         return null;
       } catch (ConnectException cex) {
@@ -224,6 +238,8 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
         // ConnectException is thrown when the connection cannot be made.
         // we trap this an return a more meaningful message for the end user
         GlobalHostStatusTracker.reportHostStatus(hostSpec, HostStatus.ConnectFail);
+        knownStates.put(hostSpec, HostStatus.ConnectFail);
+        log(Level.WARNING, "ConnectException occurred while connecting to {0}", cex, hostSpec);
         if (hostIter.hasNext()) {
           // still more addresses to try
           continue;
@@ -234,6 +250,8 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
       } catch (IOException ioe) {
         closeStream(newStream);
         GlobalHostStatusTracker.reportHostStatus(hostSpec, HostStatus.ConnectFail);
+        knownStates.put(hostSpec, HostStatus.ConnectFail);
+        log(Level.WARNING, "IOException occurred while connecting to {0}", ioe, hostSpec);
         if (hostIter.hasNext()) {
           // still more addresses to try
           continue;
@@ -242,6 +260,9 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
             PSQLState.CONNECTION_UNABLE_TO_CONNECT, ioe);
       } catch (SQLException se) {
         closeStream(newStream);
+        log(Level.WARNING, "SQLException occurred while connecting to {0}", se, hostSpec);
+        GlobalHostStatusTracker.reportHostStatus(hostSpec, HostStatus.ConnectFail);
+        knownStates.put(hostSpec, HostStatus.ConnectFail);
         if (hostIter.hasNext()) {
           // still more addresses to try
           continue;
@@ -249,7 +270,8 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
         throw se;
       }
     }
-    throw new PSQLException(GT.tr("The connection url is invalid."),
+    throw new PSQLException(GT
+        .tr("Could not find a server with specified targetServerType: {0}", targetServerType),
         PSQLState.CONNECTION_UNABLE_TO_CONNECT);
   }
 
@@ -287,6 +309,16 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
     return paramList;
   }
 
+  private static void log(Level level, String msg, Throwable thrown, Object... params) {
+    if (!LOGGER.isLoggable(level)) {
+      return;
+    }
+    LogRecord rec = new LogRecord(level, msg);
+    rec.setParameters(params);
+    rec.setThrown(thrown);
+    LOGGER.log(rec);
+  }
+
   /**
    * Convert Java time zone to postgres time zone. All others stay the same except that GMT+nn
    * changes to GMT-nn and vise versa.
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
index 76967e0d..f596419e 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
@@ -53,6 +53,7 @@ import java.sql.SQLWarning;
 import java.sql.Statement;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -1394,6 +1395,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
     pgStream.sendChar('S'); // Sync
     pgStream.sendInteger4(4); // Length
     pgStream.flush();
+    // Below "add queues" are likely not required at all
     pendingExecuteQueue.add(new ExecuteRequest(sync, null, true));
     pendingDescribePortalQueue.add(sync);
   }
@@ -2067,8 +2069,10 @@ public class QueryExecutorImpl extends QueryExecutorBase {
           Portal currentPortal = executeData.portal;
 
           Field[] fields = currentQuery.getFields();
-          if (fields != null && !noResults && tuples == null) {
-            tuples = new ArrayList<byte[][]>();
+          if (fields != null && tuples == null) {
+            // When no results expected, pretend an empty resultset was returned
+            // Not sure if new ArrayList can be always replaced with emptyList
+            tuples = noResults ? Collections.<byte[][]>emptyList() : new ArrayList<byte[][]>();
           }
 
           handler.handleResultRows(currentQuery, fields, tuples, currentPortal);
@@ -2115,8 +2119,10 @@ public class QueryExecutorImpl extends QueryExecutorBase {
           }
 
           Field[] fields = currentQuery.getFields();
-          if (fields != null && !noResults && tuples == null) {
-            tuples = new ArrayList<byte[][]>();
+          if (fields != null && tuples == null) {
+            // When no results expected, pretend an empty resultset was returned
+            // Not sure if new ArrayList can be always replaced with emptyList
+            tuples = noResults ? Collections.<byte[][]>emptyList() : new ArrayList<byte[][]>();
           }
 
           // If we received tuples we must know the structure of the
@@ -2327,8 +2333,19 @@ public class QueryExecutorImpl extends QueryExecutorBase {
           }
 
           pendingParseQueue.clear(); // No more ParseComplete messages expected.
-          pendingDescribeStatementQueue.clear(); // No more ParameterDescription messages expected.
-          pendingDescribePortalQueue.clear(); // No more RowDescription messages expected.
+          // Pending "describe" requests might be there in case of error
+          // If that is the case, reset "described" status, so the statement is properly
+          // described on next execution
+          while (!pendingDescribeStatementQueue.isEmpty()) {
+            DescribeRequest request = pendingDescribeStatementQueue.removeFirst();
+            LOGGER.log(Level.FINEST, " FE marking setStatementDescribed(false) for query {0}", request.query);
+            request.query.setStatementDescribed(false);
+          }
+          while (!pendingDescribePortalQueue.isEmpty()) {
+            SimpleQuery describePortalQuery = pendingDescribePortalQueue.removeFirst();
+            LOGGER.log(Level.FINEST, " FE marking setPortalDescribed(false) for query {0}", describePortalQuery);
+            describePortalQuery.setPortalDescribed(false);
+          }
           pendingBindQueue.clear(); // No more BindComplete messages expected.
           pendingExecuteQueue.clear(); // No more query executions expected.
           break;
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/CandidateHost.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/CandidateHost.java
new file mode 100644
index 00000000..220ae1cd
--- /dev/null
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/CandidateHost.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.hostchooser;
+
+import org.postgresql.util.HostSpec;
+
+
+/**
+ * Candidate host to be connected.
+ */
+public class CandidateHost {
+  public final HostSpec hostSpec;
+  public final HostRequirement targetServerType;
+
+  public CandidateHost(HostSpec hostSpec, HostRequirement targetServerType) {
+    this.hostSpec = hostSpec;
+    this.targetServerType = targetServerType;
+  }
+}
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/GlobalHostStatusTracker.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/GlobalHostStatusTracker.java
index 6479a937..bd53ab93 100644
--- a/pgjdbc/src/main/java/org/postgresql/hostchooser/GlobalHostStatusTracker.java
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/GlobalHostStatusTracker.java
@@ -30,23 +30,16 @@ public class GlobalHostStatusTracker {
   public static void reportHostStatus(HostSpec hostSpec, HostStatus hostStatus) {
     long now = currentTimeMillis();
     synchronized (hostStatusMap) {
-      HostSpecStatus oldStatus = hostStatusMap.get(hostSpec);
-      if (oldStatus == null || updateStatusFromTo(oldStatus.status, hostStatus)) {
-        hostStatusMap.put(hostSpec, new HostSpecStatus(hostSpec, hostStatus, now));
+      HostSpecStatus hostSpecStatus = hostStatusMap.get(hostSpec);
+      if (hostSpecStatus == null) {
+        hostSpecStatus = new HostSpecStatus(hostSpec);
+        hostStatusMap.put(hostSpec, hostSpecStatus);
       }
+      hostSpecStatus.status = hostStatus;
+      hostSpecStatus.lastUpdated = now;
     }
   }
 
-  private static boolean updateStatusFromTo(HostStatus oldStatus, HostStatus newStatus) {
-    if (oldStatus == null) {
-      return true;
-    }
-    if (newStatus == HostStatus.ConnectOK) {
-      return oldStatus != HostStatus.Master && oldStatus != HostStatus.Slave;
-    }
-    return true;
-  }
-
   /**
    * Returns a list of candidate hosts that have the required targetServerType.
    *
@@ -55,38 +48,31 @@ public class GlobalHostStatusTracker {
    * @param hostRecheckMillis How stale information is allowed.
    * @return candidate hosts to connect to.
    */
-  static List<HostSpecStatus> getCandidateHosts(HostSpec[] hostSpecs,
+  static List<HostSpec> getCandidateHosts(HostSpec[] hostSpecs,
       HostRequirement targetServerType, long hostRecheckMillis) {
-    List<HostSpecStatus> candidates = new ArrayList<HostSpecStatus>(hostSpecs.length);
+    List<HostSpec> candidates = new ArrayList<HostSpec>(hostSpecs.length);
     long latestAllowedUpdate = currentTimeMillis() - hostRecheckMillis;
     synchronized (hostStatusMap) {
       for (HostSpec hostSpec : hostSpecs) {
         HostSpecStatus hostInfo = hostStatusMap.get(hostSpec);
-        // return null status wrapper if if the current value is not known or is too old
-        if (hostInfo == null || hostInfo.lastUpdated < latestAllowedUpdate) {
-          hostInfo = new HostSpecStatus(hostSpec, null, Long.MAX_VALUE);
-        }
         // candidates are nodes we do not know about and the nodes with correct type
-        if (hostInfo.status == null || targetServerType.allowConnectingTo(hostInfo.status)) {
-          candidates.add(hostInfo);
+        if (hostInfo == null
+            || hostInfo.lastUpdated < latestAllowedUpdate
+            || targetServerType.allowConnectingTo(hostInfo.status)) {
+          candidates.add(hostSpec);
         }
       }
     }
     return candidates;
   }
 
-  /**
-   * Immutable structure of known status of one HostSpec.
-   */
   static class HostSpecStatus {
     final HostSpec host;
-    final HostStatus status;
-    final long lastUpdated;
+    HostStatus status;
+    long lastUpdated;
 
-    HostSpecStatus(HostSpec host, HostStatus hostStatus, long lastUpdated) {
+    HostSpecStatus(HostSpec host) {
       this.host = host;
-      this.status = hostStatus;
-      this.lastUpdated = lastUpdated;
     }
 
     @Override
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooser.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooser.java
index d726c285..a506b7bf 100644
--- a/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooser.java
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooser.java
@@ -5,19 +5,17 @@
 
 package org.postgresql.hostchooser;
 
-import org.postgresql.util.HostSpec;
-
 import java.util.Iterator;
 
 /**
  * Lists connections in preferred order.
  */
-public interface HostChooser extends Iterable<HostSpec> {
+public interface HostChooser extends Iterable<CandidateHost> {
   /**
    * Lists connection hosts in preferred order.
    *
    * @return connection hosts in preferred order.
    */
   @Override
-  Iterator<HostSpec> iterator();
+  Iterator<CandidateHost> iterator();
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooserFactory.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooserFactory.java
index 4fb27b1c..4099fa03 100644
--- a/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooserFactory.java
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooserFactory.java
@@ -17,7 +17,7 @@ public class HostChooserFactory {
   public static HostChooser createHostChooser(HostSpec[] hostSpecs,
       HostRequirement targetServerType, Properties info) {
     if (hostSpecs.length == 1) {
-      return new SingleHostChooser(hostSpecs[0]);
+      return new SingleHostChooser(hostSpecs[0], targetServerType);
     }
     return new MultiHostChooser(hostSpecs, targetServerType, info);
   }
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/MultiHostChooser.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/MultiHostChooser.java
index 8d1925ba..4ee2014b 100644
--- a/pgjdbc/src/main/java/org/postgresql/hostchooser/MultiHostChooser.java
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/MultiHostChooser.java
@@ -5,17 +5,16 @@
 
 package org.postgresql.hostchooser;
 
-import static java.util.Arrays.asList;
 import static java.util.Collections.shuffle;
-import static java.util.Collections.sort;
 
 import org.postgresql.PGProperty;
-import org.postgresql.hostchooser.GlobalHostStatusTracker.HostSpecStatus;
 import org.postgresql.util.HostSpec;
 import org.postgresql.util.PSQLException;
 
+import java.util.AbstractList;
 import java.util.ArrayList;
-import java.util.Comparator;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Properties;
@@ -23,13 +22,13 @@ import java.util.Properties;
 /**
  * HostChooser that keeps track of known host statuses.
  */
-public class MultiHostChooser implements HostChooser {
+class MultiHostChooser implements HostChooser {
   private HostSpec[] hostSpecs;
   private final HostRequirement targetServerType;
   private int hostRecheckTime;
   private boolean loadBalance;
 
-  protected MultiHostChooser(HostSpec[] hostSpecs, HostRequirement targetServerType,
+  MultiHostChooser(HostSpec[] hostSpecs, HostRequirement targetServerType,
       Properties info) {
     this.hostSpecs = hostSpecs;
     this.targetServerType = targetServerType;
@@ -42,79 +41,98 @@ public class MultiHostChooser implements HostChooser {
   }
 
   @Override
-  public Iterator<HostSpec> iterator() {
-    List<HostSpecStatus> candidates =
-        GlobalHostStatusTracker.getCandidateHosts(hostSpecs, targetServerType, hostRecheckTime);
-    // if no candidates are suitable (all wrong type or unavailable) then we try original list in
-    // order
-    if (candidates.isEmpty()) {
-      return asList(hostSpecs).iterator();
-    }
-    if (candidates.size() == 1) {
-      return asList(candidates.get(0).host).iterator();
+  public Iterator<CandidateHost> iterator() {
+    Iterator<CandidateHost> res = candidateIterator();
+    if (!res.hasNext()) {
+      // In case all the candidate hosts are unavailable or do not match, try all the hosts just in case
+      List<HostSpec> allHosts = Arrays.asList(hostSpecs);
+      if (loadBalance) {
+        allHosts = new ArrayList<HostSpec>(allHosts);
+        Collections.shuffle(allHosts);
+      }
+      res = withReqStatus(targetServerType, allHosts).iterator();
     }
-    sortCandidates(candidates);
-    shuffleGoodHosts(candidates);
-    return extractHostSpecs(candidates).iterator();
+    return res;
   }
 
-  private void sortCandidates(List<HostSpecStatus> candidates) {
-    if (targetServerType == HostRequirement.any) {
-      return;
+  private Iterator<CandidateHost> candidateIterator() {
+    if (targetServerType != HostRequirement.preferSlave) {
+      return getCandidateHosts(targetServerType).iterator();
     }
-    sort(candidates, new HostSpecByTargetServerTypeComparator());
-  }
 
-  private void shuffleGoodHosts(List<HostSpecStatus> candidates) {
-    if (!loadBalance) {
-      return;
+    // preferSlave tries to find slave hosts first
+    // Note: sort does not work here since there are "unknown" hosts,
+    // and that "unkown" might turn out to be master, so we should discard that
+    // if other slaves exist
+    List<CandidateHost> slaves = getCandidateHosts(HostRequirement.slave);
+    List<CandidateHost> any = getCandidateHosts(HostRequirement.any);
+
+    if (slaves.isEmpty()) {
+      return any.iterator();
     }
-    int count;
-    for (count = 1; count < candidates.size(); count++) {
-      HostSpecStatus hostSpecStatus = candidates.get(count);
-      if (hostSpecStatus.status != null
-          && !targetServerType.allowConnectingTo(hostSpecStatus.status)) {
-        break;
-      }
+
+    if (any.isEmpty()) {
+      return slaves.iterator();
     }
-    if (count == 1) {
-      return;
+
+    if (slaves.get(slaves.size() - 1).equals(any.get(0))) {
+      // When the last slave's hostspec is the same as the first in "any" list, there's no need
+      // to attempt to connect it as "slave"
+      // Note: this is only an optimization
+      slaves = rtrim(1, slaves);
     }
-    List<HostSpecStatus> goodHosts = candidates.subList(0, count);
-    shuffle(goodHosts);
+    return append(slaves, any).iterator();
   }
 
-  private List<HostSpec> extractHostSpecs(List<HostSpecStatus> hostSpecStatuses) {
-    List<HostSpec> hostSpecs = new ArrayList<HostSpec>(hostSpecStatuses.size());
-    for (HostSpecStatus hostSpecStatus : hostSpecStatuses) {
-      hostSpecs.add(hostSpecStatus.host);
+  private List<CandidateHost> getCandidateHosts(HostRequirement hostRequirement) {
+    List<HostSpec> candidates =
+        GlobalHostStatusTracker.getCandidateHosts(hostSpecs, hostRequirement, hostRecheckTime);
+    if (loadBalance) {
+      shuffle(candidates);
     }
-    return hostSpecs;
+    return withReqStatus(hostRequirement, candidates);
   }
 
-  class HostSpecByTargetServerTypeComparator implements Comparator<HostSpecStatus> {
-    @Override
-    public int compare(HostSpecStatus o1, HostSpecStatus o2) {
-      int r1 = rank(o1.status, targetServerType);
-      int r2 = rank(o2.status, targetServerType);
-      return r1 == r2 ? 0 : r1 > r2 ? -1 : 1;
-    }
+  private List<CandidateHost> withReqStatus(final HostRequirement requirement, final List<HostSpec> hosts) {
+    return new AbstractList<CandidateHost>() {
+      @Override
+      public CandidateHost get(int index) {
+        return new CandidateHost(hosts.get(index), requirement);
+      }
+
+      @Override
+      public int size() {
+        return hosts.size();
+      }
+    };
+  }
 
-    private int rank(HostStatus status, HostRequirement targetServerType) {
-      if (status == HostStatus.ConnectFail) {
-        return -1;
+  private <T> List<T> append(final List<T> a, final List<T> b) {
+    return new AbstractList<T>() {
+      @Override
+      public T get(int index) {
+        return index < a.size() ? a.get(index) : b.get(index - a.size());
       }
-      switch (targetServerType) {
-        case master:
-          return status == HostStatus.Master || status == null ? 1 : 0;
-        case slave:
-          return status == HostStatus.Slave || status == null ? 1 : 0;
-        case preferSlave:
-          return status == HostStatus.Slave || status == null ? 2
-              : status == HostStatus.Master ? 1 : 0;
-        default:
-          return 0;
+
+      @Override
+      public int size() {
+        return a.size() + b.size();
       }
-    }
+    };
+  }
+
+  private <T> List<T> rtrim(final int size, final List<T> a) {
+    return new AbstractList<T>() {
+      @Override
+      public T get(int index) {
+        return a.get(index);
+      }
+
+      @Override
+      public int size() {
+        return Math.max(0, a.size() - size);
+      }
+    };
   }
+
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/SingleHostChooser.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/SingleHostChooser.java
index 97965b07..e79e8348 100644
--- a/pgjdbc/src/main/java/org/postgresql/hostchooser/SingleHostChooser.java
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/SingleHostChooser.java
@@ -14,15 +14,15 @@ import java.util.Iterator;
 /**
  * Host chooser that returns the single host.
  */
-public class SingleHostChooser implements HostChooser {
-  private final Collection<HostSpec> hostSpec;
+class SingleHostChooser implements HostChooser {
+  private final Collection<CandidateHost> candidateHost;
 
-  public SingleHostChooser(HostSpec hostSpec) {
-    this.hostSpec = Collections.singletonList(hostSpec);
+  SingleHostChooser(HostSpec hostSpec, HostRequirement targetServerType) {
+    this.candidateHost = Collections.singletonList(new CandidateHost(hostSpec, targetServerType));
   }
 
   @Override
-  public Iterator<HostSpec> iterator() {
-    return hostSpec.iterator();
+  public Iterator<CandidateHost> iterator() {
+    return candidateHost.iterator();
   }
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/util/PSQLState.java b/pgjdbc/src/main/java/org/postgresql/util/PSQLState.java
index a77b4b42..c097f460 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/PSQLState.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/PSQLState.java
@@ -51,6 +51,7 @@ public enum PSQLState {
   NOT_IMPLEMENTED("0A000"),
 
   DATA_ERROR("22000"),
+  STRING_DATA_RIGHT_TRUNCATION("22001"),
   NUMERIC_VALUE_OUT_OF_RANGE("22003"),
   BAD_DATETIME_FORMAT("22007"),
   DATETIME_OVERFLOW("22008"),


Test patch:
diff --git a/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostTestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostTestSuite.java
index b18f9a5a..2f1b8174 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostTestSuite.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostTestSuite.java
@@ -31,6 +31,17 @@ public class MultiHostTestSuite extends TestSuite {
     return DriverManager.getConnection(TestUtil.getURL(getSlaveServer(), getSlavePort()), props);
   }
 
+  public static java.sql.Connection openSlaveDB2() throws Exception {
+    TestUtil.initDriver();
+
+    Properties props = new Properties();
+
+    props.setProperty("user", TestUtil.getUser());
+    props.setProperty("password", TestUtil.getPassword());
+
+    return DriverManager.getConnection(TestUtil.getURL(getSlaveServer2(), getSlavePort2()), props);
+  }
+
   /*
    * Returns the Test server
    */
@@ -46,6 +57,21 @@ public class MultiHostTestSuite extends TestSuite {
         .parseInt(System.getProperty("slavePort", String.valueOf(TestUtil.getPort() + 1)));
   }
 
+  /*
+   * Returns the Test server
+   */
+  public static String getSlaveServer2() {
+    return System.getProperty("slaveServer2", TestUtil.getServer());
+  }
+
+  /*
+   * Returns the Test port
+   */
+  public static int getSlavePort2() {
+    return Integer
+        .parseInt(System.getProperty("slavePort2", String.valueOf(TestUtil.getPort() + 2)));
+  }
+
   /*
    * The main entry point for JUnit
    */
@@ -55,6 +81,9 @@ public class MultiHostTestSuite extends TestSuite {
     try {
       Connection connection = openSlaveDB();
       TestUtil.closeDB(connection);
+
+      connection = openSlaveDB2();
+      TestUtil.closeDB(connection);
     } catch (PSQLException ex) {
       // replication instance is not available, but suite must have at lest one test case
       suite.addTestSuite(DummyTest.class);
diff --git a/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java b/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java
index 6d9d7941..04c38746 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java
@@ -12,7 +12,7 @@ import static org.postgresql.hostchooser.HostRequirement.any;
 import static org.postgresql.hostchooser.HostRequirement.master;
 import static org.postgresql.hostchooser.HostRequirement.preferSlave;
 import static org.postgresql.hostchooser.HostRequirement.slave;
-import static org.postgresql.hostchooser.HostStatus.ConnectFail;
+import static org.postgresql.hostchooser.HostStatus.Master;
 import static org.postgresql.hostchooser.HostStatus.Slave;
 import static org.postgresql.test.TestUtil.closeDB;
 
@@ -41,9 +41,12 @@ public class MultiHostsConnectionTest extends TestCase {
   static final String master1 = TestUtil.getServer() + ":" + TestUtil.getPort();
   static final String slave1 =
       MultiHostTestSuite.getSlaveServer() + ":" + MultiHostTestSuite.getSlavePort();
+  static final String slave2 =
+      MultiHostTestSuite.getSlaveServer2() + ":" + MultiHostTestSuite.getSlavePort2();
   static final String fake1 = "127.127.217.217:1";
   static String masterIp;
   static String slaveIp;
+  static String slaveIp2;
   static String fakeIp = fake1;
 
   static Connection con;
@@ -63,6 +66,10 @@ public class MultiHostsConnectionTest extends TestCase {
       slaveIp = getRemoteHostSpec();
       closeDB(con);
 
+      con = MultiHostTestSuite.openSlaveDB2();
+      slaveIp2 = getRemoteHostSpec();
+      closeDB(con);
+
     } catch (Exception e) {
       throw new RuntimeException(e);
     }
@@ -106,7 +113,8 @@ public class MultiHostsConnectionTest extends TestCase {
       sb.append(target).append(',');
     }
     sb.setLength(sb.length() - 1);
-    sb.append("/test");
+    sb.append("/");
+    sb.append(TestUtil.getDatabase());
 
     return con = DriverManager.getConnection(sb.toString(), props);
   }
@@ -166,9 +174,9 @@ public class MultiHostsConnectionTest extends TestCase {
 
     getConnection(master, false, fake1, slave1, master1);
     assertRemote(masterIp);
-    assertGlobalState(fake1, "ConnectFail");
-    assertGlobalState(master1, "Master");
-    assertGlobalState(slave1, "Slave");
+    assertGlobalState(fake1, "ConnectFail"); // cached
+    assertGlobalState(master1, "Master"); // connected to master
+    assertGlobalState(slave1, "Slave"); // was unknown, so tried to connect in order
   }
 
   public static void testConnectToSlave() throws SQLException {
@@ -180,9 +188,9 @@ public class MultiHostsConnectionTest extends TestCase {
 
     getConnection(slave, false, fake1, master1, slave1);
     assertRemote(slaveIp);
-    assertGlobalState(fake1, "ConnectFail");
-    assertGlobalState(slave1, "Slave");
-    assertGlobalState(master1, "Master");
+    assertGlobalState(fake1, "ConnectFail"); // cached
+    assertGlobalState(slave1, "Slave"); // connected
+    assertGlobalState(master1, "Master"); // tried as it was unknown
   }
 
   public static void testConnectToSlaveFirst() throws SQLException {
@@ -192,13 +200,13 @@ public class MultiHostsConnectionTest extends TestCase {
     assertGlobalState(slave1, "Slave");
     assertGlobalState(master1, null);
 
-    getConnection(preferSlave, false, fake1, master1, slave1);
-    assertRemote(masterIp);
+    getConnection(slave, false, fake1, master1, slave1);
+    assertRemote(slaveIp);
     assertGlobalState(fake1, "ConnectFail");
     assertGlobalState(slave1, "Slave");
-    assertGlobalState(master1, "Master");
+    assertGlobalState(master1, "Master"); // tried as it was unknown
 
-    getConnection(preferSlave, false, fake1, master1, slave1);
+    getConnection(preferSlave, true, fake1, master1, slave1);
     assertRemote(slaveIp);
     assertGlobalState(fake1, "ConnectFail");
     assertGlobalState(slave1, "Slave");
@@ -229,14 +237,75 @@ public class MultiHostsConnectionTest extends TestCase {
     assertTrue("Never tried to connect to fake node", fake1FoundTried);
   }
 
-  public static void testHostRechecks() throws SQLException, InterruptedException {
-    getConnection(master, true, fake1, master1, slave1);
+  public static void testLoadBalancing_preferSlave() throws SQLException {
+    Set<String> connectedHosts = new HashSet<String>();
+    Set<HostSpec> tryConnectedHosts = new HashSet<HostSpec>();
+    for (int i = 0; i < 20; ++i) {
+      getConnection(preferSlave, true, true, fake1, master1, slave1, slave2);
+      connectedHosts.add(getRemoteHostSpec());
+      tryConnectedHosts.addAll(hostStatusMap.keySet());
+      if (tryConnectedHosts.size() == 4) {
+        break;
+      }
+    }
+    assertEquals("Never connected to all slave hosts", new HashSet<String>(asList(slaveIp, slaveIp2)),
+        connectedHosts);
+    assertEquals("Never tried to connect to fake node",4, tryConnectedHosts.size());
+
+    getConnection(preferSlave, false, true, fake1, master1, slave1);
+    assertRemote(slaveIp);
+    connectedHosts.clear();
+    for (int i = 0; i < 20; ++i) {
+      getConnection(preferSlave, false, true, fake1, master1, slave1, slave2);
+      connectedHosts.add(getRemoteHostSpec());
+      if (connectedHosts.size() == 2) {
+        break;
+      }
+    }
+    assertEquals("Never connected to all slave hosts", new HashSet<String>(asList(slaveIp, slaveIp2)),
+        connectedHosts);
+
+    // connect to master when there's no slave
+    getConnection(preferSlave, true, true, fake1, master1);
     assertRemote(masterIp);
-    assertGlobalState(fake1, "ConnectFail");
-    assertGlobalState(slave1, null);
 
-    GlobalHostStatusTracker.reportHostStatus(hostSpec(master1), ConnectFail);
-    assertGlobalState(master1, "ConnectFail");
+    getConnection(preferSlave, false, true, fake1, master1);
+    assertRemote(masterIp);
+  }
+
+  public static void testLoadBalancing_slave() throws SQLException {
+    Set<String> connectedHosts = new HashSet<String>();
+    Set<HostSpec> tryConnectedHosts = new HashSet<HostSpec>();
+    for (int i = 0; i < 20; ++i) {
+      getConnection(slave, true, true, fake1, master1, slave1, slave2);
+      connectedHosts.add(getRemoteHostSpec());
+      tryConnectedHosts.addAll(hostStatusMap.keySet());
+      if (tryConnectedHosts.size() == 4) {
+        break;
+      }
+    }
+    assertEquals("Never connected to all salve hosts", new HashSet<String>(asList(slaveIp, slaveIp2)),
+        connectedHosts);
+    assertEquals("Never tried to connect to maste and fake node", 4, tryConnectedHosts.size());
+
+    getConnection(preferSlave, false, true, fake1, master1, slave1);
+    assertRemote(slaveIp);
+    connectedHosts.clear();
+    for (int i = 0; i < 20; ++i) {
+      getConnection(slave, false, true, fake1, master1, slave1, slave2);
+      connectedHosts.add(getRemoteHostSpec());
+      if (connectedHosts.size() == 2) {
+        break;
+      }
+    }
+    assertEquals("Never connected to all slave hosts", new HashSet<String>(asList(slaveIp, slaveIp2)),
+        connectedHosts);
+  }
+
+  public static void testHostRechecks() throws SQLException, InterruptedException {
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(master1), Slave);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(slave1), Master);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(fake1), Slave);
 
     try {
       getConnection(master, false, fake1, slave1, master1);
@@ -244,6 +313,10 @@ public class MultiHostsConnectionTest extends TestCase {
     } catch (SQLException ex) {
     }
 
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(master1), Slave);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(slave1), Master);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(fake1), Slave);
+
     SECONDS.sleep(3);
 
     getConnection(master, false, slave1, fake1, master1);
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/TimezoneTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/TimezoneTest.java
index b436d5ae..1fc4f4a1 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/TimezoneTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/TimezoneTest.java
@@ -191,8 +191,8 @@ public class TimezoneTest {
       ts = rs.getTimestamp(4, cGMT13);
       // 1970-01-01 15:00:00 +0300 -> 1970-01-02 01:00:00 +1300 (CHECK ME)
       assertEquals(-43200000L, ts.getTime());
-      str = rs.getString(3);
-      assertEquals("timetz -> getString" + format, "15:00:00", str);
+      str = rs.getString(4);
+      assertEquals("timetz -> getString" + format, "15:00:00+03", str);
 
       // date: 2005-01-01
       ts = rs.getTimestamp(5);
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/GeneratedKeysTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/GeneratedKeysTest.java
index 6e6f74a8..36a6e440 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/GeneratedKeysTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/GeneratedKeysTest.java
@@ -12,8 +12,10 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import org.postgresql.PGStatement;
 import org.postgresql.test.TestUtil;
 import org.postgresql.test.jdbc2.BaseTest4;
+import org.postgresql.util.PSQLState;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -66,16 +68,19 @@ public class GeneratedKeysTest extends BaseTest4 {
   private final ReturningInQuery returningInQuery;
   private final String returningClause;
 
-  public GeneratedKeysTest(ReturningInQuery returningInQuery) throws Exception {
+  public GeneratedKeysTest(ReturningInQuery returningInQuery, BinaryMode binaryMode) throws Exception {
     this.returningInQuery = returningInQuery;
     this.returningClause = returningInQuery.getClause();
+    setBinaryMode(binaryMode);
   }
 
-  @Parameterized.Parameters(name = "returningInQuery = {0}")
+  @Parameterized.Parameters(name = "returningInQuery = {0}, binary = {1}")
   public static Iterable<Object[]> data() {
     Collection<Object[]> ids = new ArrayList<Object[]>();
     for (ReturningInQuery returningInQuery : ReturningInQuery.values()) {
-      ids.add(new Object[]{returningInQuery});
+      for (BinaryMode binaryMode : BinaryMode.values()) {
+        ids.add(new Object[]{returningInQuery, binaryMode});
+      }
     }
     return ids;
   }
@@ -83,7 +88,7 @@ public class GeneratedKeysTest extends BaseTest4 {
   @Override
   public void setUp() throws Exception {
     super.setUp();
-    TestUtil.createTempTable(con, "genkeys", "a serial, b text, c int");
+    TestUtil.createTempTable(con, "genkeys", "a serial, b varchar(5), c int");
   }
 
   @Override
@@ -447,5 +452,36 @@ public class GeneratedKeysTest extends BaseTest4 {
     s.close();
   }
 
+  @Test
+  public void breakDescribeOnFirstServerPreparedExecution() throws SQLException {
+    // Test code is adapted from https://github.com/pgjdbc/pgjdbc/issues/811#issuecomment-352468388
+
+    PreparedStatement ps =
+        con.prepareStatement("insert into genkeys(b) values(?)" + returningClause,
+            Statement.RETURN_GENERATED_KEYS);
+    ps.setString(1, "TEST");
+
+    // The below "prepareThreshold - 1" executions ensure that bind failure would happen
+    // exactly on prepareThreshold execution (the first one when server flips to server-prepared)
+    int prepareThreshold = ps.unwrap(PGStatement.class).getPrepareThreshold();
+    for (int i = 0; i < prepareThreshold - 1; i++) {
+      ps.executeUpdate();
+    }
+    try {
+      // Send a value that's too long on the 5th request
+      ps.setString(1, "TESTTESTTEST");
+      ps.executeUpdate();
+    } catch (SQLException e) {
+      // Expected error: org.postgresql.util.PSQLException: ERROR: value
+      // too long for type character varying(10)
+      if (!PSQLState.STRING_DATA_RIGHT_TRUNCATION.getState().equals(e.getSQLState())) {
+        throw e;
+      }
+    }
+    // Send a valid value on the next request
+    ps.setString(1, "TEST");
+    ps.executeUpdate();
+  }
+
 }