Add a self_pipe set of convenience functions
This commit is contained in:
@@ -4,3 +4,6 @@
|
|||||||
^build/
|
^build/
|
||||||
^pkg/
|
^pkg/
|
||||||
\.orig$
|
\.orig$
|
||||||
|
.*\.swp$
|
||||||
|
cscope.out$
|
||||||
|
valgrind.out$
|
||||||
|
140
src/self_pipe.c
Normal file
140
src/self_pipe.c
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* self_pipe.c
|
||||||
|
*
|
||||||
|
* author: Alex Young <alex@bytemark.co.uk>
|
||||||
|
*
|
||||||
|
* Wrapper for the self-pipe trick for select()-based thread
|
||||||
|
* synchronisation. Get yourself a self_pipe with self_pipe_create(),
|
||||||
|
* select() on the read end of the pipe with the help of
|
||||||
|
* self_pipe_fd_set( sig, fds ) and self_pipe_fd_isset( sig, fds ).
|
||||||
|
* When you've received a signal, clear it with
|
||||||
|
* self_pipe_signal_clear(sig) so that the buffer doesn't get filled.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
#include "self_pipe.h"
|
||||||
|
|
||||||
|
#define ERR_MSG_PIPE "Couldn't open a pipe for signaling."
|
||||||
|
#define ERR_MSG_FCNTL "Couldn't set a signalling pipe non-blocking."
|
||||||
|
#define ERR_MSG_WRITE "Couldn't write to a signaling pipe."
|
||||||
|
#define ERR_MSG_READ "Couldn't read from a signaling pipe."
|
||||||
|
|
||||||
|
void self_pipe_server_error( int err, char *msg )
|
||||||
|
{
|
||||||
|
char errbuf[1024];
|
||||||
|
|
||||||
|
strerror_r( err, errbuf, 1024 );
|
||||||
|
|
||||||
|
debug(msg);
|
||||||
|
SERVER_ERROR( "%s\t%s", msg, errbuf );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate a struct self_pipe, opening the pipe and allocating a
|
||||||
|
* pthread mutex.
|
||||||
|
*
|
||||||
|
* Returns NULL if the pipe couldn't be opened or if we couldn't set it
|
||||||
|
* non-blocking.
|
||||||
|
*
|
||||||
|
* Remember to call self_pipe_destroy when you're done with the return
|
||||||
|
* value.
|
||||||
|
*/
|
||||||
|
struct self_pipe * self_pipe_create(void)
|
||||||
|
{
|
||||||
|
struct self_pipe *sig = xmalloc( sizeof( struct self_pipe ) );
|
||||||
|
int fds[2];
|
||||||
|
int fcntl_err;
|
||||||
|
|
||||||
|
if ( NULL == sig ) { return NULL; }
|
||||||
|
|
||||||
|
if ( pipe( fds ) ) {
|
||||||
|
free( sig );
|
||||||
|
self_pipe_server_error( errno, ERR_MSG_PIPE );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( fcntl( fds[0], F_SETFD, O_NONBLOCK ) || fcntl( fds[1], F_SETFD, O_NONBLOCK ) ) {
|
||||||
|
fcntl_err = errno;
|
||||||
|
while( close( fds[0] ) == -1 && errno == EINTR );
|
||||||
|
while( close( fds[1] ) == -1 && errno == EINTR );
|
||||||
|
free( sig );
|
||||||
|
self_pipe_server_error( fcntl_err, ERR_MSG_FCNTL );
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sig->read_fd = fds[0];
|
||||||
|
sig->write_fd = fds[1];
|
||||||
|
|
||||||
|
return sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a signal to anyone select()ing on this signal.
|
||||||
|
*
|
||||||
|
* Returns 1 on success. Can fail if weirdness happened to the write fd
|
||||||
|
* of the pipe in the self_pipe struct.
|
||||||
|
*/
|
||||||
|
int self_pipe_signal( struct self_pipe * sig )
|
||||||
|
{
|
||||||
|
if ( write( sig->write_fd, "1", 1 ) != 1 ) {
|
||||||
|
self_pipe_server_error( errno, ERR_MSG_WRITE );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear a received signal from the pipe. Every signal sent must be
|
||||||
|
* cleared by one (and only one) recipient when they return from select().
|
||||||
|
*/
|
||||||
|
int self_pipe_signal_clear( struct self_pipe *sig )
|
||||||
|
{
|
||||||
|
char buf[1];
|
||||||
|
|
||||||
|
read( sig->read_fd, buf, 1 );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the pipe and free the self_pipe. Do not try to use the
|
||||||
|
* self_pipe struct after calling this, the innards are mush.
|
||||||
|
*/
|
||||||
|
int self_pipe_destroy( struct self_pipe * sig )
|
||||||
|
{
|
||||||
|
while( close( sig->read_fd ) == -1 && errno == EINTR );
|
||||||
|
while( close( sig->write_fd ) == -1 && errno == EINTR );
|
||||||
|
|
||||||
|
/* Just in case anyone *does* try to use this after free,
|
||||||
|
* we should set the memory locations to an error value
|
||||||
|
*/
|
||||||
|
sig->read_fd = -1;
|
||||||
|
sig->write_fd = -1;
|
||||||
|
|
||||||
|
free( sig );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int self_pipe_fd_set( struct self_pipe * sig, fd_set * fds)
|
||||||
|
{
|
||||||
|
FD_SET( sig->read_fd, fds );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int self_pipe_fd_isset( struct self_pipe * sig, fd_set * fds)
|
||||||
|
{
|
||||||
|
return FD_ISSET( sig->read_fd, fds );
|
||||||
|
}
|
16
src/self_pipe.h
Normal file
16
src/self_pipe.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#define SELF_PIPE_H
|
||||||
|
|
||||||
|
#include <sys/select.h>
|
||||||
|
|
||||||
|
struct self_pipe {
|
||||||
|
int read_fd;
|
||||||
|
int write_fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct self_pipe * self_pipe_create(void);
|
||||||
|
int self_pipe_signal( struct self_pipe * sig );
|
||||||
|
int self_pipe_signal_clear( struct self_pipe *sig );
|
||||||
|
int self_pipe_destroy( struct self_pipe * sig );
|
||||||
|
int self_pipe_fd_set( struct self_pipe * sig, fd_set * fds );
|
||||||
|
int self_pipe_fd_isset( struct self_pipe *sig, fd_set *fds );
|
197
tests/check_self_pipe.c
Normal file
197
tests/check_self_pipe.c
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <check.h>
|
||||||
|
#include <mcheck.h>
|
||||||
|
|
||||||
|
#include "self_pipe.h"
|
||||||
|
|
||||||
|
START_TEST( test_opens_pipe )
|
||||||
|
{
|
||||||
|
struct self_pipe* sig;
|
||||||
|
char buf[] = " ";
|
||||||
|
|
||||||
|
sig = self_pipe_create();
|
||||||
|
|
||||||
|
write( sig->write_fd, "1", 1 );
|
||||||
|
read( sig->read_fd, buf, 1 );
|
||||||
|
|
||||||
|
fail_unless( buf[0] == '1', "Pipe does not seem to be open;" );
|
||||||
|
self_pipe_destroy( sig );
|
||||||
|
}
|
||||||
|
END_TEST
|
||||||
|
|
||||||
|
|
||||||
|
void * signal_thread( void * thing )
|
||||||
|
{
|
||||||
|
struct self_pipe *sig = (struct self_pipe *)thing;
|
||||||
|
usleep( 100000 );
|
||||||
|
self_pipe_signal( sig );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_t start_signal_thread( struct self_pipe *sig )
|
||||||
|
{
|
||||||
|
pthread_attr_t attr;
|
||||||
|
pthread_t thread_id;
|
||||||
|
|
||||||
|
pthread_attr_init( &attr );
|
||||||
|
pthread_create( &thread_id, &attr, signal_thread, sig );
|
||||||
|
pthread_attr_destroy( &attr );
|
||||||
|
|
||||||
|
return thread_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
START_TEST( test_signals )
|
||||||
|
{
|
||||||
|
struct self_pipe* sig;
|
||||||
|
fd_set fds;
|
||||||
|
pthread_t signal_thread_id;
|
||||||
|
|
||||||
|
sig = self_pipe_create();
|
||||||
|
|
||||||
|
FD_ZERO( &fds );
|
||||||
|
self_pipe_fd_set( sig, &fds );
|
||||||
|
|
||||||
|
signal_thread_id = start_signal_thread( sig );
|
||||||
|
if ( select( FD_SETSIZE, &fds, NULL, NULL, NULL ) == -1 ) {
|
||||||
|
fail( strerror(errno) );
|
||||||
|
}
|
||||||
|
self_pipe_signal_clear( sig );
|
||||||
|
|
||||||
|
fail_unless( self_pipe_fd_isset( sig, &fds ), "Signalled pipe was not FD_ISSET." );
|
||||||
|
pthread_join( signal_thread_id, NULL );
|
||||||
|
|
||||||
|
self_pipe_destroy( sig );
|
||||||
|
}
|
||||||
|
END_TEST
|
||||||
|
|
||||||
|
|
||||||
|
START_TEST( test_destroy_closes_read_pipe )
|
||||||
|
{
|
||||||
|
struct self_pipe* sig;
|
||||||
|
ssize_t read_len;
|
||||||
|
int orig_read_fd;
|
||||||
|
|
||||||
|
sig = self_pipe_create();
|
||||||
|
orig_read_fd = sig->read_fd;
|
||||||
|
self_pipe_destroy( sig );
|
||||||
|
|
||||||
|
while( (read_len = read( orig_read_fd, "", 0 )) == -1 && errno == EINTR );
|
||||||
|
|
||||||
|
switch( read_len ) {
|
||||||
|
case 0:
|
||||||
|
fail("The read fd wasn't closed." );
|
||||||
|
break;
|
||||||
|
case -1:
|
||||||
|
switch(errno) {
|
||||||
|
case EBADF:
|
||||||
|
/* This is what we want */
|
||||||
|
break;
|
||||||
|
case EAGAIN:
|
||||||
|
fail( "The read fd wasn't closed." );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fail( strerror( errno ) );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fail( "The read fd wasn't closed, and had data in it." );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END_TEST
|
||||||
|
|
||||||
|
|
||||||
|
START_TEST( test_destroy_closes_write_pipe )
|
||||||
|
{
|
||||||
|
struct self_pipe * sig;
|
||||||
|
ssize_t write_len;
|
||||||
|
int orig_write_fd;
|
||||||
|
|
||||||
|
sig = self_pipe_create();
|
||||||
|
orig_write_fd = sig->write_fd;
|
||||||
|
self_pipe_destroy( sig );
|
||||||
|
|
||||||
|
while ( ( write_len = write( orig_write_fd, "", 0 ) ) == -1 && errno == EINTR );
|
||||||
|
|
||||||
|
switch( write_len ) {
|
||||||
|
case 0:
|
||||||
|
fail( "The write fd wasn't closed." );
|
||||||
|
break;
|
||||||
|
case -1:
|
||||||
|
switch( errno ) {
|
||||||
|
case EPIPE:
|
||||||
|
case EBADF:
|
||||||
|
/* This is what we want */
|
||||||
|
break;
|
||||||
|
case EAGAIN:
|
||||||
|
fail("The write fd wasn't closed." );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fail( strerror( errno ) );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* To get here, the write(_,_,0) would have to
|
||||||
|
* write some bytes.
|
||||||
|
*/
|
||||||
|
fail( "The write fd wasn't closed, and something REALLY WEIRD is going on." );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
END_TEST
|
||||||
|
|
||||||
|
|
||||||
|
START_TEST( test_signal_after_destroy_fails )
|
||||||
|
{
|
||||||
|
struct self_pipe* sig;
|
||||||
|
|
||||||
|
sig = self_pipe_create();
|
||||||
|
self_pipe_destroy( sig );
|
||||||
|
|
||||||
|
fail_unless( self_pipe_signal( sig ) == 0, "Signaling a closed self_pipe didn't return 0." );
|
||||||
|
}
|
||||||
|
END_TEST
|
||||||
|
|
||||||
|
|
||||||
|
Suite *self_pipe_suite()
|
||||||
|
{
|
||||||
|
Suite *s = suite_create("self_pipe");
|
||||||
|
|
||||||
|
TCase *tc_create = tcase_create("create");
|
||||||
|
TCase *tc_signal = tcase_create("signal");
|
||||||
|
TCase *tc_destroy = tcase_create("destroy");
|
||||||
|
|
||||||
|
tcase_add_test(tc_create, test_opens_pipe);
|
||||||
|
tcase_add_test(tc_signal, test_signals );
|
||||||
|
tcase_add_test(tc_destroy, test_destroy_closes_read_pipe );
|
||||||
|
tcase_add_test(tc_destroy, test_destroy_closes_write_pipe );
|
||||||
|
/* We don't test that destroy free()'s the self_pipe pointer because
|
||||||
|
* that'll be caught by valgrind.
|
||||||
|
*/
|
||||||
|
|
||||||
|
suite_add_tcase(s, tc_create);
|
||||||
|
suite_add_tcase(s, tc_signal);
|
||||||
|
suite_add_tcase(s, tc_destroy);
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
int number_failed;
|
||||||
|
|
||||||
|
Suite *s = self_pipe_suite();
|
||||||
|
SRunner *sr = srunner_create(s);
|
||||||
|
srunner_run_all(sr, CK_NORMAL);
|
||||||
|
number_failed = srunner_ntests_failed(sr);
|
||||||
|
srunner_free(sr);
|
||||||
|
return (number_failed == 0) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user