diff --git a/bin/tests/sock_test.c b/bin/tests/sock_test.c new file mode 100644 index 0000000000..6267dd7e79 --- /dev/null +++ b/bin/tests/sock_test.c @@ -0,0 +1,153 @@ + +#include "attribute.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +isc_memctx_t mctx = NULL; + +static isc_boolean_t +my_callback(isc_task_t task, isc_event_t event) +{ + char *name = event->arg; + + printf("task %s (%p)\n", name, task); + fflush(stdout); + isc_event_free(&event); + + return (ISC_FALSE); +} + +static isc_boolean_t +my_shutdown(isc_task_t task, isc_event_t event) +{ + char *name = event->arg; + + printf("shutdown %s (%p)\n", name, task); + fflush(stdout); + isc_event_free(&event); + + return (ISC_TRUE); +} + +int +main(int argc, char *argv[]) +{ + isc_taskmgr_t manager = NULL; + isc_task_t t1 = NULL, t2 = NULL; + isc_task_t t3 = NULL, t4 = NULL; + isc_event_t event; + unsigned int workers; + isc_socketmgr_t socketmgr; + isc_socket_t so1, so2; + + if (argc > 1) + workers = atoi(argv[1]); + else + workers = 2; + printf("%d workers\n", workers); + + INSIST(isc_memctx_create(0, 0, &mctx) == ISC_R_SUCCESS); + + INSIST(isc_taskmgr_create(mctx, workers, 0, &manager) == + ISC_R_SUCCESS); + + INSIST(isc_task_create(manager, my_shutdown, "1", 0, &t1) == + ISC_R_SUCCESS); + INSIST(isc_task_create(manager, my_shutdown, "2", 0, &t2) == + ISC_R_SUCCESS); + INSIST(isc_task_create(manager, my_shutdown, "3", 0, &t3) == + ISC_R_SUCCESS); + INSIST(isc_task_create(manager, my_shutdown, "4", 0, &t4) == + ISC_R_SUCCESS); + + printf("task 1 = %p\n", t1); + printf("task 2 = %p\n", t2); + printf("task 3 = %p\n", t3); + printf("task 4 = %p\n", t4); + + socketmgr = NULL; + INSIST(isc_socketmgr_create(mctx, &socketmgr) == ISC_R_SUCCESS); + so1 = NULL; + INSIST(isc_socket_create(socketmgr, isc_socket_udp, + &so1) == ISC_R_SUCCESS); + so2 = NULL; + INSIST(isc_socket_create(socketmgr, isc_socket_udp, + &so2) == ISC_R_SUCCESS); + + sleep(2); + + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1", + sizeof *event); + isc_task_send(t1, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1", + sizeof *event); + isc_task_send(t1, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1", + sizeof *event); + isc_task_send(t1, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1", + sizeof *event); + isc_task_send(t1, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1", + sizeof *event); + isc_task_send(t1, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1", + sizeof *event); + isc_task_send(t1, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1", + sizeof *event); + isc_task_send(t1, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1", + sizeof *event); + isc_task_send(t1, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1", + sizeof *event); + isc_task_send(t1, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "2", + sizeof *event); + isc_task_send(t2, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "3", + sizeof *event); + isc_task_send(t3, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "4", + sizeof *event); + isc_task_send(t4, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "2", + sizeof *event); + isc_task_send(t2, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "3", + sizeof *event); + isc_task_send(t3, &event); + event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "4", + sizeof *event); + isc_task_send(t4, &event); + isc_task_purge(t3, NULL, 0); + + isc_task_detach(&t1); + isc_task_detach(&t2); + isc_task_detach(&t3); + isc_task_detach(&t4); + + sleep(10); + printf("destroy\n"); + isc_socket_detach(&so1); + isc_socket_detach(&so2); + + isc_socketmgr_destroy(&socketmgr); + isc_taskmgr_destroy(&manager); + printf("destroyed\n"); + + isc_mem_stats(mctx, stdout); + isc_memctx_destroy(&mctx); + + return (0); +} diff --git a/lib/isc/include/isc/socket.h b/lib/isc/include/isc/socket.h new file mode 100644 index 0000000000..dbf3264678 --- /dev/null +++ b/lib/isc/include/isc/socket.h @@ -0,0 +1,522 @@ + +#ifndef ISC_SOCKET_H +#define ISC_SOCKET_H 1 + +/***** + ***** Module Info + *****/ + +/* + * Sockets + * + * Provides TCP and UDP sockets for network I/O. The sockets are event + * sources in the task system. + * + * When I/O completes, a completion event for the socket is posted to the + * event queue of the task which requested the I/O. + * + * MP: + * The module ensures appropriate synchronization of data structures it + * creates and manipulates. + * + * Clients of this module must not be holding a socket's task's lock when + * making a call that affects that socket. Failure to follow this rule + * can result in deadlock. + * + * The caller must ensure that isc_socketmgr_destroy() is called only + * once for a given manager. + * + * Reliability: + * No anticipated impact. + * + * Resources: + * + * + * Security: + * No anticipated impact. + * + * Standards: + * None. + */ + +/*** + *** Imports + ***/ + +#include +#include + +#include +#include +#include + +#include + +/*** + *** Types + ***/ + +typedef struct isc_socket *isc_socket_t; +typedef struct isc_socketmgr *isc_socketmgr_t; + +/* + * XXX Export this as isc/sockaddr.h + */ +typedef struct isc_sockaddr { + /* + * XXX Must be big enough for all sockaddr types we care about. + */ + union foo { + struct sockaddr_in sin; + }; +} *isc_sockaddr_t; + +typedef struct isc_sockevent { + struct isc_event common; /* Sender is the socket. */ + isc_result_t result; /* OK, EOF, whatever else */ + unsigned int n; /* bytes read or written */ + struct isc_sockaddr address; /* source address */ + int length; /* length of address */ +} *isc_sockevent_t; + +#define ISC_SOCKEVENT_ANYEVENT (0) +#define ISC_SOCKEVENT_RECVDONE (EVENT_CLASS_SOCKET + 1) +#define ISC_SOCKEVENT_SENDDONE (EVENT_CLASS_SOCKET + 2) +#define ISC_SOCKEVENT_NEWCONN (EVENT_CLASS_SOCKET + 3) +#define ISC_SOCKEVENT_CONNECTED (EVENT_CLASS_SOCKET + 4) + +typedef enum { + isc_socket_udp, + isc_socket_tcp +} isc_sockettype_t; + +typedef enum { + isc_sockshut_reading, + isc_sockshut_writing, + isc_sockshut_all +} isc_socketshutdown_t; + +/*** + *** Socket and Socket Manager Functions + *** + *** Note: all Ensures conditions apply only if the result is success for + *** those functions which return an isc_result. + ***/ + +isc_result_t +isc_socket_create(isc_socketmgr_t manager, + isc_sockettype_t type, + isc_socket_t *socketp); +/* + * Create a new 'type' socket managed by 'manager'. + * + * Requires: + * + * 'manager' is a valid manager + * + * 'socketp' is a valid pointer, and *socketp == NULL + * + * Ensures: + * + * '*socketp' is attached to the newly created socket + * + * Returns: + * + * Success + * No memory + * Unexpected error + */ + + +void +isc_socket_shutdown(isc_socket_t socket, isc_task_t task, + isc_socketshutdown_t how); +/* + * Shutdown 'socket' according to 'how'. + * + * Note: if 'task' is NULL, then the shutdown applies to all tasks using the + * socket. + * + * Requires: + * + * 'socket' is a valid socket. + * + * 'task' is NULL or is a valid task. + * + * Ensures: + * + * If 'how' is 'isc_sockshut_reading' or 'isc_sockshut_all' then + * + * Any pending read completion events for the task are + * removed from the task's event queue. + * + * No further read completion events will be delivered to the + * task. + * + * No further read requests may be made. + * + * If 'how' is 'isc_sockshut_writing' or 'isc_sockshut_all' then + * + * Any pending write completion events for the task are + * removed from the task's event queue. + * + * No further write completion events will be delivered to the + * task. + * + * No further write requests may be made. + * + * If 'socket' is a TCP socket, then when the last currently + * pending write completes, TCP FIN will sent to the remote peer. + */ + +void +isc_socket_attach(isc_socket_t socket, isc_socket_t *socketp); +/* + * Attach *socketp to socket. + * + * Requires: + * + * 'socket' is a valid socket. + * + * 'socketp' points to a NULL socket. + * + * Ensures: + * + * *socketp is attached to socket. + */ + +void +isc_socket_detach(isc_socket_t *socketp); +/* + * Detach *socketp from its socket. + * + * Notes: + * + * Detaching the last reference may cause any still-pending I/O to be + * cancelled. + * + * Requires: + * + * 'socketp' points to a valid socket. + * + * Ensures: + * + * *socketp is NULL. + * + * If '*socketp' is the last reference to the socket, + * then: + * + * The socket will be shutdown (both reading and writing) + * for all tasks. + * + * All resources used by the socket have been freed + */ + +isc_result_t +net_socket_bind(isc_socket_t socket, struct isc_sockaddr *addressp, + int length); +/* + * Bind 'socket' to '*addressp'. + * + * Requires: + * + * 'socket' is a valid socket + * + * 'addressp' points to a valid isc_sockaddr. + * + * 'length' is approprate for the isc_sockaddr type. + * + * Returns: + * + * Success + * Address not available + * Address in use + * Permission denied + * Unexpected error + */ + +isc_result_t +isc_socket_listen(isc_socket_t socket, int backlog, + isc_task_t task, isc_taskaction_t action, void *arg); +/* + * Listen on 'socket'. Every time a new connection request arrives, + * a NEWCONN event with action 'action' and arg 'arg' will be posted + * to the event queue for 'task'. + * + * Notes: + * + * 'backlog' is as in the UNIX system call listen(). + * + * Requires: + * + * 'socket' is a valid TCP socket. + * + * 'task' is a valid task + * + * 'action' is a valid action + * + * Returns: + * + * Success + * Unexpected error + */ + +void +isc_socket_hold(isc_socket_t socket); +/* + * Put a TCP listener socket on hold. No NEWCONN events will be posted + * + * Notes: + * + * While 'on hold', new connection requests will be queued or dropped + * by the operating system. + * + * Requires: + * + * 'socket' is a valid TCP socket + * + * Some task is listening on the socket. + * + */ + +void +isc_socket_unhold(isc_socket_t socket); +/* + * Restore normal NEWCONN event posting. + * + * Requires: + * + * 'socket' is a valid TCP socket + * + * Some task is listening on the socket. + * + * 'socket' is holding. + * + */ + +isc_result_t +isc_socket_accept(isc_socket_t s1, isc_socket_t *s2p); +/* + * Accept a connection from 's1', creating a new socket for the connection + * and attaching '*s2p' to it. + * + * Requires: + * + * 'socket' is a valid TCP socket. + * + * s2p is a valid pointer, and *s2p == NULL; + * + * Ensures: + * + * *s2p is attached to the newly created socket. + * + * Returns: + * + * Success + * No memory + * No pending connection requests + * Unexpected error + */ + +isc_result_t +isc_socket_connect(isc_socket_t socket, struct isc_sockaddr *addressp, + int length, isc_task_t task, isc_taskaction_t action, + void *arg); +/* + * Connect 'socket' to peer with address *saddr. When the connection + * succeeds, or when an error occurs, a CONNECTED event with action 'action' + * and arg 'arg' will be posted to the event queue for 'task'. + * + * Requires: + * + * 'socket' is a valid TCP socket + * + * 'addressp' points to a valid isc_sockaddr + * + * 'length' is approprate for the isc_sockaddr type + * + * 'task' is a valid task + * + * 'action' is a valid action + * + * Returns: + * + * Success + * No memory + * Address not available + * Address in use + * Host unreachable + * Network unreachable + * Connection refused + * Unexpected error + */ + +isc_result_t +isc_socket_getpeername(isc_socket_t socket, struct isc_sockaddr *addressp, + int *lengthp); +/* + * Get the name of the peer connected to 'socket'. + * + * Requires: + * + * 'socket' is a valid TCP socket. + * + * 'addressp' points to '*lengthp' bytes. + * + * Returns: + * + * Success + * Address buffer too small + */ + +isc_result_t +isc_socket_getsockname(isc_socket_t socket, struct isc_sockaddr *addressp, + int *lengthp); +/* + * Get the name of 'socket'. + * + * Requires: + * + * 'socket' is a valid socket. + * + * 'addressp' points to '*lengthp' bytes. + * + * Returns: + * + * Success + * Address buffer too small + */ + +isc_result_t +isc_socket_recv(isc_socket_t socket, isc_region_t region, + isc_boolean_t partial, + isc_task_t task, isc_taskaction_t action, void *arg); +/* + * Receive from 'socket', storing the results in region. + * + * Notes: + * + * Let 'length' refer to the length of 'region'. + * + * If 'partial' is true, then at most 'length' bytes will be read. + * Otherwise the read will not complete until exactly 'length' bytes + * have been read. + * + * The read will complete when the desired number of bytes have been + * read, if end-of-input occurs, or if an error occurs. A read done + * event with the given 'action' and 'arg' will be posted to the + * event queue of 'task'. + * + * The caller may neither read from nor write to 'region' until it + * has received the read completion event. + * + * Requires: + * + * 'socket' is a valid socket + * + * 'region' is a valid region + * + * 'task' is a valid task + * + * action != NULL and is a valid action + * + * Returns: + * + * Success + * No memory + * Unexpected error + */ + +isc_result_t +isc_socket_send(isc_socket_t socket, isc_region_t region, + isc_task_t task, isc_taskaction_t action, void *arg); +/* + * Send the contents of 'region' to the socket's peer. + * + * Notes: + * + * Shutting down the requestor's task *may* result in any + * still pending writes being dropped. + * + * If 'action' is NULL, then no completion event will be posted. + * + * The caller may neither read from nor write to 'region' until it + * has received the write completion event, or all references to the + * socket have been detached. + * + * Requires: + * + * 'socket' is a valid socket + * + * 'region' is a valid region + * + * 'task' is a valid task + * + * action == NULL or is a valid action + * + * Returns: + * + * Success + * No memory + * Unexpected error + */ + +/* XXX this is some of how to do a read + * generate new net_request + * generate new read-result net_event + * attach to requestor + * lock socket + * queue request + * unlock socket + */ + +isc_result_t +isc_socketmgr_create(isc_memctx_t mctx, isc_socketmgr_t *managerp); +/* + * Create a socket manager. + * + * Notes: + * + * All memory will be allocated in memory context 'mctx'. + * + * Requires: + * + * 'mctx' is a valid memory context. + * + * 'managerp' points to a NULL isc_socketmgr_t. + * + * Ensures: + * + * '*managerp' is a valid isc_socketmgr_t. + * + * Returns: + * + * Success + * No memory + * Unexpected error + */ + +void +isc_socketmgr_destroy(isc_socketmgr_t *); +/* + * Destroy a socket manager. + * + * Notes: + * + * This routine blocks until there are no sockets left in the manager, + * so if the caller holds any socket references using the manager, it + * must detach them before calling isc_socketmgr_destroy() or it will + * block forever. + * + * Requires: + * + * '*managerp' is a valid isc_socketmgr_t. + * + * Ensures: + * + * *managerp == NULL + * + * All resources used by the manager have been freed. + */ + +#endif /* ISC_SOCKET_H */ diff --git a/lib/isc/unix/socket.c b/lib/isc/unix/socket.c new file mode 100644 index 0000000000..a0209f81c0 --- /dev/null +++ b/lib/isc/unix/socket.c @@ -0,0 +1,524 @@ + +#include "attribute.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#define WINAPI /* we're not windows */ +#endif + +/* + * We use macros instead of calling the routines directly because + * the capital letters make the locking stand out. + * + * We INSIST that they succeed since there's no way for us to continue + * if they fail. + */ + +#define LOCK(lp) \ + INSIST(isc_mutex_lock((lp)) == ISC_R_SUCCESS); +#define UNLOCK(lp) \ + INSIST(isc_mutex_unlock((lp)) == ISC_R_SUCCESS); +#define BROADCAST(cvp) \ + INSIST(isc_condition_broadcast((cvp)) == ISC_R_SUCCESS); +#define SIGNAL(cvp) \ + INSIST(isc_condition_signal((cvp)) == ISC_R_SUCCESS); +#define WAIT(cvp, lp) \ + INSIST(isc_condition_wait((cvp), (lp)) == ISC_R_SUCCESS); +#define WAITUNTIL(cvp, lp, tp) \ + isc_condition_waituntil((cvp), (lp), (tp)) + +/* + * Debugging + */ +#if 1 +#define XTRACE(a) fprintf(stderr, a) +#define XENTER(a) fprintf(stderr, "ENTER %s\n", (a)) +#define XEXIT(a) fprintf(stderr, "EXIT %s\n", (a)) +#else +#define XTRACE(a) +#define XENTER(a) +#define XEXIT(a) +#endif + +/* + * A socket request. These are allocated XXX + */ +struct isc_socket_req { + isc_task_t task; +}; + + +#define SOCKET_MAGIC 0x494f696fU /* IOio */ +#define VALID_SOCKET(t) ((t) != NULL && \ + (t)->magic == SOCKET_MAGIC) +struct isc_socket { + /* Not locked. */ + unsigned int magic; + isc_socketmgr_t manager; + isc_mutex_t lock; + /* Locked by socket lock. */ + unsigned int references; + int fd; + LIST(struct isc_socket_req) read_reqs; + LIST(struct isc_socket_req) write_reqs; + /* Locked by manager lock. */ + isc_sockettype_t type; + LINK(struct isc_socket) link; +}; + +#define SOCKET_MANAGER_MAGIC 0x494f6d67U /* IOmg */ +#define VALID_MANAGER(m) ((m) != NULL && \ + (m)->magic == SOCKET_MANAGER_MAGIC) + +struct isc_socketmgr { + /* Not locked. */ + unsigned int magic; + isc_memctx_t mctx; + isc_mutex_t lock; + /* Locked by manager lock. */ + isc_boolean_t done; + LIST(struct isc_socket) sockets; + unsigned int nscheduled; + isc_thread_t thread; + int pipe_fds[2]; /* XXX lock needed? */ + fd_set read_fds; /* XXX This sucks... */ + fd_set write_fds; +}; + +#define SELECT_POKE_SHUTDOWN (1) +#define SELECT_POKE_REFRESH (2) + +typedef unsigned long select_msg_t; + +/* + * poke the select loop when there is something for us to do. + */ +static void +select_poke(isc_socketmgr_t mgr, select_msg_t msg) +{ + write(mgr->pipe_fds[1], &msg, sizeof(select_msg_t)); +} + +/* + * read a message on the internal fd. + */ +static select_msg_t +select_readmsg(isc_socketmgr_t mgr) +{ + select_msg_t msg; + + read(mgr->pipe_fds[0], &msg, sizeof(select_msg_t)); + + return msg; +} + +/* + * Set a socket up for reading or writing. This is a low level, internal + * routine. + * + * Caller must ensure locking. + */ +static inline isc_result_t +schedule(isc_socket_t sock) +{ + isc_result_t result; + isc_socketmgr_t manager; + + /* + * do stuff here to arange to track I/O on this socket. + */ + + return (ISC_R_SUCCESS); +} + +/* + * Remove either read, write, or both from a socket. + * + * Caller must ensure locking. + */ +static inline void +deschedule(isc_socket_t sock) +{ + isc_boolean_t need_wakeup = ISC_FALSE; + isc_socketmgr_t manager; + + manager = sock->manager; +} + +/* + * Kill. + * + * Caller must ensure locking. + */ +static void +destroy(isc_socket_t sock) +{ + isc_socketmgr_t manager = sock->manager; + + LOCK(&manager->lock); + + /* + * XXX + * This is going to be tricky... Run through the list of all + * tasks attached to this socket and purge events in their + * queues. + */ + deschedule(sock); + UNLINK(manager->sockets, sock, link); + + UNLOCK(&manager->lock); + + (void)isc_mutex_destroy(&sock->lock); + sock->magic = 0; + isc_mem_put(manager->mctx, sock, sizeof *sock); +} + +/* + * Create a new 'type' socket managed by 'manager'. The sockets + * parameters are specified by 'expires' and 'interval'. Events + * will be posted to 'task' and when dispatched 'action' will be + * called with 'arg' as the arg value. The new socket is returned + * in 'socketp'. + */ +isc_result_t +isc_socket_create(isc_socketmgr_t manager, isc_sockettype_t type, + isc_socket_t *socketp) +{ + isc_socket_t sock; + isc_result_t result; + + REQUIRE(VALID_MANAGER(manager)); + REQUIRE(socketp != NULL && *socketp == NULL); + + XENTER("isc_socket_create"); + + sock = isc_mem_get(manager->mctx, sizeof *sock); + if (sock == NULL) + return (ISC_R_NOMEMORY); + + sock->magic = SOCKET_MAGIC; + sock->manager = manager; + sock->references = 1; + sock->type = type; + + /* + * set up list of readers and writers to be initially empty + */ + INIT_LIST(sock->read_reqs); + INIT_LIST(sock->write_reqs); + + /* + * Create the associated socket XXX + */ + switch (type) { + case isc_socket_udp: + sock->fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + break; + case isc_socket_tcp: + sock->fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + break; + } + if (sock->fd < 0) { + isc_mem_put(manager->mctx, sock, sizeof *sock); + + switch (errno) { + case EMFILE: + case ENFILE: + case ENOBUFS: + return (ISC_R_NORESOURCES); + break; + default: + UNEXPECTED_ERROR(__FILE__, __LINE__, + "socket() failed: %s", + strerror(errno)); + return (ISC_R_UNEXPECTED); + break; + } + } + + /* + * initialize the lock + */ + if (isc_mutex_init(&sock->lock) != ISC_R_SUCCESS) { + isc_mem_put(manager->mctx, sock, sizeof *sock); + close(sock->fd); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_mutex_init() failed"); + return (ISC_R_UNEXPECTED); + } + + LOCK(&manager->lock); + + /* + * Note we don't have to lock the socket like we normally would because + * there are no external references to it yet. + */ + + APPEND(manager->sockets, sock, link); + result = schedule(sock); + + UNLOCK(&manager->lock); + + if (result == ISC_R_SUCCESS) + *socketp = sock; + + XEXIT("isc_socket_create"); + + return (result); +} + +/* + * Attach to a socket. Caller must explicitly detach when it is done. + */ +void +isc_socket_attach(isc_socket_t sock, isc_socket_t *socketp) +{ + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(socketp != NULL && *socketp == NULL); + + LOCK(&sock->lock); + sock->references++; + UNLOCK(&sock->lock); + + *socketp = sock; +} + +/* + * Dereference a socket. If this is the last reference to it, clean things + * up by destroying the socket. + */ +void +isc_socket_detach(isc_socket_t *socketp) +{ + isc_socket_t sock; + isc_boolean_t free_socket = ISC_FALSE; + + REQUIRE(socketp != NULL); + sock = *socketp; + REQUIRE(VALID_SOCKET(sock)); + + XENTER("isc_socket_detach"); + + LOCK(&sock->lock); + REQUIRE(sock->references > 0); + sock->references--; + if (sock->references == 0) + free_socket = ISC_TRUE; + UNLOCK(&sock->lock); + + if (free_socket) + destroy(sock); + + XEXIT("isc_socket_detach"); + + *socketp = NULL; +} + +static void +dispatch(isc_socketmgr_t manager, isc_socket_t sock) +{ + isc_boolean_t done = ISC_FALSE; + isc_boolean_t post_event; + isc_boolean_t need_schedule; + isc_event_t event; + isc_eventtype_t type = 0; + isc_result_t result; + + while (manager->nscheduled > 0 && !done) { + /* + * Do what here? XXX + */ + } +} + +/* + * This is the task that will loop forever, always in a select or poll call. + * When select returns something to do, track down what thread gets to do + * this I/O and post the event to it. + */ +static isc_threadresult_t +WINAPI +run(void *uap) +{ + isc_socketmgr_t manager = uap; + isc_boolean_t done; + int ctlfd; + int cc; + fd_set readfds; + fd_set writefds; + select_msg_t msg; + + /* + * Get the control fd here. This will never change. + */ + LOCK(&manager->lock); + ctlfd = manager->pipe_fds[0]; + + done = ISC_FALSE; + while (!done) { + readfds = manager->read_fds; + writefds = manager->write_fds; + + UNLOCK(&manager->lock); + + /* + * call select/poll. This will block. XXX flesh out + */ + cc = select(FD_SETSIZE, &readfds, &writefds, NULL, NULL); + if (cc < 0) { + if (errno != EINTR) + UNEXPECTED_ERROR(__FILE__, __LINE__, + "select returned error (%s)", + strerror(errno)); + } + + LOCK(&manager->lock); + + /* + * Process reads on internal, control fd. + */ + if (FD_ISSET(ctlfd, &readfds)) { + msg = select_readmsg(manager); + + /* + * handle shutdown message. No other type is handled + * here, as REFRESH tells us to reread our fd_sets, + * which we always do at the top of the loop. + */ + if (msg == SELECT_POKE_SHUTDOWN) + done = ISC_TRUE; + } + + /* + * Process read/writes on other fds here + */ + } + + UNLOCK(&manager->lock); + return ((isc_threadresult_t)0); +} + +/* + * Create a new socket manager. + */ +isc_result_t +isc_socketmgr_create(isc_memctx_t mctx, isc_socketmgr_t *managerp) +{ + isc_socketmgr_t manager; + + REQUIRE(managerp != NULL && *managerp == NULL); + + XENTER("isc_socketmgr_create"); + + manager = isc_mem_get(mctx, sizeof *manager); + if (manager == NULL) + return (ISC_R_NOMEMORY); + + manager->magic = SOCKET_MANAGER_MAGIC; + manager->mctx = mctx; + manager->done = ISC_FALSE; + INIT_LIST(manager->sockets); + manager->nscheduled = 0; + if (isc_mutex_init(&manager->lock) != ISC_R_SUCCESS) { + isc_mem_put(mctx, manager, sizeof *manager); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_mutex_init() failed"); + return (ISC_R_UNEXPECTED); + } + + /* + * Create the special fds that will be used to wake up the + * select/poll loop when something internal needs to be done. + */ + if (pipe(manager->pipe_fds) != 0) { + (void)isc_mutex_destroy(&manager->lock); + isc_mem_put(mctx, manager, sizeof *manager); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "pipe() failed: %s", + strerror(errno)); /* XXX */ + + return (ISC_R_UNEXPECTED); + } + + /* + * Set up initial state for the select loop + */ + FD_ZERO(&manager->read_fds); + FD_ZERO(&manager->write_fds); + FD_SET(manager->pipe_fds[0], &manager->read_fds); + + /* + * Start up the select/poll thread. + */ + if (isc_thread_create(run, manager, &manager->thread) != + ISC_R_SUCCESS) { + (void)isc_mutex_destroy(&manager->lock); + isc_mem_put(mctx, manager, sizeof *manager); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_thread_create() failed"); + return (ISC_R_UNEXPECTED); + } + + *managerp = manager; + + XEXIT("isc_socketmgr_create (normal)"); + return (ISC_R_SUCCESS); +} + +void +isc_socketmgr_destroy(isc_socketmgr_t *managerp) +{ + isc_socketmgr_t manager; + + /* + * Destroy a socket manager. + */ + + REQUIRE(managerp != NULL); + manager = *managerp; + REQUIRE(VALID_MANAGER(manager)); + + LOCK(&manager->lock); + REQUIRE(EMPTY(manager->sockets)); + manager->done = ISC_TRUE; + UNLOCK(&manager->lock); + + /* + * Here, poke our select/poll thread. Do this by closing the write + * half of the pipe, which will send EOF to the read half. + */ + select_poke(manager, SELECT_POKE_SHUTDOWN); + + /* + * Wait for thread to exit. + */ + if (isc_thread_join(manager->thread, NULL) != ISC_R_SUCCESS) + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_thread_join() failed"); + + /* + * Clean up. + */ + close(manager->pipe_fds[0]); + close(manager->pipe_fds[1]); + (void)isc_mutex_destroy(&manager->lock); + manager->magic = 0; + isc_mem_put(manager->mctx, manager, sizeof *manager); + + *managerp = NULL; +}