andre@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
andre@0: /* This Source Code Form is subject to the terms of the Mozilla Public
andre@0:  * License, v. 2.0. If a copy of the MPL was not distributed with this
andre@0:  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
andre@0: 
andre@0: /*
andre@0:  * This file implements _PR_MD_PR_POLL for Win32.
andre@0:  */
andre@0: 
andre@0: /* The default value of FD_SETSIZE is 64. */
andre@0: #define FD_SETSIZE 1024
andre@0: 
andre@0: #include "primpl.h"
andre@0: 
andre@0: #if !defined(_PR_GLOBAL_THREADS_ONLY)
andre@0: 
andre@0: struct select_data_s {
andre@0:     PRInt32 status;
andre@0:     PRInt32 error;
andre@0:     fd_set *rd, *wt, *ex;
andre@0:     const struct timeval *tv;
andre@0: };
andre@0: 
andre@0: static void
andre@0: _PR_MD_select_thread(void *cdata)
andre@0: {
andre@0:     struct select_data_s *cd = (struct select_data_s *)cdata;
andre@0: 
andre@0:     cd->status = select(0, cd->rd, cd->wt, cd->ex, cd->tv);
andre@0: 
andre@0:     if (cd->status == SOCKET_ERROR) {
andre@0:         cd->error = WSAGetLastError();
andre@0:     }
andre@0: }
andre@0: 
andre@0: int _PR_NTFiberSafeSelect(
andre@0:     int nfds,
andre@0:     fd_set *readfds,
andre@0:     fd_set *writefds,
andre@0:     fd_set *exceptfds,
andre@0:     const struct timeval *timeout)
andre@0: {
andre@0:     PRThread *me = _PR_MD_CURRENT_THREAD();
andre@0:     int ready;
andre@0: 
andre@0:     if (_PR_IS_NATIVE_THREAD(me)) {
andre@0:         ready = _MD_SELECT(nfds, readfds, writefds, exceptfds, timeout);
andre@0:     }
andre@0:     else
andre@0:     {
andre@0:         /*
andre@0:         ** Creating a new thread on each call!!
andre@0:         ** I guess web server doesn't use non-block I/O.
andre@0:         */
andre@0:         PRThread *selectThread;
andre@0:         struct select_data_s data;
andre@0:         data.status = 0;
andre@0:         data.error = 0;
andre@0:         data.rd = readfds;
andre@0:         data.wt = writefds;
andre@0:         data.ex = exceptfds;
andre@0:         data.tv = timeout;
andre@0: 
andre@0:         selectThread = PR_CreateThread(
andre@0:             PR_USER_THREAD, _PR_MD_select_thread, &data,
andre@0:             PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
andre@0:         if (selectThread == NULL) return -1;
andre@0: 
andre@0:         PR_JoinThread(selectThread);
andre@0:         ready = data.status;
andre@0:         if (ready == SOCKET_ERROR) WSASetLastError(data.error);
andre@0:     }
andre@0:     return ready;
andre@0: }
andre@0: 
andre@0: #endif /* !defined(_PR_GLOBAL_THREADS_ONLY) */
andre@0: 
andre@0: PRInt32 _PR_MD_PR_POLL(PRPollDesc *pds, PRIntn npds, PRIntervalTime timeout)
andre@0: {
andre@0:     int ready, err;
andre@0:     fd_set rd, wt, ex;
andre@0:     fd_set *rdp, *wtp, *exp;
andre@0:     int nrd, nwt, nex;
andre@0:     PRFileDesc *bottom;
andre@0:     PRPollDesc *pd, *epd;
andre@0:     PRThread *me = _PR_MD_CURRENT_THREAD();
andre@0: 
andre@0:     struct timeval tv, *tvp = NULL;
andre@0: 
andre@0:     if (_PR_PENDING_INTERRUPT(me))
andre@0:     {
andre@0:         me->flags &= ~_PR_INTERRUPT;
andre@0:         PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
andre@0:         return -1;
andre@0:     }
andre@0: 
andre@0:     /*
andre@0:     ** Is it an empty set? If so, just sleep for the timeout and return
andre@0:     */
andre@0:     if (0 == npds)
andre@0:     {
andre@0:         PR_Sleep(timeout);
andre@0:         return 0;
andre@0:     }
andre@0: 
andre@0:     nrd = nwt = nex = 0;
andre@0:     FD_ZERO(&rd);
andre@0:     FD_ZERO(&wt);
andre@0:     FD_ZERO(&ex);
andre@0: 
andre@0:     ready = 0;
andre@0:     for (pd = pds, epd = pd + npds; pd < epd; pd++)
andre@0:     {
andre@0:         SOCKET osfd;
andre@0:         PRInt16 in_flags_read = 0, in_flags_write = 0;
andre@0:         PRInt16 out_flags_read = 0, out_flags_write = 0;
andre@0: 
andre@0:         if ((NULL != pd->fd) && (0 != pd->in_flags))
andre@0:         {
andre@0:             if (pd->in_flags & PR_POLL_READ)
andre@0:             {
andre@0:                 in_flags_read = (pd->fd->methods->poll)(
andre@0:                     pd->fd, (PRInt16)(pd->in_flags & ~PR_POLL_WRITE),
andre@0:                     &out_flags_read);
andre@0:             }
andre@0:             if (pd->in_flags & PR_POLL_WRITE)
andre@0:             {
andre@0:                 in_flags_write = (pd->fd->methods->poll)(
andre@0:                     pd->fd, (PRInt16)(pd->in_flags & ~PR_POLL_READ),
andre@0:                     &out_flags_write);
andre@0:             }
andre@0:             if ((0 != (in_flags_read & out_flags_read))
andre@0:             || (0 != (in_flags_write & out_flags_write)))
andre@0:             {
andre@0:                 /* this one's ready right now (buffered input) */
andre@0:                 if (0 == ready)
andre@0:                 {
andre@0:                     /*
andre@0:                      * We will have to return without calling the
andre@0:                      * system poll/select function.  So zero the
andre@0:                      * out_flags fields of all the poll descriptors
andre@0:                      * before this one.
andre@0:                      */
andre@0:                     PRPollDesc *prev;
andre@0:                     for (prev = pds; prev < pd; prev++)
andre@0:                     {
andre@0:                         prev->out_flags = 0;
andre@0:                     }
andre@0:                 }
andre@0:                 ready += 1;
andre@0:                 pd->out_flags = out_flags_read | out_flags_write;
andre@0:             }
andre@0:             else
andre@0:             {
andre@0:                 pd->out_flags = 0;  /* pre-condition */
andre@0:                 /* make sure this is an NSPR supported stack */
andre@0:                 bottom = PR_GetIdentitiesLayer(pd->fd, PR_NSPR_IO_LAYER);
andre@0:                 PR_ASSERT(NULL != bottom);  /* what to do about that? */
andre@0:                 if ((NULL != bottom)
andre@0:                 && (_PR_FILEDESC_OPEN == bottom->secret->state))
andre@0:                 {
andre@0:                     if (0 == ready)
andre@0:                     {
andre@0:                         osfd = (SOCKET) bottom->secret->md.osfd;
andre@0:                         if (in_flags_read & PR_POLL_READ)
andre@0:                         {
andre@0:                             pd->out_flags |= _PR_POLL_READ_SYS_READ;
andre@0:                             FD_SET(osfd, &rd);
andre@0:                             nrd++;
andre@0:                         }
andre@0:                         if (in_flags_read & PR_POLL_WRITE)
andre@0:                         {
andre@0:                             pd->out_flags |= _PR_POLL_READ_SYS_WRITE;
andre@0:                             FD_SET(osfd, &wt);
andre@0:                             nwt++;
andre@0:                         }
andre@0:                         if (in_flags_write & PR_POLL_READ)
andre@0:                         {
andre@0:                             pd->out_flags |= _PR_POLL_WRITE_SYS_READ;
andre@0:                             FD_SET(osfd, &rd);
andre@0:                             nrd++;
andre@0:                         }
andre@0:                         if (in_flags_write & PR_POLL_WRITE)
andre@0:                         {
andre@0:                             pd->out_flags |= _PR_POLL_WRITE_SYS_WRITE;
andre@0:                             FD_SET(osfd, &wt);
andre@0:                             nwt++;
andre@0:                         }
andre@0:                         if (pd->in_flags & PR_POLL_EXCEPT) {
andre@0:                             FD_SET(osfd, &ex);
andre@0:                             nex++;
andre@0:                         }
andre@0:                     }
andre@0:                 }
andre@0:                 else
andre@0:                 {
andre@0:                     if (0 == ready)
andre@0:                     {
andre@0:                         PRPollDesc *prev;
andre@0:                         for (prev = pds; prev < pd; prev++)
andre@0:                         {
andre@0:                             prev->out_flags = 0;
andre@0:                         }
andre@0:                     }
andre@0:                     ready += 1;  /* this will cause an abrupt return */
andre@0:                     pd->out_flags = PR_POLL_NVAL;  /* bogii */
andre@0:                 }
andre@0:             }
andre@0:         }
andre@0:         else
andre@0:         {
andre@0:             pd->out_flags = 0;
andre@0:         }
andre@0:     }
andre@0: 
andre@0:     if (0 != ready) return ready;  /* no need to block */
andre@0: 
andre@0:     /*
andre@0:      * FD_SET does nothing if the fd_set's internal fd_array is full.  If
andre@0:      * nrd, nwt, or nex is greater than FD_SETSIZE, we know FD_SET must
andre@0:      * have failed to insert an osfd into the corresponding fd_set, and
andre@0:      * therefore we should fail.
andre@0:      */
andre@0:     if ((nrd > FD_SETSIZE) || (nwt > FD_SETSIZE) || (nex > FD_SETSIZE)) {
andre@0:         PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
andre@0:         return -1;
andre@0:     }
andre@0: 
andre@0:     rdp = (0 == nrd) ? NULL : &rd;
andre@0:     wtp = (0 == nwt) ? NULL : &wt;
andre@0:     exp = (0 == nex) ? NULL : &ex;
andre@0: 
andre@0:     if ((NULL == rdp) && (NULL == wtp) && (NULL == exp)) {
andre@0:         PR_Sleep(timeout);
andre@0:         return 0;
andre@0:     }
andre@0: 
andre@0:     if (timeout != PR_INTERVAL_NO_TIMEOUT)
andre@0:     {
andre@0:         PRInt32 ticksPerSecond = PR_TicksPerSecond();
andre@0:         tv.tv_sec = timeout / ticksPerSecond;
andre@0:         tv.tv_usec = PR_IntervalToMicroseconds( timeout % ticksPerSecond );
andre@0:         tvp = &tv;
andre@0:     }
andre@0: 
andre@0: #if defined(_PR_GLOBAL_THREADS_ONLY)
andre@0:     ready = _MD_SELECT(0, rdp, wtp, exp, tvp);
andre@0: #else
andre@0:     ready = _PR_NTFiberSafeSelect(0, rdp, wtp, exp, tvp);
andre@0: #endif
andre@0: 
andre@0:     /*
andre@0:     ** Now to unravel the select sets back into the client's poll
andre@0:     ** descriptor list. Is this possibly an area for pissing away
andre@0:     ** a few cycles or what?
andre@0:     */
andre@0:     if (ready > 0)
andre@0:     {
andre@0:         ready = 0;
andre@0:         for (pd = pds, epd = pd + npds; pd < epd; pd++)
andre@0:         {
andre@0:             PRInt16 out_flags = 0;
andre@0:             if ((NULL != pd->fd) && (0 != pd->in_flags))
andre@0:             {
andre@0:                 SOCKET osfd;
andre@0:                 bottom = PR_GetIdentitiesLayer(pd->fd, PR_NSPR_IO_LAYER);
andre@0:                 PR_ASSERT(NULL != bottom);
andre@0: 
andre@0:                 osfd = (SOCKET) bottom->secret->md.osfd;
andre@0: 
andre@0:                 if (FD_ISSET(osfd, &rd))
andre@0:                 {
andre@0:                     if (pd->out_flags & _PR_POLL_READ_SYS_READ)
andre@0:                         out_flags |= PR_POLL_READ;
andre@0:                     if (pd->out_flags & _PR_POLL_WRITE_SYS_READ)
andre@0:                         out_flags |= PR_POLL_WRITE;
andre@0:                 } 
andre@0:                 if (FD_ISSET(osfd, &wt))
andre@0:                 {
andre@0:                     if (pd->out_flags & _PR_POLL_READ_SYS_WRITE)
andre@0:                         out_flags |= PR_POLL_READ;
andre@0:                     if (pd->out_flags & _PR_POLL_WRITE_SYS_WRITE)
andre@0:                         out_flags |= PR_POLL_WRITE;
andre@0:                 } 
andre@0:                 if (FD_ISSET(osfd, &ex)) out_flags |= PR_POLL_EXCEPT;
andre@0:             }
andre@0:             pd->out_flags = out_flags;
andre@0:             if (out_flags) ready++;
andre@0:         }
andre@0:         PR_ASSERT(ready > 0);
andre@0:     }
andre@0:     else if (ready == SOCKET_ERROR)
andre@0:     {
andre@0:         err = WSAGetLastError();
andre@0:         if (err == WSAENOTSOCK)
andre@0:         {
andre@0:             /* Find the bad fds */
andre@0:             int optval;
andre@0:             int optlen = sizeof(optval);
andre@0:             ready = 0;
andre@0:             for (pd = pds, epd = pd + npds; pd < epd; pd++)
andre@0:             {
andre@0:                 pd->out_flags = 0;
andre@0:                 if ((NULL != pd->fd) && (0 != pd->in_flags))
andre@0:                 {
andre@0:                     bottom = PR_GetIdentitiesLayer(pd->fd, PR_NSPR_IO_LAYER);
andre@0:                     if (getsockopt(bottom->secret->md.osfd, SOL_SOCKET,
andre@0:                         SO_TYPE, (char *) &optval, &optlen) == -1)
andre@0:                     {
andre@0:                         PR_ASSERT(WSAGetLastError() == WSAENOTSOCK);
andre@0:                         if (WSAGetLastError() == WSAENOTSOCK)
andre@0:                         {
andre@0:                             pd->out_flags = PR_POLL_NVAL;
andre@0:                             ready++;
andre@0:                         }
andre@0:                     }
andre@0:                 }
andre@0:             }
andre@0:             PR_ASSERT(ready > 0);
andre@0:         }
andre@0:         else _PR_MD_MAP_SELECT_ERROR(err);
andre@0:     }
andre@0: 
andre@0:     return ready;
andre@0: }