stream-service: Avoid race condition when accepting sockets
Even if `poll()` indicates that the socket is ready it might block if
it's in blocking mode. This change avoids blocking in such cases (accept
will fail with EAGAIN/EWOULDBLOCK and `watch()` will return TRUE).
As the non-blocking mode is inherited on Windows (on Linux, the man page
documents the non-inheritance as a Linux specialty), we set the mode for
the accepted socket explicitly to blocking to match the expectations of
`stream_t`.
Fixes: daf1880b39 ("stream: add a stream service class abstracting services using BSD sockets")
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
typedef struct private_stream_service_t private_stream_service_t;
|
typedef struct private_stream_service_t private_stream_service_t;
|
||||||
|
|
||||||
@@ -149,6 +150,22 @@ static void destroy_async_data(async_data_t *data)
|
|||||||
free(data);
|
free(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/disable non-blocking mode on a socket.
|
||||||
|
*/
|
||||||
|
static bool set_blocking(int fd, bool block)
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
u_long on = block ? 0 : 1;
|
||||||
|
return ioctlsocket(fd, FIONBIO, &on) == 0;
|
||||||
|
#else
|
||||||
|
int flags = fcntl(fd, F_GETFL);
|
||||||
|
return flags != -1 &&
|
||||||
|
fcntl(fd, F_SETFL, block ? (flags & ~O_NONBLOCK)
|
||||||
|
: (flags | O_NONBLOCK)) != -1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reduce running counter
|
* Reduce running counter
|
||||||
*/
|
*/
|
||||||
@@ -180,6 +197,14 @@ static job_requeue_t accept_async(async_data_t *data)
|
|||||||
this->running++;
|
this->running++;
|
||||||
this->mutex->unlock(this->mutex);
|
this->mutex->unlock(this->mutex);
|
||||||
|
|
||||||
|
/* accepted socket may inherit non-blocking mode from listening socket
|
||||||
|
* depending on the implementation, so we normalize this to blocking mode */
|
||||||
|
if (!set_blocking(data->fd, TRUE))
|
||||||
|
{
|
||||||
|
reduce_running(data);
|
||||||
|
return JOB_REQUEUE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
stream = stream_create_from_fd(data->fd);
|
stream = stream_create_from_fd(data->fd);
|
||||||
if (stream)
|
if (stream)
|
||||||
{
|
{
|
||||||
@@ -296,6 +321,12 @@ stream_service_t *stream_service_create_from_fd(int fd)
|
|||||||
{
|
{
|
||||||
private_stream_service_t *this;
|
private_stream_service_t *this;
|
||||||
|
|
||||||
|
if (!set_blocking(fd, FALSE))
|
||||||
|
{
|
||||||
|
close(fd);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
INIT(this,
|
INIT(this,
|
||||||
.public = {
|
.public = {
|
||||||
.on_accept = _on_accept,
|
.on_accept = _on_accept,
|
||||||
|
|||||||
@@ -76,10 +76,12 @@ struct stream_service_t {
|
|||||||
/**
|
/**
|
||||||
* Create a service from a file descriptor.
|
* Create a service from a file descriptor.
|
||||||
*
|
*
|
||||||
* The file descriptor MUST be a socket.
|
* The file descriptor MUST be a socket. It is set to non-blocking mode to
|
||||||
|
* safely accept() connections from the watcher thread. For consistency, the
|
||||||
|
* accepted connection sockets are set to blocking mode on all platforms.
|
||||||
*
|
*
|
||||||
* @param fd file descriptor to wrap into a stream_service_t
|
* @param fd file descriptor to wrap (adopted, closed on failure)
|
||||||
* @return stream_service instance
|
* @return service, NULL if fd can't be switched to non-blocking mode
|
||||||
*/
|
*/
|
||||||
stream_service_t *stream_service_create_from_fd(int fd);
|
stream_service_t *stream_service_create_from_fd(int fd);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user