From a89a8e37542a17298ed5f38b0bec7d3cedc8d396 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Mon, 31 Jan 2000 14:46:29 +0000 Subject: [PATCH] New function, omapi_listener_shutdown, to destroy all memory used by the listener. Manage a list of all connections known by the listener, so they can be shut down by omapi_listener_shutdown. --- lib/omapi/listener.c | 286 +++++++++++++++++++++++++++++++++---------- 1 file changed, 221 insertions(+), 65 deletions(-) diff --git a/lib/omapi/listener.c b/lib/omapi/listener.c index bff2142dff..6a0c5c5f78 100644 --- a/lib/omapi/listener.c +++ b/lib/omapi/listener.c @@ -30,9 +30,16 @@ typedef struct omapi_listener_object { OMAPI_OBJECT_PREAMBLE; + isc_mutex_t mutex; isc_task_t *task; - isc_socket_t *socket; /* Connection socket. */ + isc_socket_t *socket; /* Listening socket. */ isc_sockaddr_t address; + /* + * Locked by mutex. + */ + isc_boolean_t accepting; + isc_condition_t waiter; + ISC_LIST(omapi_connection_t) connections; } omapi_listener_t; /* @@ -47,21 +54,15 @@ listener_accept(isc_task_t *task, isc_event_t *event) { isc_socket_t *socket; omapi_connection_t *connection = NULL; omapi_object_t *protocol = NULL; + omapi_listener_t *listener; /* - * XXXDCL What are the meaningful things the listen/accept function - * can do if it fails to process an incoming connection because one - * of the functions it calls fails? - * The cleanup options are hurting my head. + * XXXDCL audit error handling */ - /* - * Immediately set up another listen task for the socket. - */ - isc_socket_accept(event->sender, task, listener_accept, event->arg); - result = ((isc_socket_newconnev_t *)event)->result; socket = ((isc_socket_newconnev_t *)event)->newsocket; + listener = (omapi_listener_t *)event->arg; /* * No more need for the event, once all the desired data has been @@ -69,13 +70,32 @@ listener_accept(isc_task_t *task, isc_event_t *event) { */ isc_event_free(&event); + if (result == ISC_R_CANCELED) { + /* + * omapi_listener_shutdown was called. Stop accepting incoming + * connection by not queuing another accept. + */ + LOCK(&listener->mutex); + listener->accepting = ISC_FALSE; + + SIGNAL(&listener->waiter); + UNLOCK(&listener->mutex); + + return; + } + + /* + * Set up another accept task for the socket. + */ + isc_socket_accept(listener->socket, task, listener_accept, listener); + /* * Check for the validity of new connection event. */ if (result != ISC_R_SUCCESS) /* - * The result is probably ISC_R_UNEXPECTED; what can really be - * done about this other than just flunking out of here? + * The result is probably ISC_R_UNEXPECTED. What can really + * be done about it other than just * flunking out of here? */ return; @@ -117,9 +137,6 @@ listener_accept(isc_task_t *task, isc_event_t *event) { ISC_LIST_INIT(connection->output_buffers); ISC_LIST_APPEND(connection->output_buffers, obuffer, link); - RUNTIME_CHECK(isc_mutex_init(&connection->mutex) == ISC_R_SUCCESS); - RUNTIME_CHECK(isc_mutex_init(&connection->recv_lock) == ISC_R_SUCCESS); - /* * Create a new protocol object to oversee the handling of this * connection. @@ -138,31 +155,34 @@ listener_accept(isc_task_t *task, isc_event_t *event) { OBJECT_REF(&connection->inner, protocol); /* - * Send the introductory message. - */ - result = send_intro(protocol, OMAPI_PROTOCOL_VERSION); - - if (result != ISC_R_SUCCESS) - goto free_protocol_object; - - /* - * Lose the external reference to the protocol object so - * both the connection object and protocol object will - * be freed when the connection ends. + * Lose the external reference to the protocol object so both the + * connection object and protocol object will be freed when the + * connection ends. */ OBJECT_DEREF(&protocol); + /* + * Add the connection to the list of connections known by the + * listener. This is an added reference to the connection + * object, but since there's no easy way to use omapi_object_reference + * with the ISC_LIST macros, that reference is just not counted. + */ + ISC_LIST_APPEND(listener->connections, connection, link); + + /* + * Remember the listener that accepted the connection, so it + * can be told when the connection goes away. + */ + OBJECT_REF(&connection->listener, listener); + + /* + * Send the introductory message. The return value does not + * matter; if send_intro failed, it already destroyed the connection. + */ + (void)send_intro(connection->inner, OMAPI_PROTOCOL_VERSION); + return; -free_protocol_object: - /* - * Remove the protocol object's reference to the connection - * object, so that the connection object will be destroyed. - * XXXDCL I am not quite sure the right thing is being done here. - */ - OBJECT_DEREF(&protocol); - - /* FALLTHROUGH */ free_connection_object: /* * Destroy the connection. This will free everything created @@ -189,6 +209,8 @@ omapi_listener_listen(omapi_object_t *caller, int port, int max) { omapi_listener_t *listener; struct in_addr inaddr; + REQUIRE(caller != NULL); + task = NULL; result = isc_task_create(omapi_taskmgr, NULL, 0, &task); if (result != ISC_R_SUCCESS) @@ -208,11 +230,9 @@ omapi_listener_listen(omapi_object_t *caller, int port, int max) { listener->task = task; - /* - * Tie the listener object to the calling object. - */ - OBJECT_REF(&caller->outer, listener); - OBJECT_REF(&listener->inner, caller); + ISC_LIST_INIT(listener->connections); + RUNTIME_CHECK(isc_mutex_init(&listener->mutex) == ISC_R_SUCCESS); + RUNTIME_CHECK(isc_condition_init(&listener->waiter) == ISC_R_SUCCESS); /* * Create a socket on which to listen. @@ -225,6 +245,9 @@ omapi_listener_listen(omapi_object_t *caller, int port, int max) { /* * Set up the addressses on which to listen and bind to it. */ + if (port == 0) + port = OMAPI_PROTOCOL_PORT; + inaddr.s_addr = INADDR_ANY; isc_sockaddr_fromin(&listener->address, &inaddr, port); @@ -237,25 +260,137 @@ omapi_listener_listen(omapi_object_t *caller, int port, int max) { */ result = isc_socket_listen(listener->socket, max); - if (result == ISC_R_SUCCESS) + if (result == ISC_R_SUCCESS) { /* * Queue up the first accept event. The listener object - * will be passed to listener_accept() when it is called, - * though currently nothing is done with it. + * will be passed to listener_accept() when it is called. */ + listener->accepting = ISC_TRUE; result = isc_socket_accept(listener->socket, task, listener_accept, listener); + } - if (result != ISC_R_SUCCESS) + if (result == ISC_R_SUCCESS) { /* - * The listener has a refcnt of 2, so this does not really - * free it. XXXDCL + * Tie the listener object to the calling object. + */ + OBJECT_REF(&caller->outer, listener); + OBJECT_REF(&listener->inner, caller); + + } else + /* + * Failed to set up the listener. */ OBJECT_DEREF(&listener); return (result); } +void +omapi_listener_shutdown(omapi_object_t *listener) { + omapi_listener_t *l; + omapi_connection_t *c; + isc_time_t timeout; + isc_interval_t interval; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE((listener != NULL && listener->type == omapi_type_listener) || + (listener->outer != NULL && + listener->outer->type == omapi_type_listener)); + + if (listener->type == omapi_type_listener) + l = (omapi_listener_t *)listener; + else + l = (omapi_listener_t *)listener->outer; + + /* + * It is improper to call this function without having had a successful + * run of omapi_listener_listen. + */ + REQUIRE(l->socket != NULL && l->task != NULL); + + /* + * Stop accepting connections. + */ + isc_socket_cancel(l->socket, NULL, ISC_SOCKCANCEL_ACCEPT); + + /* + * All connections this listener was responsible for must be gone. + * Since it is possible that this shutdown was triggered by one + * of the clients, give it a little time to exit, as well as + * allowing other connections to finish up cleanly. The + * cancelled accept event also needs to be received before + * the listener task, socket and object can be destroyed. + * + * isc_time_nowplusinterval returns an isc_result_t; anything other + * than ISC_R_SUCCESS is wildly unexpected because the Unix + * implementation uses gettimeofday(), which is documented to only + * return an error if its argument is an invalid memory address, and + * the Win32 implementation always returns ISC_R_SUCCESS. In any + * event, if it fails, there is nothing to do but soldier on. + * The waituntil would immediately timeout, and the connections + * would be forcibly blown away. + * + * 5 seconds is an arbitrary constant. + */ + isc_interval_set(&interval, 5, 0); + isc_time_nowplusinterval(&timeout, &interval); + + LOCK(&l->mutex); + + while (! ISC_LIST_EMPTY(l->connections) && result == ISC_R_SUCCESS) { + ISC_UTIL_TRACE(fprintf(stderr, "WAIT %p LOCK %p %s %d\n", + &l->waiter, &l->mutex, + __FILE__, __LINE__)); + + result = isc_condition_waituntil(&l->waiter, &l->mutex, + &timeout); + + ISC_UTIL_TRACE(fprintf(stderr, "WAITED %p LOCKED %p %s %d\n", + &l->waiter, &l->mutex, + __FILE__, __LINE__)); + } + + /* + * If there are still some connections hanging about, + * they won't be for long. + */ + for (c = ISC_LIST_HEAD(l->connections); c != NULL; + c = ISC_LIST_NEXT(c, link)) + omapi_connection_disconnect((omapi_object_t *)c, + OMAPI_FORCE_DISCONNECT); + + /* + * Again wait for any remaining connections to be destroyed, and + * ensure the listen socket has received the cancelled accept event. + * This will happen rapidly now because they were all cancelled. + */ + while (! ISC_LIST_EMPTY(l->connections) || l->accepting) + WAIT(&l->waiter, &l->mutex); + + /* + * The accept cancel event should now have been posted, + * and the connections should be gone. + */ + INSIST(! l->accepting && ISC_LIST_EMPTY(l->connections)); + + UNLOCK(&l->mutex); + + /* + * Break the link between the listener object and its parent + * (usually a generic object); this is done so the server's + * reference to its managing object does not prevent the listener + * object from being destroyed. + */ + OBJECT_DEREF(&l->inner->outer); + OBJECT_DEREF(&l->inner); + + /* + * The listener object can now be freed. + */ + OBJECT_DEREF(&l); +} + static isc_result_t listener_setvalue(omapi_object_t *listener, omapi_string_t *name, omapi_data_t *value) @@ -283,38 +418,59 @@ listener_getvalue(omapi_object_t *listener, omapi_string_t *name, } static void -listener_destroy(omapi_object_t *object) { - omapi_listener_t *listener; +listener_destroy(omapi_object_t *listener) { + omapi_listener_t *l; - REQUIRE(object != NULL && object->type == omapi_type_listener); + REQUIRE(listener != NULL && listener->type == omapi_type_listener); - listener = (omapi_listener_t *)object; + l = (omapi_listener_t *)listener; - isc_task_destroy(&listener->task); + INSIST(ISC_LIST_EMPTY(l->connections)); - if (listener->socket != NULL) { -#if 0 /*XXXDCL*/ - isc_socket_cancel(listener->socket, NULL, ISC_SOCKCANCEL_ALL); - isc_socket_shutdown(listener->socket, ISC_SOCKSHUT_ALL); -#else - isc_task_shutdown(listener->task); -#endif - listener->socket = NULL; + if (l->task != NULL) { + isc_task_destroy(&l->task); + l->task = NULL; } + + if (l->socket != NULL) { + isc_socket_detach(&l->socket); + l->socket = NULL; + } + + RUNTIME_CHECK(isc_mutex_destroy(&l->mutex) == ISC_R_SUCCESS); + RUNTIME_CHECK(isc_condition_destroy(&l->waiter) == ISC_R_SUCCESS); } static isc_result_t listener_signalhandler(omapi_object_t *listener, const char *name, va_list ap) { + omapi_connection_t *c; + omapi_listener_t *l; + isc_result_t result; + REQUIRE(listener != NULL && listener->type == omapi_type_listener); - + + l = (omapi_listener_t *)listener; + /* - * This function is reached when listener_accept does an - * object_signal of "connect" on the listener object. Nothing - * need be done here, but the object that originally requested - * the listen needs to signalled that a connection was made. + * free_connection() signals the listener when one of the connections + * it accepted has gone away. */ - return (omapi_object_passsignal(listener, name, ap)); + if (strcmp(name, "disconnect") == 0) { + c = va_arg(ap, omapi_connection_t *); + + LOCK(&l->mutex); + + ISC_LIST_UNLINK(l->connections, c, link); + + SIGNAL(&l->waiter); + UNLOCK(&l->mutex); + + result = ISC_R_SUCCESS; + } else + result = omapi_object_passsignal(listener, name, ap); + + return (result); } /*