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

Do not call workSocket() in TNonblockigServer without ensuring that there is data on the socket #1497

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
3 changes: 2 additions & 1 deletion build/cmake/ConfigureChecks.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ check_include_file(netinet/in.h HAVE_NETINET_IN_H)
check_include_file(stdint.h HAVE_STDINT_H)
check_include_file(unistd.h HAVE_UNISTD_H)
check_include_file(pthread.h HAVE_PTHREAD_H)
check_include_file(sys/time.h HAVE_SYS_TIME_H)
check_include_file(sys/ioctl.h HAVE_SYS_IOCTL_H)
check_include_file(sys/param.h HAVE_SYS_PARAM_H)
check_include_file(sys/resource.h HAVE_SYS_RESOURCE_H)
check_include_file(sys/socket.h HAVE_SYS_SOCKET_H)
check_include_file(sys/stat.h HAVE_SYS_STAT_H)
check_include_file(sys/time.h HAVE_SYS_TIME_H)
check_include_file(sys/un.h HAVE_SYS_UN_H)
check_include_file(sys/poll.h HAVE_SYS_POLL_H)
check_include_file(sys/select.h HAVE_SYS_SELECT_H)
Expand Down
7 changes: 5 additions & 2 deletions build/cmake/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@
/* Define to 1 if you have the <pthread.h> header file. */
#cmakedefine HAVE_PTHREAD_H 1

/* Define to 1 if you have the <sys/time.h> header file. */
#cmakedefine HAVE_SYS_TIME_H 1
/* Define to 1 if you have the <sys/ioctl.h> header file. */
#cmakedefine HAVE_SYS_IOCTL_H 1

/* Define to 1 if you have the <sys/param.h> header file. */
#cmakedefine HAVE_SYS_PARAM_H 1
Expand All @@ -124,6 +124,9 @@
/* Define to 1 if you have the <sys/select.h> header file. */
#cmakedefine HAVE_SYS_SELECT_H 1

/* Define to 1 if you have the <sys/time.h> header file. */
#cmakedefine HAVE_SYS_TIME_H 1

/* Define to 1 if you have the <sched.h> header file. */
#cmakedefine HAVE_SCHED_H 1

Expand Down
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ AC_CHECK_HEADERS([netinet/in.h])
AC_CHECK_HEADERS([pthread.h])
AC_CHECK_HEADERS([stddef.h])
AC_CHECK_HEADERS([stdlib.h])
AC_CHECK_HEADERS([sys/ioctl.h])
AC_CHECK_HEADERS([sys/socket.h])
AC_CHECK_HEADERS([sys/time.h])
AC_CHECK_HEADERS([sys/un.h])
Expand Down
23 changes: 13 additions & 10 deletions lib/cpp/src/thrift/server/TNonblockingServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,18 @@ void TNonblockingServer::TConnection::workSocket() {
}
// size known; now get the rest of the frame
transition();

// If the socket has more data than the frame header, continue to work on it. This is not strictly necessary for
// regular sockets, because if there is more data, libevent will fire the event handler registered for read
// readiness, which will in turn call workSocket(). However, some socket types (such as TSSLSocket) may have the
// data sitting in their internal buffers and from libevent's perspective, there is no further data available. In
// that case, not having this workSocket() call here would result in a hang as we will never get to work the socket,
// despite having more data.
if (tSocket_->hasPendingDataToRead())
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be an "if" or a "while"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should be an if. At this point, we know the frame size. The next step is to read the frame data itself. And for that, there is already logic to work the socket until all the data is read. For SSL specifically, there is no concern that the SSL buffers can keep pending data forever, because when the entire data frame arrives, there won't be any incomplete SSL bytes. In any case, the code should work generically for all socket types.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am concerned this can cause an infinite loop if data is always available, since it calls itself.

Copy link
Contributor Author

@bgedik bgedik Mar 7, 2018

Choose a reason for hiding this comment

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

It can't. Note that the line above is transition(). So the next time we call workSocket() it will take a different case statement, as we are in a different state.

Btw, I assume you notice that the original workSocket call I removed, which was causing the superfluous exception, was reached via workSocket -> transition -> workSocket.

{
workSocket();
}

return;

case SOCKET_RECV:
Expand Down Expand Up @@ -677,9 +689,6 @@ void TNonblockingServer::TConnection::transition() {
appState_ = APP_SEND_RESULT;
setWrite();

// Try to work the socket immediately
// workSocket();

return;
}

