rem: (void) Queue(sock, NULL, NULL, 0);
add: (void) Ns_SockCancelCallbackEx(sock, NULL, NULL); }  int Ns_SockCancelCallbackEx(SOCKET sock, Ns_SockProc *proc, void *arg) { return Queue(sock, proc, arg, NS_SOCK_CANCEL);
context: static Ns_ThreadProc SockCallbackThread;static int Queue(SOCKET sock, Ns_SockProc *proc, void *arg, int when);static void CallbackTrigger(void);/* * Static variables defined in this file */static Callback	    *firstQueuePtr, *lastQueuePtr;static int	     shutdownPending;static int	     running;static Ns_Thread     sockThread;static Ns_Mutex      lock;static Ns_Cond	     cond;static SOCKET	     trigPipe[2];static Tcl_HashTable table;/* *---------------------------------------------------------------------- * * Ns_SockCallback -- * *	Register a callback to be run when a socket reaches a certain  *	state.  * * Results: *	NS_OK/NS_ERROR  * * Side effects: *	Will wake up the callback thread.  * *---------------------------------------------------------------------- */intNs_SockCallback(SOCKET sock, Ns_SockProc *proc, void *arg, int when){    return Queue(sock, proc, arg, when);}/* *---------------------------------------------------------------------- * * Ns_SockCancelCallback -- * *	Remove a callback registered on a socket.  * * Results: *	None.  * * Side effects: *	Will wake up the callback thread.  * *---------------------------------------------------------------------- */voidNs_SockCancelCallback(SOCKET sock){    (void) Queue(sock, NULL, NULL, 0);}/* *---------------------------------------------------------------------- * * NsStartSockShutdown, NsWaitSockShutdown -- * *	Initiate and then wait for socket callbacks shutdown. * * Results: *	None. * * Side effects: *	May timeout waiting for shutdown. * *---------------------------------------------------------------------- */voidNsStartSockShutdown(void){    Ns_MutexLock(&lock);    if (running) {	shutdownPending = 1;	CallbackTrigger();    }    Ns_MutexUnlock(&lock);}voidNsWaitSockShutdown(Ns_Time *toPtr){    int status;        status = NS_OK;    Ns_MutexLock(&lock);    while (status == NS_OK && running) {	status = Ns_CondTimedWait(&cond, &lock, toPtr);    }    Ns_MutexUnlock(&lock);    if (status != NS_OK) {	Ns_Log(Warning, "socks: timeout waiting for callback shutdown");    } else if (sockThread != NULL) {	Ns_ThreadJoin(&sockThread, NULL);	sockThread = NULL;    	ns_sockclose(trigPipe[0]);    	ns_sockclose(trigPipe[1]);    }}/* *---------------------------------------------------------------------- * * CallbackTrigger -- * *	Wakeup the callback thread if it's in poll(). * * Results: *	None. * * Side effects: *	None. * *---------------------------------------------------------------------- */static voidCallbackTrigger(void){    if (send(trigPipe[1], "", 1, 0) != 1) {	Ns_Fatal("trigger send() failed: %s", ns_sockstrerror(ns_sockerrno));    }}/* *---------------------------------------------------------------------- * * Queue -- * *	Queue a callback for socket. * * Results: *	NS_OK or NS_ERROR on shutdown pending. * * Side effects: *	Socket thread may be created or signalled. * *---------------------------------------------------------------------- */static intQueue(SOCKET sock, Ns_SockProc *proc, void *arg, int when){    Callback   *cbPtr;    int         status, trigger, create;    cbPtr = ns_malloc(sizeof(Callback));    cbPtr->sock = sock;    cbPtr->proc = proc;    cbPtr->arg = arg;    cbPtr->when = when;    trigger = create = 0;    Ns_MutexLock(&lock);    if (shutdownPending) {	ns_free(cbPtr);    	status = NS_ERROR;    } else {	if (!running) {    	    Tcl_InitHashTable(&table, TCL_ONE_WORD_KEYS);	    Ns_MutexSetName(&lock, "ns:sockcallbacks");	    create = 1;	    running = 1;	} else if (firstQueuePtr == NULL) {	    trigger = 1;	}        if (firstQueuePtr == NULL) {            firstQueuePtr = cbPtr;        } else {            lastQueuePtr->nextPtr = cbPtr;        }        cbPtr->nextPtr = NULL;        lastQueuePtr = cbPtr;    	status = NS_OK;    }    Ns_MutexUnlock(&lock);    if (trigger) {	CallbackTrigger();    } else if (create) {    	if (ns_sockpair(trigPipe) != 0) {	    Ns_Fatal("ns_sockpair() failed: %s", ns_sockstrerror(ns_sockerrno));    	}    	Ns_ThreadCreate(SockCallbackThread, NULL, 0, &sockThread);    }    return status;}/* *---------------------------------------------------------------------- * * SockCallbackThread -- * *	Run callbacks registered with Ns_SockCallback. * * Results: *	None.  * * Side effects: *	Depends on callbacks. * *---------------------------------------------------------------------- */static voidSockCallbackThread(void *ignored){    char          c;    int           when[3], events[3];    int           n, i, new, stop;    int		  max, nfds;    Callback     *cbPtr;    Tcl_HashEntry *hPtr;    Tcl_HashSearch search;    struct pollfd *pfds;    Ns_ThreadSetName("-socks-");    Ns_WaitForStartup();    Ns_Log(Notice, "socks: starting");    events[0] = POLLIN;    events[1] = POLLOUT;    events[2] = POLLPRI;    when[0] = NS_SOCK_READ;    when[1] = NS_SOCK_WRITE;    when[2] = NS_SOCK_EXCEPTION | NS_SOCK_DROP;    max = 100;    pfds = ns_malloc(sizeof(struct pollfd) * max);    pfds[0].fd = trigPipe[0];    pfds[0].events = POLLIN;        while (1) {	/*	 * Grab the list of any queue updates and the shutdown	 * flag.	 */    	Ns_MutexLock(&lock);	cbPtr = firstQueuePtr;	firstQueuePtr = NULL;        lastQueuePtr = NULL;	stop = shutdownPending;	Ns_MutexUnlock(&lock);    	/*    	 * Move any queued callbacks to the active table.	 */    	while (cbPtr != NULL) {	    hPtr = Tcl_CreateHashEntry(&table, (char *) cbPtr->sock, &new);	    if (!new) {		ns_free(Tcl_GetHashValue(hPtr));	    }	    Tcl_SetHashValue(hPtr, cbPtr);	    cbPtr = cbPtr->nextPtr;	}	/*	 * Verify and set the poll bits for all active callbacks.	 */	if (max <= table.numEntries) {	    max  = table.numEntries + 100;	    pfds = ns_realloc(pfds, (size_t)max);	}	nfds = 1;	hPtr = Tcl_FirstHashEntry(&table, &search);	while (hPtr != NULL) {	    cbPtr = Tcl_GetHashValue(hPtr);	    if (!(cbPtr->when & NS_SOCK_ANY)) {	    	Tcl_DeleteHashEntry(hPtr);		ns_free(cbPtr);	    } else {		cbPtr->idx = nfds;		pfds[nfds].fd = cbPtr->sock;		pfds[nfds].events = pfds[nfds].revents = 0;        	for (i = 0; i < 3; ++i) {                    if (cbPtr->when & when[i]) {			pfds[nfds].events |= events[i];                    }        	}		++nfds;	    }	    hPtr = Tcl_NextHashEntry(&search);        }    	/*	 * Select on the sockets and drain the trigger pipe if	 * necessary.	 */	if (stop) {	    break;	}	pfds[0].revents = 0;	do {	    n = poll(pfds, (size_t)nfds, -1);	} while (n < 0 && errno == EINTR);	if (n < 0) {	    Ns_Fatal("poll() failed: %s", strerror(errno));	}	if ((pfds[0].revents & POLLIN) && recv(trigPipe[0], &c, 1, 0) != 1) {	    Ns_Fatal("trigger read() failed: %s", strerror(errno));	}    	/*	 * Execute any ready callbacks.	 */	     	hPtr = Tcl_FirstHashEntry(&table, &search);	while (n > 0 && hPtr != NULL) {	    cbPtr = Tcl_GetHashValue(hPtr);            for (i = 0; i < 3; ++i) {                if ((cbPtr->when & when[i])		    && (pfds[cbPtr->idx].revents & events[i])) {                    if (!((*cbPtr->proc)(cbPtr->sock, cbPtr->arg, when[i]))) {			cbPtr->when = 0;		    }                }            }	    hPtr = Tcl_NextHashEntry(&search);        }    }    /*     * Invoke any exit callabacks, cleanup the callback     * system, and signal shutdown complete.     */    Ns_Log(Notice, "socks: shutdown pending");    hPtr = Tcl_FirstHashEntry(&table, &search);    while (hPtr != NULL) {	cbPtr = Tcl_GetHashValue(hPtr);	if (cbPtr->when & NS_SOCK_EXIT) {	    (void) ((*cbPtr->proc)(cbPtr->sock, cbPtr->arg, NS_SOCK_EXIT));	}	hPtr = Tcl_NextHashEntry(&search);    }    hPtr = Tcl_FirstHashEntry(&table, &search);    while (hPtr != NULL) {	ns_free(Tcl_GetHashValue(hPtr));	hPtr = Tcl_NextHashEntry(&search);    }    Tcl_DeleteHashTable(&table);    Ns_Log(Notice, "socks: shutdown complete");    Ns_MutexLock(&lock);    running = 0;    Ns_CondBroadcast(&cond);    Ns_MutexUnlock(&lock);}