On 06/25/2011 12:14 AM, Steve Singer wrote:
> I'm not a libpq guru but I've given your patch a look over.
> 

Thanks for the review!

I have since simplified the patch to assume that partial SSL writes are
disabled -- according to SSL_write(3) this is the default behaviour.
Now the SSL retry buffer only holds the data to be retried, the
remainder is moved to the new outBuffer.

> -The patch doesn't compile with non-ssl builds,  the debug at the bottom
> of PQSendSome isn't in an #ifdef
> 

Ack, there was another one in pqFlush. Fixed that.

> -I don't think your handling the return code properly.   Consider this case.
> 
> pqSendSome(some data)
>   sslRetryBuf = some Data
>   return 1
> pqSendSome(more data)
>   it sends all of 'some data'
>   returns 0
> 
> I think 1 should be returned because all of 'more data' still needs to
> be sent.  I think returning a 0 will break PQsetnonblocking if you call
> it when there is data in both sslRetryBuf and outputBuffer.

Hmm, I thought I thought about that. There was a check in the original
patch: "if (conn->sslRetryBytes || (conn->outCount - remaining) > 0)"
So if the SSL retry buffer was emptied it would return 1 if there was
something left in the regular output buffer. Or did I miss something
there?

> We might even want to try sending the data in outputBuffer after we've
> sent all the data sitting in sslRetryBuf.
> 

IMHO that'd add unnecessary complexity to the pqSendSome. As this only
happens in non-blocking mode the caller should be well prepared to
handle the retry.

> If you close the connection with an outstanding sslRetryBuf you need to
> free it.

Fixed.

New version of the patch attached.

regards,
Martin
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9e4807e..8dc0226 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2792,6 +2792,10 @@ freePGconn(PGconn *conn)
 	if (conn->gsslib)
 		free(conn->gsslib);
 #endif
+#if defined(USE_SSL)
+	if (conn->sslOutBufPtr)
+		free(conn->sslOutBufPtr);
+#endif
 	/* Note that conn->Pfdebug is not ours to close or free */
 	if (conn->last_query)
 		free(conn->last_query);
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 17dde4a..d0c7812 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -781,6 +781,8 @@ pqSendSome(PGconn *conn, int len)
 	char	   *ptr = conn->outBuffer;
 	int			remaining = conn->outCount;
 	int			result = 0;
+	int			sent = 0;
+	bool		shiftOutBuffer = true;
 
 	if (conn->sock < 0)
 	{
@@ -789,23 +791,34 @@ pqSendSome(PGconn *conn, int len)
 		return -1;
 	}
 
+#ifdef USE_SSL
+	if (conn->sslRetryPos)
+	{
+		/* 
+		 * We have some leftovers from previous SSL write attempts, these need
+		 * to be sent before the regular output buffer.
+		 */
+		len = remaining = conn->sslRetrySize;
+		ptr = conn->sslRetryPos;
+		shiftOutBuffer = false;
+	}
+#endif
+
 	/* while there's still data to send */
 	while (len > 0)
 	{
-		int			sent;
 		char		sebuf[256];
+		int			write_size = len;
 
-#ifndef WIN32
-		sent = pqsecure_write(conn, ptr, len);
-#else
-
+#ifdef WIN32
 		/*
 		 * Windows can fail on large sends, per KB article Q201213. The
 		 * failure-point appears to be different in different versions of
 		 * Windows, but 64k should always be safe.
 		 */
-		sent = pqsecure_write(conn, ptr, Min(len, 65536));
+		write_size = Min(len, 65536);
 #endif
+		sent = pqsecure_write(conn, ptr, write_size);
 
 		if (sent < 0)
 		{
@@ -857,6 +870,38 @@ pqSendSome(PGconn *conn, int len)
 					return -1;
 			}
 		}
+#ifdef USE_SSL
+		else if (sent == 0 && conn->ssl && !conn->sslRetryPos)
+		{
+			/*
+			 * pqsecure_write indicates that a SSL write needs to be retried.
+			 * With non-blocking connections we need to ensure that the buffer
+			 * passed to the SSL_write() retry is the the exact same buffer as
+			 * in previous write -- see SSL_write(3SSL) for more on this. For
+			 * this we need to save the the original buffer and allocate a new
+			 * buffer for outgoing data.
+			 */
+			conn->sslOutBufPtr = conn->outBuffer;
+			conn->sslRetryPos = ptr;
+			conn->sslRetrySize = write_size;
+
+			/*
+			 * Allocate a new output buffer and prepare to move the remainder
+			 * of the original outBuffer there.
+			 */
+			remaining -= write_size;
+			ptr += write_size;
+
+			conn->outBufSize = Max(16 * 1024, remaining);
+			conn->outBuffer = malloc(conn->outBufSize);
+			if (conn->outBuffer == NULL)
+			{
+				printfPQExpBuffer(&conn->errorMessage,
+								  "cannot allocate memory for output buffer\n");
+				return -1;
+			}
+		}
+#endif
 		else
 		{
 			ptr += sent;
@@ -903,10 +948,36 @@ pqSendSome(PGconn *conn, int len)
 		}
 	}
 
-	/* shift the remaining contents of the buffer */
-	if (remaining > 0)
-		memmove(conn->outBuffer, ptr, remaining);
-	conn->outCount = remaining;
+#ifdef USE_SSL
+	if (conn->sslRetryPos && sent > 0)
+	{
+		/*
+		 * A SSL write was successfully retried, free the retry buffer and
+		 * switch to the regular outBuffer. Note that we expect that the entire
+		 * buffer was written since SSL partial writes ought to be disabled.
+		 */
+		free(conn->sslOutBufPtr);
+		conn->sslRetryPos = NULL;
+		conn->sslOutBufPtr = NULL;
+		conn->sslRetrySize = 0;
+
+		/* 
+		 * Indicate that a retry is needed if we still have some data left in
+		 * output buffers. We are in non-blocking mode, so this shouldn't
+		 * be a surprise.
+		 */
+		if (conn->outCount > 0)
+			result = 1;
+	}
+#endif
+
+	/* Shift the remaining contents of the buffer. */
+	if (shiftOutBuffer)
+	{
+		if (remaining > 0)
+			memmove(conn->outBuffer, ptr, remaining);
+		conn->outCount = remaining;
+	}
 
 	return result;
 }
@@ -924,7 +995,11 @@ pqFlush(PGconn *conn)
 	if (conn->Pfdebug)
 		fflush(conn->Pfdebug);
 
+#ifdef USE_SSL
+	if (conn->outCount > 0 || conn->sslRetrySize > 0)
+#else
 	if (conn->outCount > 0)
+#endif
 		return pqSendSome(conn, conn->outCount);
 
 	return 0;
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index d56ef5d..da1f2da 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -407,6 +407,10 @@ struct pg_conn
 	X509	   *peer;			/* X509 cert of server */
 	char		peer_dn[256 + 1];		/* peer distinguished name */
 	char		peer_cn[SM_USER + 1];	/* peer common name */
+
+	char	   *sslRetryPos;	/* last buffer address passed SSL write */
+	char	   *sslOutBufPtr;	/* pointer to the original outBuffer */
+	int			sslRetrySize;	/* last buffer length passed SSL write */
 #ifdef USE_SSL_ENGINE
 	ENGINE	   *engine;			/* SSL engine, if any */
 #else
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to