Expand Down Expand Up @@ -718,9 +727,6 @@ void TNonblockingServer::TConnection::transition() {
// Register read event
setRead();

// Try to work the socket right away
// workSocket();

return;

case APP_READ_FRAME_SIZE:
Expand Down Expand Up @@ -753,9 +759,6 @@ void TNonblockingServer::TConnection::transition() {
socketState_ = SOCKET_RECV;
appState_ = APP_READ_REQUEST;

// Work the socket right away
workSocket();

return;

case APP_CLOSE_CONNECTION:
Expand Down Expand Up @@ -1063,7 +1066,7 @@ void TNonblockingServer::expireClose(stdcxx::shared_ptr<Runnable> task) {
connection->forceClose();
}

void TNonblockingServer::stop() {
void TNonblockingServer::stop() {
// Breaks the event loop in all threads so that they end ASAP.
for (uint32_t i = 0; i < ioThreads_.size(); ++i) {
ioThreads_[i]->stop();
Expand Down
4 changes: 4 additions & 0 deletions lib/cpp/src/thrift/transport/PlatformSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
# define THRIFT_LSEEK _lseek
# define THRIFT_WRITE _write
# define THRIFT_READ _read
# define THRIFT_IOCTL_SOCKET ioctlsocket
# define THRIFT_IOCTL_SOCKET_NUM_BYTES_TYPE u_long
# define THRIFT_FSTAT _fstat
# define THRIFT_STAT _stat
# ifdef _WIN32_WCE
Expand Down Expand Up @@ -111,6 +113,8 @@
# define THRIFT_LSEEK lseek
# define THRIFT_WRITE write
# define THRIFT_READ read
# define THRIFT_IOCTL_SOCKET ioctl
# define THRIFT_IOCTL_SOCKET_NUM_BYTES_TYPE int
# define THRIFT_STAT stat
# define THRIFT_FSTAT fstat
# define THRIFT_GAI_STRERROR gai_strerror
Expand Down
11 changes: 11 additions & 0 deletions lib/cpp/src/thrift/transport/TSSLSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,17 @@ TSSLSocket::~TSSLSocket() {
close();
}

bool TSSLSocket::hasPendingDataToRead() {
if (!isOpen()) {
return false;
}
initializeHandshake();
if (!checkHandshake())
throw TSSLException("TSSLSocket::hasPendingDataToRead: Handshake is not completed");
// data may be available in SSL buffers (note: SSL_pending does not have a failure mode)
return SSL_pending(ssl_) > 0 || TSocket::hasPendingDataToRead();
}

void TSSLSocket::init() {
handshakeCompleted_ = false;
readRetryCount_ = 0;
Expand Down
1 change: 1 addition & 0 deletions lib/cpp/src/thrift/transport/TSSLSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class TSSLSocket : public TSocket {
bool peek();
void open();
void close();
bool hasPendingDataToRead();
uint32_t read(uint8_t* buf, uint32_t len);
void write(const uint8_t* buf, uint32_t len);
uint32_t write_partial(const uint8_t* buf, uint32_t len);
Expand Down
23 changes: 23 additions & 0 deletions lib/cpp/src/thrift/transport/TSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

#include <cstring>
#include <sstream>
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
Expand Down Expand Up @@ -167,6 +170,26 @@ TSocket::~TSocket() {
close();
}

bool TSocket::hasPendingDataToRead() {
if (!isOpen()) {
return false;
}

int32_t retries = 0;
THRIFT_IOCTL_SOCKET_NUM_BYTES_TYPE numBytesAvailable;
try_again:
int r = THRIFT_IOCTL_SOCKET(socket_, FIONREAD, &numBytesAvailable);
if (r == -1) {
int errno_copy = THRIFT_GET_SOCKET_ERROR;
if (errno_copy == THRIFT_EINTR && (retries++ < maxRecvRetries_)) {
goto try_again;
}
GlobalOutput.perror("TSocket::hasPendingDataToRead() THRIFT_IOCTL_SOCKET() " + getSocketInfo(), errno_copy);
throw TTransportException(TTransportException::UNKNOWN, "Unknown", errno_copy);
}
return numBytesAvailable > 0;
}

bool TSocket::isOpen() {
return (socket_ != THRIFT_INVALID_SOCKET);
}
Expand Down
15 changes: 14 additions & 1 deletion lib/cpp/src/thrift/transport/TSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ class TSocket : public TVirtualTransport<TSocket> {
virtual bool isOpen();

/**
* Calls select on the socket to see if there is more data available.
* Checks whether there is more data available in the socket to read.
*
* This call blocks until at least one byte is available or the socket is closed.
*/
virtual bool peek();

Expand All @@ -100,6 +102,17 @@ class TSocket : public TVirtualTransport<TSocket> {
*/
virtual void close();

/**
* Determines whether there is pending data to read or not.
*
* This call does not block.
* \throws TTransportException of types:
* NOT_OPEN means the socket has been closed
* UNKNOWN means something unexpected happened
* \returns true if there is pending data to read, false otherwise
*/
virtual bool hasPendingDataToRead();

/**
* Reads from the underlying socket.
* \returns the number of bytes read or 0 indicates EOF
Expand Down