text: /*
   * %CopyrightBegin%
   *
   * Copyright Ericsson AB 2006-2020. All Rights Reserved.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * %CopyrightEnd%
   */
  
  /**
   * @description Poll interface suitable for ERTS
   *
   *		The interface is currently implemented using:
   *		- select
   *		- poll
   *              - /dev/poll
   *              - epoll with poll or select as fallback
   *              - kqueue with poll or select as fallback
   *
   *
   * @author Rickard Green
   * @author Lukas Larsson
   *
   * There are two major different implementations off IO polling in this
   * file. The concurrent and non-concurrent implementations.
   * When available epoll/kqueue are used to implement the concurrent
   * versions. poll, select and dev/poll use non-concurrent updates.
   *
   * Concurrent version:
   *   In the concurrent version erts_poll_control directly modifies
   *   the kernel pollset without waking the thread that is waiting
   *   on events. Also the ErtsPollResFd type is directly mapped to
   *   the native event type, so no extra copying is needed. Note that
   *   as no locking at all is done, fds can be triggered that have been
   *   removed from the pollset. The check_io layer has to deal with this.
   *
   * Non-concurrent version:
   *   In the non-concurrent version, the pollset has an internal representation
   *   of the pollset that is updated by erts_poll_control. When an fd is updated,
   *   its number is placed in the update request queue and then the waiting thread
   *   is woken in order to see the change. The internal data in the pollset is
   *   protected by a mutex that has to be taken by both the modifying and waiting
   *   thread at different times.
   *
   *   The non-concurrent pollset cannot have fd's closed in it while a thread is
   *   waiting on that fd. In order to fix this, when an ERTS_POLL_OP_DEL command
   *   is issued, the fd is marked as closing and the waiting thread is woken. The
   *   fd is then returned in the waiting threads results as ERTS_POLL_EV_NONE.
   *
   */
  
  #ifdef HAVE_CONFIG_H
  #  include "config.h"
  #endif
  
  #if defined(__DARWIN__) || defined(__APPLE__) && defined(__MACH__)
  /* Setting _DARWIN_UNLIMITED_SELECT before including sys/select.h enables
   * the version of select() that does not place a limit on the fd_set.
   */
  #  define _DARWIN_UNLIMITED_SELECT
  #endif
  
  /*
   * According to posix, select() implementations should
   * support a max timeout value of at least 31 days.
   */
  #define ERTS_SELECT_MAX_TV_SEC__ (31*24*60*60-1)
  
  #ifndef WANT_NONBLOCKING
  #  define WANT_NONBLOCKING
  #endif
  
  #include "erl_thr_progress.h"
  #include "erl_poll.h"
  #if ERTS_POLL_USE_KQUEUE
  #  include <sys/types.h>
  #  include <sys/event.h>
  #  include <sys/time.h>
  #endif
  #if ERTS_POLL_USE_SELECT
  #  ifdef SYS_SELECT_H
  #    include <sys/select.h>
  #  endif
  #elif defined(_DARWIN_UNLIMITED_SELECT)
  #  undef _DARWIN_UNLIMITED_SELECT
  #endif
  #ifdef NO_SYSCONF
  #  if ERTS_POLL_USE_SELECT
  #    include <sys/param.h>
  #  else
  #    include <limits.h>
  #  endif
  #endif
  #include "erl_driver.h"
  #include "erl_alloc.h"
  #include "erl_msacc.h"
  #include "erl_misc_utils.h"
  
  #if !defined(ERTS_POLL_USE_EPOLL) \
      && !defined(ERTS_POLL_USE_DEVPOLL)  \
      && !defined(ERTS_POLL_USE_POLL) \
      && !defined(ERTS_POLL_USE_SELECT)
  #error "Missing implementation of erts_poll()"
  #endif
  
  #if 0
  #define ERTS_POLL_DEBUG_PRINT 1
  
  #define DEBUG_PRINT(FMT, PS, ...)                                       \
      do {                                                                \
          int myerrno = errno;                                            \
          erts_printf("%d: " FMT "\r\n", (PS)->id, ##__VA_ARGS__);        \
          errno = myerrno;                                                \
      } while(0)
  
  /* Define to print info about modifications done to each fd */
  #define DEBUG_PRINT_FD(FMT, PS, FD, ...) DEBUG_PRINT("%d: " FMT, PS, FD, ##__VA_ARGS__)
  /* Define to print entry and exit from erts_poll_wait (can be very spammy) */
  // #define DEBUG_PRINT_WAIT(FMT, PS, ...) DEBUG_PRINT(FMT, PS, ##__VA_ARGS__)
  // #define DEBUG_PRINT_WAIT(FMT, PS, ...) do { if ((PS)->id != -1) DEBUG_PRINT(FMT, PS, ##__VA_ARGS__); } while(0)
  
  #else
  #define ERTS_POLL_DEBUG_PRINT 0
  #define DEBUG_PRINT(...)
  #endif
  
  #ifndef DEBUG_PRINT_FD
  #define DEBUG_PRINT_FD(...)
  #endif
  #ifndef DEBUG_PRINT_WAIT
  #define DEBUG_PRINT_WAIT(...)
  #endif
  
  
  #if defined(_DARWIN_UNLIMITED_SELECT) && ERTS_POLL_USE_SELECT
  typedef struct {
      size_t sz;
      fd_set* ptr;
  }ERTS_fd_set;
  
  #  define ERTS_FD_ZERO(fds)	memset((fds)->ptr, 0, (fds)->sz)
  #  define ERTS_FD_SIZE(n)	((((n)+NFDBITS-1)/NFDBITS)*sizeof(fd_mask))
  
  static ERTS_INLINE void ERTS_FD_CLR(int fd, ERTS_fd_set *fds)
  {
      ASSERT(ERTS_FD_SIZE(fd+1) <= fds->sz);
      FD_CLR(fd, fds->ptr);
  }
  static ERTS_INLINE void ERTS_FD_SET(int fd, ERTS_fd_set *fds)
  {
      ASSERT(ERTS_FD_SIZE(fd+1) <= fds->sz);
      FD_SET(fd, fds->ptr);
  }
  static ERTS_INLINE int ERTS_FD_ISSET(int fd, ERTS_fd_set *fds)
  {
      ASSERT(ERTS_FD_SIZE(fd+1) <= fds->sz);
      return FD_ISSET(fd, fds->ptr);
  }
  
  static void ERTS_FD_COPY(ERTS_fd_set *src, ERTS_fd_set *dst)
  {
      if (dst->sz != src->sz) {
  	dst->ptr = dst->ptr
  	    ? erts_realloc(ERTS_ALC_T_SELECT_FDS, dst->ptr, src->sz)
  	    : erts_alloc(ERTS_ALC_T_SELECT_FDS, src->sz);
  	dst->sz = src->sz;
      }
      memcpy(dst->ptr, src->ptr, src->sz);
  }
  
  static ERTS_INLINE
  int ERTS_SELECT(int nfds, ERTS_fd_set *readfds, ERTS_fd_set *writefds,
  		ERTS_fd_set *exceptfds, struct timeval *timeout)
  {
      ASSERT(!readfds || readfds->sz >= ERTS_FD_SIZE(nfds));
      ASSERT(!writefds || writefds->sz >= ERTS_FD_SIZE(nfds));
      ASSERT(!exceptfds);
      return select(nfds, 
  		  (readfds ? readfds->ptr : NULL ),
  		  (writefds ? writefds->ptr : NULL),
  		  NULL,
  		  timeout);
  }
  
  #else /* !_DARWIN_UNLIMITED_SELECT */
  #  define ERTS_fd_set	fd_set
  #  define ERTS_FD_CLR	FD_CLR
  #  define ERTS_FD_ISSET FD_ISSET
  #  define ERTS_FD_SET	FD_SET
  #  define ERTS_FD_ZERO	FD_ZERO
  #  define ERTS_FD_COPY(src,dst) (*(dst) = *(src))
  #  define ERTS_SELECT	select
  #endif
  
  #define ERTS_POLL_IS_FALLBACK (ERTS_POLL_USE_POLL || ERTS_POLL_USE_SELECT) && ERTS_ENABLE_KERNEL_POLL
  
  #define ERTS_POLL_USE_CONCURRENT_UPDATE (ERTS_POLL_USE_EPOLL || ERTS_POLL_USE_KQUEUE)
  
  #define ERTS_POLL_USE_WAKEUP(ps) (!ERTS_POLL_USE_CONCURRENT_UPDATE || (ps)->id < 0)
  
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
  
  #define ERTS_POLLSET_SET_HAVE_UPDATE_REQUESTS(PS) \
    erts_atomic32_set_nob(&(PS)->have_update_requests, (erts_aint32_t) 1)
  #define ERTS_POLLSET_UNSET_HAVE_UPDATE_REQUESTS(PS) \
    erts_atomic32_set_nob(&(PS)->have_update_requests, (erts_aint32_t) 0)
  #define ERTS_POLLSET_HAVE_UPDATE_REQUESTS(PS) \
    ((int) erts_atomic32_read_nob(&(PS)->have_update_requests))
  
  #define ERTS_POLLSET_LOCK(PS) \
    erts_mtx_lock(&(PS)->mtx)
  #define ERTS_POLLSET_UNLOCK(PS) \
    erts_mtx_unlock(&(PS)->mtx)
  
  #else
  
  #define ERTS_POLLSET_SET_HAVE_UPDATE_REQUESTS(PS) do {} while(0)
  #define ERTS_POLLSET_UNSET_HAVE_UPDATE_REQUESTS(PS) do {} while(0)
  #define ERTS_POLLSET_HAVE_UPDATE_REQUESTS(PS) 0
  
  #define ERTS_POLLSET_LOCK(PS) do {} while(0)
  #define ERTS_POLLSET_UNLOCK(PS) do {} while(0)
  
  #endif
  
  /*
   * --- Data types ------------------------------------------------------------
   */
  
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
  
  #define ERTS_POLLSET_UPDATE_REQ_BLOCK_SIZE 128
  
  typedef struct ErtsPollSetUpdateRequestsBlock_ ErtsPollSetUpdateRequestsBlock;
  struct ErtsPollSetUpdateRequestsBlock_ {
      ErtsPollSetUpdateRequestsBlock *next;
      int len;
      int fds[ERTS_POLLSET_UPDATE_REQ_BLOCK_SIZE];
  };
  
  #  define ERTS_POLL_FD_FLG_INURQ	(((unsigned short) 1) << 0)
  #  define ERTS_POLL_FD_FLG_RST		(((unsigned short) 1) << 1)
  
  typedef struct {
  #if ERTS_POLL_USE_POLL
      int pix;
  #endif
  
      ErtsPollEvents used_events;
      ErtsPollEvents events;
      unsigned short flags;
  
  } ErtsFdStatus;
  
  #endif
  
  /*
   * This struct is not really exported, but it's nice to
   * get unique names in debugger for kp/nkp
   */
  struct ERTS_POLL_EXPORT(erts_pollset) {
      int id;
      int internal_fd_limit;
      erts_atomic_t no_of_user_fds;
  
  #if ERTS_POLL_USE_KERNEL_POLL
      int kp_fd;
      int oneshot;
  #endif /* ERTS_POLL_USE_KERNEL_POLL */
  
  #if ERTS_POLL_USE_POLL
      int next_poll_fds_ix;
      int no_poll_fds;
      int poll_fds_len;
      struct pollfd *poll_fds;
  #elif ERTS_POLL_USE_SELECT
      int next_sel_fd;
      int max_fd;
      ERTS_fd_set input_fds;
      ERTS_fd_set res_input_fds;
      ERTS_fd_set output_fds;
      ERTS_fd_set res_output_fds;
  #elif ERTS_POLL_USE_DEVPOLL
      struct pollfd *poll_fds;
      int poll_fds_ix;
  #endif
  
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
      ErtsFdStatus *fds_status;
      int fds_status_len;
      ErtsPollSetUpdateRequestsBlock update_requests;
      ErtsPollSetUpdateRequestsBlock *curr_upd_req_block;
      erts_atomic32_t have_update_requests;
      erts_mtx_t mtx;
  #else
      int do_wakeup;
  #endif
  
  #if ERTS_POLL_USE_TIMERFD
      int timer_fd;
  #endif
      ErtsMonotonicTime timeout_time;
      erts_atomic32_t wakeup_state;
      int wake_fds[2];
  };
  
  void erts_silence_warn_unused_result(long unused);
  static void fatal_error(char *format, ...);
  
  static int max_fds = -1;
  
  #if ERTS_POLL_USE_POLL
  
  #if !ERTS_POLL_IS_FALLBACK
  static ERTS_INLINE short ev2pollev(ErtsPollEvents ev)
  {
      return ERTS_POLL_EV_E2N(ev);
  }
  
  static ERTS_INLINE ErtsPollEvents pollev2ev(short ev)
  {
      return ERTS_POLL_EV_N2E(ev);
  }
  
  #else /* ERTS_POLL_IS_FALLBACK */
  
  static ERTS_INLINE short
  ev2pollev(ErtsPollEvents ev)
  {
      short res_ev = (short) 0;
      if (ev & ERTS_POLL_EV_IN)
         res_ev |= ERTS_POLL_EV_NKP_IN;
      if (ev & ERTS_POLL_EV_OUT)
         res_ev |= ERTS_POLL_EV_NKP_OUT;
      return res_ev;
  }
  
  static ERTS_INLINE ErtsPollEvents
  pollev2ev(short ev)
  {
      ErtsPollEvents res_ev = (ErtsPollEvents) 0;
      if (ev & ERTS_POLL_EV_NKP_IN)
         res_ev |= ERTS_POLL_EV_IN;
      if (ev & ERTS_POLL_EV_NKP_OUT)
         res_ev |= ERTS_POLL_EV_OUT;
      if (ev & ERTS_POLL_EV_NKP_ERR)
         res_ev |= ERTS_POLL_EV_ERR;
      if (ev & ERTS_POLL_EV_NKP_NVAL)
         res_ev |= ERTS_POLL_EV_NVAL;
     return res_ev;
  }
  
  #endif /* !ERTS_POLL_IS_FALLBACK */
  
  #endif /* ERTS_POLL_USE_POLL */
  
  
  #ifdef HARD_DEBUG
  static void check_poll_result(ErtsPollResFd pr[], int len);
  #endif /* HARD_DEBUG */
  #if ERTS_POLL_USE_DEVPOLL && defined(DEBUG)
  static void check_poll_status(ErtsPollSet *ps);
  #endif /* ERTS_POLL_USE_DEVPOLL && DEBUG */
  static void print_misc_debug_info(void);
  #if ERTS_POLL_USE_EPOLL
  uint32_t epoll_events(int kp_fd, int fd);
  #endif
  
  #define ERTS_POLL_NOT_WOKEN	0
  #define ERTS_POLL_WOKEN		-1
  #define ERTS_POLL_WOKEN_INTR	1
  #define ERTS_POLL_WSTATE_UNUSED ~0
  
  static ERTS_INLINE void
  reset_wakeup_state(ErtsPollSet *ps)
  {
      erts_atomic32_set_mb(&ps->wakeup_state, ERTS_POLL_NOT_WOKEN);
  }
  
  static ERTS_INLINE int
  is_woken(ErtsPollSet *ps)
  {
      if (!ERTS_POLL_USE_WAKEUP(ps))
          return 0;
      return erts_atomic32_read_acqb(&ps->wakeup_state) != ERTS_POLL_NOT_WOKEN;
  }
  
  static ERTS_INLINE int
  is_interrupted_reset(ErtsPollSet *ps)
  {
      if (!ERTS_POLL_USE_WAKEUP(ps))
          return 0;
      return (erts_atomic32_xchg_acqb(&ps->wakeup_state, ERTS_POLL_NOT_WOKEN)
  	    == ERTS_POLL_WOKEN_INTR);
  }
  
  static ERTS_INLINE void
  woke_up(ErtsPollSet *ps)
  {
      erts_aint32_t wakeup_state;
      if (!ERTS_POLL_USE_WAKEUP(ps))
          return;
      wakeup_state = erts_atomic32_read_acqb(&ps->wakeup_state);
      if (wakeup_state == ERTS_POLL_NOT_WOKEN)
  	(void) erts_atomic32_cmpxchg_nob(&ps->wakeup_state,
  					 ERTS_POLL_WOKEN,
  					 ERTS_POLL_NOT_WOKEN);
      ASSERT(erts_atomic32_read_nob(&ps->wakeup_state) != ERTS_POLL_NOT_WOKEN);
  }
  
  /*
   * --- Wakeup pipe -----------------------------------------------------------
   */
  
  static ERTS_INLINE void
  wake_poller(ErtsPollSet *ps, int interrupted)
  {
      int wake;
      erts_aint32_t wakeup_state;
      if (!interrupted)
          wakeup_state = erts_atomic32_cmpxchg_relb(&ps->wakeup_state,
                                                    ERTS_POLL_WOKEN,
                                                    ERTS_POLL_NOT_WOKEN);
      else
          wakeup_state = erts_atomic32_xchg_relb(&ps->wakeup_state,
                                                 ERTS_POLL_WOKEN_INTR);
      wake = wakeup_state == ERTS_POLL_NOT_WOKEN;
  
      if (wake)
      {
  	ssize_t res;
          DEBUG_PRINT_WAIT("wake_poller(%d)", ps, interrupted);
  	if (ps->wake_fds[1] < 0)
  	    return; /* Not initialized yet */
  	do {
  	    /* write() is async-signal safe (according to posix) */
  	    res = write(ps->wake_fds[1], "!", 1);
  	} while (res < 0 && errno == EINTR);
  	if (res <= 0 && errno != ERRNO_BLOCK) {
              fatal_error("%s:%d:wake_poller(): "
                          "Failed to write to wakeup pipe fd=%d: "
                          "%s (%d)\n",
                          __FILE__, __LINE__,
                          ps->wake_fds[1],
                          erl_errno_id(errno), errno);
  	}
      }
  }
  
  static ERTS_INLINE void
  cleanup_wakeup_pipe(ErtsPollSet *ps)
  {
      int intr = 0;
      int fd = ps->wake_fds[0];
      int res;
      ASSERT(ERTS_POLL_USE_WAKEUP(ps));
      do {
  	char buf[32];
  	res = read(fd, buf, sizeof(buf));
  	if (res > 0)
  	    intr = 1;
      } while (res > 0 || (res < 0 && errno == EINTR));
      if (res < 0 && errno != ERRNO_BLOCK) {
  	fatal_error("%s:%d:cleanup_wakeup_pipe(): "
  		    "Failed to read on wakeup pipe fd=%d: "
  		    "%s (%d)\n",
  		    __FILE__, __LINE__,
  		    fd,
  		    erl_errno_id(errno), errno);
      }
      if (intr)
  	erts_atomic32_set_nob(&ps->wakeup_state, ERTS_POLL_WOKEN_INTR);
  }
  
  static void
  create_wakeup_pipe(ErtsPollSet *ps)
  {
      int do_wake = 0;
      int wake_fds[2];
      ps->wake_fds[0] = -1;
      ps->wake_fds[1] = -1;
      if (!ERTS_POLL_USE_WAKEUP(ps)) {
          erts_atomic32_init_nob(&ps->wakeup_state,
                                 (erts_aint32_t) ERTS_POLL_WSTATE_UNUSED);
          return;
      }
      erts_atomic32_init_nob(&ps->wakeup_state,
                             (erts_aint32_t) ERTS_POLL_NOT_WOKEN);
      if (pipe(wake_fds) < 0) {
  	fatal_error("%s:%d:create_wakeup_pipe(): "
  		    "Failed to create pipe: %s (%d)\n",
  		    __FILE__,
  		    __LINE__,
  		    erl_errno_id(errno),
  		    errno);
      }
  
      SET_NONBLOCKING(wake_fds[0]);
      SET_NONBLOCKING(wake_fds[1]);
  
      DEBUG_PRINT("wakeup fds = {%d, %d}", ps, wake_fds[0], wake_fds[1]);
  
      ERTS_POLL_EXPORT(erts_poll_control)(ps,
  					wake_fds[0],
                                          ERTS_POLL_OP_ADD,
  					ERTS_POLL_EV_IN,
                                          &do_wake);
      if (ps->internal_fd_limit <= wake_fds[1])
  	ps->internal_fd_limit = wake_fds[1] + 1;
      if (ps->internal_fd_limit <= wake_fds[0])
  	ps->internal_fd_limit = wake_fds[0] + 1;
      ps->wake_fds[0] = wake_fds[0];
      ps->wake_fds[1] = wake_fds[1];
  }
  
  /*
   * --- timer fd -----------------------------------------------------------
   */
  
  #if ERTS_POLL_USE_TIMERFD
  
  /* We use the timerfd when using epoll_wait to get high accuracy
     timeouts, i.e. we want to sleep with < ms accuracy. */
  
  static void
  create_timerfd(ErtsPollSet *ps)
  {
      int do_wake = 0;
      int timer_fd = timerfd_create(CLOCK_MONOTONIC,0);
      ERTS_POLL_EXPORT(erts_poll_control)(ps,
  					timer_fd,
                                          ERTS_POLL_OP_ADD,
  					ERTS_POLL_EV_IN,
                                          &do_wake);
      if (ps->internal_fd_limit <= timer_fd)
  	ps->internal_fd_limit = timer_fd + 1;
      ps->timer_fd = timer_fd;
  }
  
  static ERTS_INLINE void
  timerfd_set(ErtsPollSet *ps, struct itimerspec *its)
  {
  #ifdef DEBUG
      struct itimerspec old_its;
      int res;
      res = timerfd_settime(ps->timer_fd, 0, its, &old_its);
      ASSERT(res == 0);
      ASSERT(old_its.it_interval.tv_sec == 0 &&
             old_its.it_interval.tv_nsec == 0 &&
             old_its.it_value.tv_sec == 0 &&
             old_its.it_value.tv_nsec == 0);
  
  #else
      timerfd_settime(ps->timer_fd, 0, its, NULL);
  #endif
  }
  
  static ERTS_INLINE int
  timerfd_clear(ErtsPollSet *ps, ErtsPollResFd pr[], int res, int max_res) {
  
      struct itimerspec its;
      /* we always have to clear the timer */
      its.it_interval.tv_sec = 0;
      its.it_interval.tv_nsec = 0;
      its.it_value.tv_sec = 0;
      its.it_value.tv_nsec = 0;
      timerfd_settime(ps->timer_fd, 0, &its, NULL);
  
      /* only timeout fd triggered */
      if (res == 1 && pr[0].data.fd == ps->timer_fd)
          return 0;
  
      return res;
  }
  
  #endif /* ERTS_POLL_USE_TIMERFD */
  
  /*
   * --- Poll set update requests ----------------------------------------------
   */
  
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
  
  static ERTS_INLINE void
  enqueue_update_request(ErtsPollSet *ps, int fd)
  {
      ErtsPollSetUpdateRequestsBlock *urqbp;
  
      ASSERT(fd < ps->fds_status_len);
  
      if (ps->fds_status[fd].flags & ERTS_POLL_FD_FLG_INURQ)
  	return;
  
      if (ps->update_requests.len == 0)
  	ERTS_POLLSET_SET_HAVE_UPDATE_REQUESTS(ps);
  
      urqbp = ps->curr_upd_req_block;
  
      if (urqbp->len == ERTS_POLLSET_UPDATE_REQ_BLOCK_SIZE) {
  	urqbp = erts_alloc(ERTS_ALC_T_POLLSET_UPDREQ,
  			   sizeof(ErtsPollSetUpdateRequestsBlock));
  	urqbp->next = ps->curr_upd_req_block;
  	urqbp->len = 0;
  	ps->curr_upd_req_block = urqbp;
      }
  
      ps->fds_status[fd].flags |= ERTS_POLL_FD_FLG_INURQ;
      urqbp->fds[urqbp->len++] = fd;
  }
  
  static ERTS_INLINE void
  free_update_requests_block(ErtsPollSet *ps,
  			   ErtsPollSetUpdateRequestsBlock *urqbp)
  {
      if (urqbp != &ps->update_requests)
  	erts_free(ERTS_ALC_T_POLLSET_UPDREQ, (void *) urqbp);
      else {
  	urqbp->len = 0;
      }
  }
  
  #endif /* !ERTS_POLL_USE_CONCURRENT_UPDATE */
  
  /*
   * --- Growing poll set structures -------------------------------------------
   */
  
  #if !ERTS_NO_KERNEL_POLL_VERSION || !ERTS_ENABLE_KERNEL_POLL
  /* only one shared implementation */
  
  #define ERTS_FD_TABLE_MIN_LENGTH	1024
  #define ERTS_FD_TABLE_EXP_THRESHOLD	(2048*1024)
  
  int erts_poll_new_table_len(int old_len, int need_len)
  {
      int new_len;
  
      ASSERT(need_len > old_len);
      if (need_len < ERTS_FD_TABLE_MIN_LENGTH) {
  	new_len = ERTS_FD_TABLE_MIN_LENGTH;
      }
      else {
          new_len = old_len;
          if (new_len < ERTS_FD_TABLE_MIN_LENGTH)
              new_len = ERTS_FD_TABLE_MIN_LENGTH;
          do {
              if (new_len < ERTS_FD_TABLE_EXP_THRESHOLD)
                  new_len *= 2;
              else
                  new_len += ERTS_FD_TABLE_EXP_THRESHOLD;
          } while (new_len < need_len);
      }
      ASSERT(new_len >= need_len);
      return new_len;
  }
  #endif
  
  #if ERTS_POLL_USE_POLL
  static void
  grow_poll_fds(ErtsPollSet *ps, int min_ix)
  {
      int i;
      int new_len = erts_poll_new_table_len(ps->poll_fds_len, min_ix + 1);
      if (new_len > max_fds)
  	new_len = max_fds;
      ps->poll_fds = (ps->poll_fds_len
  		    ? erts_realloc(ERTS_ALC_T_POLL_FDS,
  				   ps->poll_fds,
  				   sizeof(struct pollfd)*new_len)
  		    : erts_alloc(ERTS_ALC_T_POLL_FDS,
  				 sizeof(struct pollfd)*new_len));
      for (i = ps->poll_fds_len; i < new_len; i++) {
  	ps->poll_fds[i].fd = -1;
  	ps->poll_fds[i].events = (short) 0;
  	ps->poll_fds[i].revents = (short) 0;
      }
      ps->poll_fds_len = new_len;
  }
  #endif
  
  #ifdef _DARWIN_UNLIMITED_SELECT
  static void
  grow_select_fds(int fd, ERTS_fd_set* fds)
  {
      int new_len = erts_poll_new_table_len(fds->sz, fd + 1);
      if (new_len > max_fds)
  	new_len = max_fds;
      new_len = ERTS_FD_SIZE(new_len);
      fds->ptr = fds->sz
  	? erts_realloc(ERTS_ALC_T_SELECT_FDS, fds->ptr, new_len)
  	: erts_alloc(ERTS_ALC_T_SELECT_FDS, new_len);
      memset((char*)fds->ptr + fds->sz, 0, new_len - fds->sz);
      fds->sz = new_len;
  }
  static ERTS_INLINE void
  ensure_select_fds(int fd, ERTS_fd_set* in, ERTS_fd_set* out)
  {
      ASSERT(in->sz == out->sz);
      if (ERTS_FD_SIZE(fd+1) > in->sz) {
  	grow_select_fds(fd, in);
  	grow_select_fds(fd, out);
      }
  }
  static ERTS_INLINE int
  check_select_fds(int fd, ERTS_fd_set* in, ERTS_fd_set* out)
  {
      ASSERT(in->sz == out->sz);
      return (ERTS_FD_SIZE(fd+1) <= in->sz);
  }
  #else
  #  define ensure_select_fds(fd, in, out) do {} while(0)
  #  define check_select_fds(fd, in, out) (1)
  #endif /* _DARWIN_UNLIMITED_SELECT */
  
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
  static void
  grow_fds_status(ErtsPollSet *ps, int min_fd)
  {
      int i;
      int new_len = erts_poll_new_table_len(ps->fds_status_len, min_fd + 1);
      ASSERT(min_fd < max_fds);
      if (new_len > max_fds)
  	new_len = max_fds;
      ps->fds_status = (ps->fds_status_len
  		      ? erts_realloc(ERTS_ALC_T_FD_STATUS,
  				     ps->fds_status,
  				     sizeof(ErtsFdStatus)*new_len)
  		      : erts_alloc(ERTS_ALC_T_FD_STATUS,
  				   sizeof(ErtsFdStatus)*new_len));
      for (i = ps->fds_status_len; i < new_len; i++) {
  #if ERTS_POLL_USE_POLL
  	ps->fds_status[i].pix = -1;
  #endif
  	ps->fds_status[i].used_events = (ErtsPollEvents) 0;
  	ps->fds_status[i].events = (ErtsPollEvents) 0;
  	ps->fds_status[i].flags = (unsigned short) 0;
      }
      ps->fds_status_len = new_len;
  }
  #endif
  
  /*
   * --- Selecting fd to poll on -----------------------------------------------
   */
  
  #if ERTS_POLL_USE_EPOLL
  static int
  concurrent_update_pollset(ErtsPollSet *ps, int fd, ErtsPollOp op,
                            ErtsPollEvents events)
  {
      int res;
      int epoll_op = EPOLL_CTL_MOD;
      struct epoll_event epe_templ;
      struct epoll_event epe;
  
      epe_templ.events = ERTS_POLL_EV_E2N(events);
      epe_templ.data.fd = fd;
  
      if (ps->oneshot)
          epe_templ.events |= EPOLLONESHOT;
  
  #ifdef VALGRIND
      /* Silence invalid valgrind warning ... */
      memset((void *) &epe.data, 0, sizeof(epoll_data_t));
  #endif
  
      switch (op) {
      case ERTS_POLL_OP_DEL:
  	/* A note on EPOLL_CTL_DEL: linux kernel versions before 2.6.9
  	   need a non-NULL event pointer even though it is ignored... */
          epoll_op = EPOLL_CTL_DEL;
          epe_templ.events = 0;
  	erts_atomic_dec_nob(&ps->no_of_user_fds);
          break;
      case ERTS_POLL_OP_ADD:
          epoll_op = EPOLL_CTL_ADD;
  	erts_atomic_inc_nob(&ps->no_of_user_fds);
          break;
      case ERTS_POLL_OP_MOD:
          epoll_op = EPOLL_CTL_MOD;
          break;
      default:
          ASSERT(0);
          break;
      }
  
      do {
  	/* We init 'epe' every time since epoll_ctl() may modify it
  	   (not declared const and not documented as const). */
  	epe.events = epe_templ.events;
  	epe.data.fd = epe_templ.data.fd;
  	res = epoll_ctl(ps->kp_fd, epoll_op, fd, &epe);
      } while (res != 0 && errno == EINTR);
  
  #if ERTS_POLL_DEBUG_PRINT
      {
  	int saved_errno = errno;
  	DEBUG_PRINT_FD("%s = epoll_ctl(%d, %s, %d, {0x%x, %d})",
                         ps, fd,
                         res == 0 ? "0" : erl_errno_id(errno),
                         ps->kp_fd,
                         (epoll_op == EPOLL_CTL_ADD
                          ? "EPOLL_CTL_ADD"
                          : (epoll_op == EPOLL_CTL_MOD
                             ? "EPOLL_CTL_MOD"
                             : (epoll_op == EPOLL_CTL_DEL
                                ? "EPOLL_CTL_DEL"
                                : "UNKNOWN"))),
                         fd,
                         epe_templ.events,
                         fd);
  	errno = saved_errno;
      }
  #endif
      if (res != 0) {
  	switch (op) {
  	case ERTS_POLL_OP_MOD:
  	    epe.events = 0;
  	    do {
  		/* We init 'epe' every time since epoll_ctl() may modify it
  		   (not declared const and not documented as const). */
  		epe.events = 0;
  		epe.data.fd = fd;
  		res = epoll_ctl(ps->kp_fd, EPOLL_CTL_DEL, fd, &epe);
  	    } while (res != 0 && errno == EINTR);
  	/* Fall through ... */
  	case ERTS_POLL_OP_ADD: {
  	    erts_atomic_dec_nob(&ps->no_of_user_fds);
              res = ERTS_POLL_EV_NVAL;
  	    break;
  	}
  	case ERTS_POLL_OP_DEL: {
  	    /*
  	     * Since we use a lazy update approach EPOLL_CTL_DEL will
  	     * frequently fail. This since epoll automatically removes
  	     * a filedescriptor that is closed from the poll set.
  	     */
  	    res = 0;
  	    break;
  	}
  	default:
  	    fatal_error("%s:%d:update_pollset(): Internal error\n",
  			__FILE__, __LINE__);
  	    break;
  	}
      } else {
          res = events;
      }
      return res;
  }
  
  #endif /* ERTS_POLL_USE_EPOLL */
  
  #if ERTS_POLL_USE_KQUEUE
  
  /* Some versions of the EV_SET macro used kevp multiple times,
     so we define out own version that make sure that it is safe
     to do kevp++ in the argument list. */
  #define ERTS_EV_SET(kevp, a, b, c, f) do {              \
          struct kevent *kevp_ = kevp;                    \
          EV_SET(kevp_, a, b, c, 0, 0, f);                \
      } while(0)
  
  static int
  concurrent_update_pollset(ErtsPollSet *ps, int fd, ErtsPollOp op,
                            ErtsPollEvents events)
  {
      int res = 0, len = 0;
      struct kevent evts[2];
      struct timespec ts = {0, 0};
      uint32_t oneshot = 0;
  
      if (op == ERTS_POLL_OP_ADD) {
          /* This is a hack to make the "noshell" option work; kqueue can poll
           * these fds but will not report EV_EOF, so we return NVAL to use the
           * fallback instead.
           *
           * This may be common to all pipes but we have no way to tell whether
           * an fd is a pipe or not. */
          switch (fd) {
          case STDIN_FILENO:
          case STDOUT_FILENO:
          case STDERR_FILENO:
              return ERTS_POLL_EV_NVAL;
          default:
              break;
          }
      }
  
  #if defined(EV_DISPATCH) && !(defined(__OpenBSD__) || defined(__NetBSD__))
      /* If we have EV_DISPATCH we use it, unless we are on OpenBSD/NetBSD as the
         behavior of EV_EOF seems to be edge triggered there and we need it
         to be level triggered.
  
         The kevent descriptions for both read and write are added on OP_ADD
         and removed on OP_DEL. And then after than only EV_ENABLE|EV_DISPATCH
         are used.
  
         It could be possible to not modify the pollset when disabling and/or
         deleting events, but that may cause the poll threads to be awoken
         a lot more than they should so we take the cost here instead of
         in the poll thread.
  
         Note: We need to have EV_DISPATCH both when the event is enabled and
         disabled, as otherwise the event may be triggered twice on each re-arm.
         Not sure if this is intended or not (can't find anything about it in the
         man page), but it seems to be the way it works...
      */
  
      if (ps->oneshot)
          oneshot = EV_DISPATCH;
  
      if (op == ERTS_POLL_OP_DEL) {
          erts_atomic_dec_nob(&ps->no_of_user_fds);
          /* We could probably skip this delete, do we want to? */
          ERTS_EV_SET(&evts[len++], fd, EVFILT_READ, EV_DELETE, (void *) 0);
          ERTS_EV_SET(&evts[len++], fd, EVFILT_WRITE, EV_DELETE, (void *) 0);
      } else if (op == ERTS_POLL_OP_ADD) {
          uint32_t flags;
          erts_atomic_inc_nob(&ps->no_of_user_fds);
  
          flags = EV_ADD|oneshot;
          flags |= ((events & ERTS_POLL_EV_IN) ? 0 : EV_DISABLE);
          ERTS_EV_SET(&evts[len++], fd, EVFILT_READ, flags, (void *) ERTS_POLL_EV_IN);
  
          flags = EV_ADD|oneshot;
          flags |= ((events & ERTS_POLL_EV_OUT) ? 0 : EV_DISABLE);
          ERTS_EV_SET(&evts[len++], fd, EVFILT_WRITE, flags, (void *) ERTS_POLL_EV_OUT);
      } else {
          uint32_t flags;
          ASSERT(op == ERTS_POLL_OP_MOD);
  
          flags = oneshot;
          flags |= (events & ERTS_POLL_EV_IN) ? EV_ENABLE : EV_DISABLE;
          ERTS_EV_SET(&evts[len++], fd, EVFILT_READ, flags, (void *) ERTS_POLL_EV_IN);
  
          flags = oneshot;
          flags |= (events & ERTS_POLL_EV_OUT) ? EV_ENABLE : EV_DISABLE;
          ERTS_EV_SET(&evts[len++], fd, EVFILT_WRITE, flags, (void *) ERTS_POLL_EV_OUT);
      }
  #else
      uint32_t flags = EV_ADD|EV_ENABLE;
  
      if (ps->oneshot) flags |= EV_ONESHOT;
  
      if (op == ERTS_POLL_OP_DEL) {
          erts_atomic_dec_nob(&ps->no_of_user_fds);
          /* We don't do anything when a delete is issued. The fds will be removed
             when they are triggered, or when they are closed. */
          if (ps->oneshot)
              events = 0;
          else {
              flags = EV_DELETE;
              events = ERTS_POLL_EV_IN;
          }
      } else if (op == ERTS_POLL_OP_ADD) {
          erts_atomic_inc_nob(&ps->no_of_user_fds);
          /* Only allow EV_IN in non-oneshot poll-sets */
          ASSERT(ps->oneshot || events == ERTS_POLL_EV_IN);
      } else if (!ps->oneshot) {
          ASSERT(op == ERTS_POLL_OP_MOD);
          /* If we are not oneshot and do a mod we should disable the FD.
             We assume that it is only the read side that is active as
             currently only read is selected upon in the non-oneshot
             poll-sets. */
          if (!events)
              flags = EV_DISABLE;
          else
              flags = EV_ENABLE;
          events = ERTS_POLL_EV_IN;
      }
  
      if (events & ERTS_POLL_EV_IN) {
          ERTS_EV_SET(&evts[len++], fd, EVFILT_READ, flags, (void *) ERTS_POLL_EV_IN);
      }
      if (events & ERTS_POLL_EV_OUT) {
          ERTS_EV_SET(&evts[len++], fd, EVFILT_WRITE, flags, (void *) ERTS_POLL_EV_OUT);
      }
  
  #endif
      if (len)
          do {
              res = kevent(ps->kp_fd, evts, len, NULL, 0, &ts);
          } while (res < 0 && errno == EINTR);
  #if ERTS_POLL_DEBUG_PRINT
      {
          int saved_errno = errno, i;
          char keventb[255], *keventbp = keventb;
          if (res < 0)
              keventbp += sprintf(keventbp,"%s = ",erl_errno_id(saved_errno));
          else
              keventbp += sprintf(keventbp,"%d = ",res);
          keventbp += sprintf(keventbp, "kevent(%d, {",ps->kp_fd);
          for (i = 0; i < len; i++) {
              const char *flags = "UNKNOWN";
              if (evts[i].flags == (EV_DELETE)) flags = "EV_DELETE";
              if (evts[i].flags == (EV_ADD)) flags = "EV_ADD";
              if (evts[i].flags == (EV_ADD|EV_ONESHOT)) flags = "EV_ADD|EV_ONESHOT";
              if (evts[i].flags == (EV_ENABLE)) flags = "EV_ENABLE";
              if (evts[i].flags == (EV_DISABLE)) flags = "EV_DISABLE";
              if (evts[i].flags == (EV_ADD|EV_DISABLE)) flags = "EV_ADD|EV_DISABLE";
  #ifdef EV_DISPATCH
              if (evts[i].flags == (EV_ADD|EV_DISPATCH)) flags = "EV_ADD|EV_DISPATCH";
              if (evts[i].flags == (EV_ENABLE|EV_DISPATCH)) flags = "EV_ENABLE|EV_DISPATCH";
              if (evts[i].flags == (EV_DISABLE|EV_DISPATCH)) flags = "EV_DISABLE|EV_DISABLE";
  #endif
  
              keventbp += sprintf(keventbp, "%s{%lu, %s, %s}",i > 0 ? ", " : "",
                                  evts[i].ident,
                                  (evts[i].filter == EVFILT_READ
                                   ? "EVFILT_READ"
                                   : (evts[i].filter == EVFILT_WRITE
                                      ? "EVFILT_WRITE"
                                      : "UNKNOWN")), flags);
          }
          keventbp += sprintf(keventbp, "}, %d)", len);
          DEBUG_PRINT_FD("%s", ps, fd, keventb);
          errno = saved_errno;
      }
  #endif
      if (res < 0) {
          if (op != ERTS_POLL_OP_DEL) {
  #ifdef EV_RECEIPT
              struct kevent receipt_evts[2];
              len = 0;
              ERTS_EV_SET(&evts[len++], fd, EVFILT_WRITE, EV_DELETE|EV_RECEIPT, (void *) 0);
              ERTS_EV_SET(&evts[len++], fd, EVFILT_READ, EV_DELETE|EV_RECEIPT, (void *) 0);
              do {
                  res = kevent(ps->kp_fd, evts, len, receipt_evts, 2, &ts);
              } while (res < 0 && errno == EINTR);
  #else
              ERTS_EV_SET(&evts[0], fd, EVFILT_WRITE, EV_DELETE, (void *) 0);
              do {
                  res = kevent(ps->kp_fd, evts, 1, NULL, 0, &ts);
              } while (res < 0 && errno == EINTR);
              ERTS_EV_SET(&evts[0], fd, EVFILT_READ, EV_DELETE, (void *) 0);
              do {
                  res = kevent(ps->kp_fd, evts, 1, NULL, 0, &ts);
              } while (res < 0 && errno == EINTR);
  #endif
              if (op == ERTS_POLL_OP_ADD)
                  erts_atomic_dec_nob(&ps->no_of_user_fds);
              events = ERTS_POLL_EV_NVAL;
          } else
              events = 0;
      }
      return events;
  }
  
  #endif /* ERTS_POLL_USE_KQUEUE */
  
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
  
  static ERTS_INLINE void
  init_batch_update(ErtsPollSet *ps, int len)
  {
  #if ERTS_POLL_USE_DEVPOLL
      ASSERT(ps->poll_fds == NULL);
      ps->poll_fds = erts_alloc(ERTS_ALC_T_TMP, sizeof(struct pollfd) * len);
      ps->poll_fds_ix = 0;
  #endif
  }
  
  static ERTS_INLINE void
  write_batch_update(ErtsPollSet *ps)
  {
  #if ERTS_POLL_USE_DEVPOLL
      ssize_t wres;
      char *buf = (char *) ps->poll_fds;
      size_t buf_size = sizeof(struct pollfd)*ps->poll_fds_ix;
  
      while (1) {
  	wres = write(ps->kp_fd, (void *) buf, buf_size);
  	if (wres < 0) {
  	    if (errno == EINTR)
  		continue;
  	    fatal_error("%s:%d:write_batch_buf(): "
  			"Failed to write to /dev/poll: "
  			"%s (%d)\n",
  			__FILE__, __LINE__,
  			erl_errno_id(errno), errno);
  	}
  #if ERTS_POLL_DEBUG_PRINT
          {
              int saved_errno = errno, i;
              char devpollb[2048], *devpollbp = devpollb;
              devpollbp += sprintf(devpollbp, "%d = devpoll(%d, {", wres, ps->kp_fd);
              for (i = 0; i < wres / sizeof(struct pollfd); i++) {
                  if (devpollbp == devpollb)
                      devpollbp += sprintf(devpollbp, "%d = devpoll(%d, {", wres, ps->kp_fd);
                  devpollbp += sprintf(devpollbp, "%s{fd = %d, events = %s}",
                                       i > 0 ? ", " : "",
                                       ps->poll_fds[i].fd,
                                       ev2str(ps->poll_fds[i].events));
                  if (devpollbp - devpollb > 512) {
                      devpollbp += sprintf(devpollbp, "}, %d)", ps->poll_fds_ix);
                      DEBUG_PRINT("%s", ps, devpollb);
                      devpollbp = devpollb;
                  }
              }
              devpollbp += sprintf(devpollbp, "}, %d)", ps->poll_fds_ix);
              DEBUG_PRINT("%s", ps, devpollb);
              errno = saved_errno;
          }
  #endif
  
  	buf_size -= wres;
  	if (buf_size <= 0)
  	    break;
  	buf += wres;
      }
  
      if (buf_size < 0) {
  	fatal_error("%s:%d:write_devpoll_buf(): Internal error\n",
  		    __FILE__, __LINE__);
      }
      erts_free(ERTS_ALC_T_TMP, ps->poll_fds);
      ps->poll_fds = NULL;
  #endif
  }
  
  static ERTS_INLINE int
  need_update(ErtsPollSet *ps, int fd, int *resetp)
  {
      int reset;
      ASSERT(fd < ps->fds_status_len);
  
      reset = (int) (ps->fds_status[fd].flags & ERTS_POLL_FD_FLG_RST);
      ps->fds_status[fd].flags &= ~ERTS_POLL_FD_FLG_RST;
  
      *resetp = reset;
  
      if (reset || ps->fds_status[fd].used_events != ps->fds_status[fd].events)
  	return 1;
  
      return 0;
  }
  
  static int update_pollset(ErtsPollSet *ps, ErtsPollResFd pr[], int fd)
  {
      int res = 0, reset = 0;
      ErtsPollEvents events = ps->fds_status[fd].events;
      ASSERT(fd < ps->fds_status_len);
  
      if (!need_update(ps, fd, &reset))
  	return res;
  
  #if ERTS_POLL_USE_POLL	/* --- poll -------------------------------- */
      if (!events) {
  	int pix = ps->fds_status[fd].pix;
  	int last_pix;
  
          if (reset) {
              /* pr is only null during poll initialization and then no reset should happen */
              ASSERT(pr != NULL);
              /* When a fd has been reset, we tell the caller of erts_poll_wait
                 this by setting the fd as ERTS_POLL_EV_NONE */
              ERTS_POLL_RES_SET_FD(&pr[res], fd);
              ERTS_POLL_RES_SET_EVTS(&pr[res], ERTS_POLL_EV_NONE);
              DEBUG_PRINT_FD("trig %s (poll)", ps, fd, ev2str(ERTS_POLL_EV_NONE));
              res++;
          }
  
  	if (pix < 0) {
  	    return res;
  	}
  	erts_atomic_dec_nob(&ps->no_of_user_fds);
  	last_pix = --ps->no_poll_fds;
  	if (pix != last_pix) {
  	/* Move last pix to this pix */
  	    ps->poll_fds[pix].fd = ps->poll_fds[last_pix].fd;
  	    ps->poll_fds[pix].events = ps->poll_fds[last_pix].events;
  	    ps->poll_fds[pix].revents = ps->poll_fds[last_pix].revents;
  	    ps->fds_status[ps->poll_fds[pix].fd].pix = pix;
  	}
  	/* Clear last pix */
  	ps->poll_fds[last_pix].fd = -1;
  	ps->poll_fds[last_pix].events = (short) 0;
  	ps->poll_fds[last_pix].revents = (short) 0;
  	/* Clear this fd status */
  	ps->fds_status[fd].pix = -1;
  	ps->fds_status[fd].used_events = (ErtsPollEvents) 0;
  
      }
      else {
  	int pix = ps->fds_status[fd].pix;
  	if (pix < 0) {
  	    erts_atomic_inc_nob(&ps->no_of_user_fds);
  	    ps->fds_status[fd].pix = pix = ps->no_poll_fds++;
  	    if (pix >= ps->poll_fds_len)
  		grow_poll_fds(ps, pix);
  	    ps->poll_fds[pix].fd = fd;
  	    ps->fds_status[fd].pix = pix;
  	}
  
  	/* Events to be used in next poll */
  	ps->poll_fds[pix].events = ev2pollev(events);
  	if (ps->poll_fds[pix].revents) {
  	    /* Remove result events that we should not poll for anymore */
  	    ps->poll_fds[pix].revents
  		&= ev2pollev(~(~ps->fds_status[fd].used_events
  			       & events));
  	}
  	/* Save events to be used in next poll */
  	ps->fds_status[fd].used_events = events;
      }
      return res;
  #elif ERTS_POLL_USE_SELECT	/* --- select ------------------------------ */
      if (!events) {
  
          if (reset) {
              /* When a fd has been reset, we tell the caller of erts_poll_wait
                 this by setting the fd as ERTS_POLL_EV_NONE */
              ERTS_POLL_RES_SET_FD(&pr[res], fd);
              ERTS_POLL_RES_SET_EVTS(&pr[res], ERTS_POLL_EV_NONE);
              DEBUG_PRINT_FD("trig %s (select)", ps, fd, ev2str(ERTS_POLL_EV_NONE));
              res++;
          }
  
          if (check_select_fds(fd, &ps->input_fds, &ps->output_fds)) {
              ERTS_FD_CLR(fd, &ps->input_fds);
              ERTS_FD_CLR(fd, &ps->output_fds);
          }
  
          if (ps->fds_status[fd].used_events) {
              erts_atomic_dec_nob(&ps->no_of_user_fds);
              ps->fds_status[fd].used_events = (ErtsPollEvents) 0;
          }
  
          if (fd == ps->max_fd) {
              int max = ps->max_fd;
              for (max = ps->max_fd; max >= 0; max--)
                  if (ps->fds_status[max].used_events)
                      break;
              ps->max_fd = max;
          }
  
      } else {
  
  	ensure_select_fds(fd, &ps->input_fds, &ps->output_fds);
  
          if (!ps->fds_status[fd].used_events)
              erts_atomic_inc_nob(&ps->no_of_user_fds);
  
          if (events & ERTS_POLL_EV_IN)
              ERTS_FD_SET(fd, &ps->input_fds);
          else
              ERTS_FD_CLR(fd, &ps->input_fds);
  
          if (events & ERTS_POLL_EV_OUT)
              ERTS_FD_SET(fd, &ps->output_fds);
          else
              ERTS_FD_CLR(fd, &ps->output_fds);
  
  	ps->fds_status[fd].used_events = events;
  
          if (fd > ps->max_fd)
              ps->max_fd = fd;
      }
  
      return res;
  #elif ERTS_POLL_USE_DEVPOLL
  
      if (!events) {
  
          if (reset) {
              /* When a fd has been reset, we tell the caller of erts_poll_wait
                 this by setting the fd as ERTS_POLL_EV_NONE */
              ERTS_POLL_RES_SET_FD(&pr[res], fd);
              ERTS_POLL_RES_SET_EVTS(&pr[res], ERTS_POLL_EV_NONE);
              DEBUG_PRINT_FD("trig %s (devpoll)", ps, fd, ev2str(ERTS_POLL_EV_NONE));
              res++;
          }
  
          ps->poll_fds[ps->poll_fds_ix].fd = fd;
          ps->poll_fds[ps->poll_fds_ix].revents = 0;
          ps->poll_fds[ps->poll_fds_ix++].events = POLLREMOVE;
  
          if (ps->fds_status[fd].used_events) {
              erts_atomic_dec_nob(&ps->no_of_user_fds);
              ps->fds_status[fd].used_events = 0;
          }
  
      } else {
          if (!ps->fds_status[fd].used_events) {
              erts_atomic_inc_nob(&ps->no_of_user_fds);
          }
          ps->poll_fds[ps->poll_fds_ix].fd = fd;
          ps->poll_fds[ps->poll_fds_ix].revents = 0;
          ps->poll_fds[ps->poll_fds_ix++].events = ERTS_POLL_EV_E2N(events);
          ps->fds_status[fd].used_events = ps->fds_status[fd].events;
      }
  
      return res;
  #endif
  }
  
  static int
  handle_update_requests(ErtsPollSet *ps, ErtsPollResFd pr[], int no_fds)
  {
      int res = 0;
      ErtsPollSetUpdateRequestsBlock *urqbp = ps->curr_upd_req_block;
  
      while (urqbp) {
  	ErtsPollSetUpdateRequestsBlock *free_urqbp = urqbp;
  	int i;
  	int len = urqbp->len;
  
          init_batch_update(ps, len);
  
  	for (i = 0; i < len; i++) {
  	    int fd = urqbp->fds[i];
  	    ASSERT(fd < ps->fds_status_len);
              ASSERT(ps->fds_status[fd].flags & ERTS_POLL_FD_FLG_INURQ);
  
              /* We have run out of PollResFd slots to put results in,
                 so we yield here and return later for more. */
              if (res == no_fds && pr != NULL) {
                  memmove(urqbp->fds, urqbp->fds+i, sizeof(int) * (len - i));
                  urqbp->len -= i;
                  ps->curr_upd_req_block = urqbp;
                  write_batch_update(ps);
                  return res;
              }
  
              if (ps->fds_status[fd].flags & ERTS_POLL_FD_FLG_INURQ) {
                  ps->fds_status[fd].flags &= ~ERTS_POLL_FD_FLG_INURQ;
                  res += update_pollset(ps, pr + res, fd);
              }
  	}
  
  	free_urqbp = urqbp;
  	urqbp = urqbp->next;
  
  	free_update_requests_block(ps, free_urqbp);
  
          write_batch_update(ps);
  
      }
  
      ps->curr_upd_req_block = &ps->update_requests;
  
  #if ERTS_POLL_USE_DEVPOLL && defined(HARD_DEBUG)
      check_poll_status(ps);
  #endif
  
      ERTS_POLLSET_UNSET_HAVE_UPDATE_REQUESTS(ps);
      return res;
  }
  
  #endif /* !ERTS_POLL_USE_CONCURRENT_UPDATE */
  
  static ERTS_INLINE ErtsPollEvents
  poll_control(ErtsPollSet *ps, int fd, ErtsPollOp op,
               ErtsPollEvents events, int *do_wake)
  {
      ErtsPollEvents new_events;
  
      if (fd < ps->internal_fd_limit || fd >= max_fds) {
  	if (fd < 0 || fd >= max_fds) {
  	    new_events = ERTS_POLL_EV_NVAL;
  	    goto done;
  	}
  #if ERTS_POLL_USE_KERNEL_POLL
  	if (fd == ps->kp_fd) {
  	    new_events = ERTS_POLL_EV_NVAL;
  	    goto done;
  	}
  #endif
  	if (fd == ps->wake_fds[0] || fd == ps->wake_fds[1]) {
  	    new_events = ERTS_POLL_EV_NVAL;
  	    goto done;
  	}
  #if ERTS_POLL_USE_TIMERFD
          if (fd == ps->timer_fd) {
  	    new_events = ERTS_POLL_EV_NVAL;
              goto done;
          }
  #endif
      }
  
  #if ERTS_POLL_USE_CONCURRENT_UPDATE
  
      new_events = concurrent_update_pollset(ps, fd, op, events);
  
  #else /* !ERTS_POLL_USE_CONCURRENT_UPDATE */
      if (fd >= ps->fds_status_len)
  	grow_fds_status(ps, fd);
  
      ASSERT(fd < ps->fds_status_len);
  
      if (op == ERTS_POLL_OP_DEL) {
          ps->fds_status[fd].flags |= ERTS_POLL_FD_FLG_RST;
          ps->fds_status[fd].events = 0;
          *do_wake = 1;
      } else if (op == ERTS_POLL_OP_ADD) {
          ASSERT(ps->fds_status[fd].events == 0);
          ps->fds_status[fd].events = events;
          *do_wake = 1;
      } else {
          ASSERT(op == ERTS_POLL_OP_MOD);
          ps->fds_status[fd].events = events;
          *do_wake = 1;
      }
      new_events = ps->fds_status[fd].events;
  
      enqueue_update_request(ps, fd);
  
  #endif /* !ERTS_POLL_USE_CONCURRENT_UPDATE */
  
   done:
      DEBUG_PRINT_FD("%s = %s(%p, %d, %s, %s) do_wake=%d",
                     ps, fd, ev2str(new_events), __FUNCTION__, ps,
                     fd, op2str(op), ev2str(events), *do_wake);
      return new_events;
  }
  
  ErtsPollEvents
  ERTS_POLL_EXPORT(erts_poll_control)(ErtsPollSet *ps,
  				    ErtsSysFdType fd,
                                      ErtsPollOp op,
  				    ErtsPollEvents events,
  				    int* do_wake) /* In: Wake up polling thread */
  				                  /* Out: Poller is woken */
  {
      ErtsPollEvents res;
  
      ERTS_POLLSET_LOCK(ps);
  
      res = poll_control(ps, fd, op, events, do_wake);
  
      ERTS_POLLSET_UNLOCK(ps);
  
      if (*do_wake)
  	wake_poller(ps, 0);
  
      return res;
  }
  
  /*
   * --- Wait on poll set ------------------------------------------------------
   */
  
  #if ERTS_POLL_USE_KERNEL_POLL
  
  static ERTS_INLINE int
  ERTS_POLL_EXPORT(save_result)(ErtsPollSet *ps, ErtsPollResFd pr[], int max_res, int chk_fds_res, int ebadf)
  {
      int n = chk_fds_res < max_res ? chk_fds_res : max_res, i;
      int res = n;
      int wake_fd = ps->wake_fds[0];
  
      if (ERTS_POLL_USE_WAKEUP(ps) || ERTS_POLL_DEBUG_PRINT || ERTS_POLL_USE_TIMERFD) {
  
          for (i = 0; i < n; i++) {
              int fd = ERTS_POLL_RES_GET_FD(&pr[i]);
  #if ERTS_POLL_DEBUG_PRINT
              ErtsPollEvents evts = ERTS_POLL_RES_GET_EVTS(pr+i);
  
              if (fd != wake_fd
  #if ERTS_POLL_USE_TIMERFD
                  && fd != ps->timer_fd
  #endif
                  )
                  DEBUG_PRINT_FD("trig %s (%s)", ps, fd,
                                 ev2str(evts),
  #if ERTS_POLL_USE_KQUEUE
                                 "kqueue"
  #elif ERTS_POLL_USE_EPOLL
                                 "epoll"
  #else
                                 "/dev/poll"
  #endif
                      );
  #endif
  
              if (ERTS_POLL_USE_WAKEUP(ps) && fd == wake_fd) {
                  cleanup_wakeup_pipe(ps);
                  ERTS_POLL_RES_SET_FD(&pr[i], ERTS_SYS_FD_INVALID);
                  ERTS_POLL_RES_SET_EVTS(&pr[i], ERTS_POLL_EV_NONE);
                  res--;
              }
  #if ERTS_POLL_USE_TIMERFD
              else if (fd == ps->timer_fd) {
                  ERTS_POLL_RES_SET_FD(&pr[i], ERTS_SYS_FD_INVALID);
                  ERTS_POLL_RES_SET_EVTS(&pr[i], ERTS_POLL_EV_NONE);
                  res--;
              }
  #endif
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
              else {
                  /* Reset the events to emulate ONESHOT semantics */
                  ps->fds_status[fd].events = 0;
                  enqueue_update_request(ps, fd);
              }
  #endif
          }
      }
  
      if (res == 0)
          return res;
      else
          return n;
  }
  
  #else /* !ERTS_POLL_USE_KERNEL_POLL */
  
  static ERTS_INLINE int
  ERTS_POLL_EXPORT(save_result)(ErtsPollSet *ps, ErtsPollResFd pr[], int max_res, int chk_fds_res, int ebadf)
  {
  #if ERTS_POLL_USE_POLL	/* --- poll -------------------------------- */
  	int res = 0;
  	int wake_fd = ps->wake_fds[0];
  	int i, first_ix, end_ix;
  
  	/*
  	 * In order to be somewhat fair, we continue on the poll_fds
  	 * index where we stopped last time.
  	 */
  	first_ix = i = ((ps->next_poll_fds_ix < ps->no_poll_fds)
  			? ps->next_poll_fds_ix
  			: 0);
  	end_ix = ps->no_poll_fds;
  
  	while (1) {
  	    while (i < end_ix && res < max_res) {
  		if (ps->poll_fds[i].revents != (short) 0) {
  		    int fd = ps->poll_fds[i].fd;
  		    ErtsPollEvents revents;
  		    if (fd == wake_fd) {
  			cleanup_wakeup_pipe(ps);
  			i++;
  			continue;
  		    }
  		    revents = pollev2ev(ps->poll_fds[i].revents);
  		    ERTS_POLL_RES_SET_FD(&pr[res], fd);
  		    ERTS_POLL_RES_SET_EVTS(&pr[res], revents);
  
                      /* If an fd returns as error, we may want to check the
                         update_requests queue to see if it has been reset
                         before delivering the result?!?! This should allow
                         the user to do driver_dselect + close without waiting
                         for stop_select... */
  
                      DEBUG_PRINT_FD("trig %s (poll)", ps, ERTS_POLL_RES_GET_FD(&pr[res]),
                                     ev2str(ERTS_POLL_RES_GET_EVTS(&pr[res])));
  
  		    res++;
  
                      /* Clear the events for this fd in order to mimic
                         how epoll ONESHOT works */
                      ps->fds_status[fd].events = 0;
                      enqueue_update_request(ps, fd);
  		}
  		i++;
  	    }
  	    if (res == max_res || i == first_ix)
  		break;
  	    ASSERT(i == ps->no_poll_fds);
  	    i = 0;
  	    end_ix = first_ix;
  	}
  
  	ps->next_poll_fds_ix = i;
  	return res;
  
  #elif ERTS_POLL_USE_SELECT	/* --- select ------------------------------ */
  	int res = 0;
  	int wake_fd = ps->wake_fds[0];
  	int fd, first_fd, end_fd;
  
  	/*
  	 * In order to be fair, we continue on the fd where we stopped
  	 * last time.
  	 */
  	first_fd = fd = ps->next_sel_fd <= ps->max_fd ? ps->next_sel_fd : 0;
  	end_fd = ps->max_fd + 1;
  
  	if (!ebadf) {
  	    while (1) {
  		while (fd < end_fd && res < max_res) {
                      ErtsPollEvents events = 0;
  		    if (ERTS_FD_ISSET(fd, &ps->res_input_fds)) {
  			if (fd == wake_fd) {
  			    cleanup_wakeup_pipe(ps);
  			    fd++;
  			    continue;
  			}
  			events |= ERTS_POLL_EV_IN;
  		    }
  		    if (ERTS_FD_ISSET(fd, &ps->res_output_fds))
  			events |= ERTS_POLL_EV_OUT;
  		    if (events) {
                          ERTS_POLL_RES_SET_FD(&pr[res], fd);
                          ERTS_POLL_RES_SET_EVTS(&pr[res], events);
  			res++;
                          ps->fds_status[fd].events = 0;
                          enqueue_update_request(ps, fd);
  		    }
  		    fd++;
  		}
  		if (res == max_res || fd == first_fd)
  		    break;
  		ASSERT(fd == ps->max_fd + 1);
  		fd = 0;
  		end_fd = first_fd;
  	    }
  	}
  	else {
  	    /*
  	     * Bad file descriptors in poll set.
  	     *
  	     * This only happens when running poorly written
  	     * drivers. This code could be optimized, but we
  	     * don't bother since it should never happen...
  	     */
  	    while (1) {
  		while (fd < end_fd && res < max_res) {
  		    if (ps->fds_status[fd].events) {
  			int sres;
  			ERTS_fd_set *iset = NULL;
  			ERTS_fd_set *oset = NULL;
  			if (ps->fds_status[fd].events & ERTS_POLL_EV_IN) {
  			    iset = &ps->res_input_fds;
  			    ERTS_FD_ZERO(iset);
  			    ERTS_FD_SET(fd, iset);
  			}
  			if (ps->fds_status[fd].events & ERTS_POLL_EV_OUT) {
  			    oset = &ps->res_output_fds;
  			    ERTS_FD_ZERO(oset);
  			    ERTS_FD_SET(fd, oset);
  			}
  			do {
  			    /* Initiate 'tv' each time;
  			       select() may modify it */
  			    SysTimeval tv = {0, 0};
  			    sres = ERTS_SELECT(ps->max_fd+1, iset, oset, NULL, &tv);
  			} while (sres < 0 && errno == EINTR);
  			if (sres < 0) {
  			    if (fd == wake_fd) {
  				cleanup_wakeup_pipe(ps);
  				fd++;
  				continue;
  			    }
                              ERTS_POLL_RES_SET_FD(&pr[res], fd);
                              ERTS_POLL_RES_SET_EVTS(&pr[res], ERTS_POLL_EV_NVAL);
  			    res++;
  			}
  			else if (sres > 0) {
                              ErtsPollEvents events = 0;
                              ERTS_POLL_RES_SET_FD(&pr[res], fd);
  			    if (iset && ERTS_FD_ISSET(fd, iset)) {
  				if (fd == wake_fd) {
  				    cleanup_wakeup_pipe(ps);
  				    fd++;
  				    continue;
  				}
  				events |= ERTS_POLL_EV_IN;
  			    }
  			    if (oset && ERTS_FD_ISSET(fd, oset)) {
  				events |= ERTS_POLL_EV_OUT;
  			    }
  			    ASSERT(events);
                              ERTS_POLL_RES_SET_EVTS(&pr[res], events);
  			    res++;
  			}
  		    }
  		    fd++;
  		}
  		if (res == max_res || fd == first_fd)
  		    break;
  		ASSERT(fd == ps->max_fd + 1);
  		fd = 0;
  		end_fd = first_fd;
  	    }
  	}
  	ps->next_sel_fd = fd;
  	return res;
  #endif /* ERTS_POLL_USE_SELECT */
  }
  
  #endif /* !ERTS_POLL_USE_KERNEL_POLL */
  
  static ERTS_INLINE ErtsMonotonicTime
  get_timeout(ErtsPollSet *ps,
  	    int resolution,
  	    ErtsMonotonicTime timeout_time)
  {
      ErtsMonotonicTime timeout;
  
      if (timeout_time == ERTS_POLL_NO_TIMEOUT) {
  	timeout = 0;
      }
      else if (timeout_time == ERTS_POLL_INF_TIMEOUT) {
          timeout = -1;
      }
      else {
  	ErtsMonotonicTime diff_time, current_time;
  	current_time = erts_get_monotonic_time(NULL);
  	diff_time = timeout_time - current_time;
  	if (diff_time <= 0) {
  	    timeout = 0;
  	}
  	else {
  	    switch (resolution) {
  	    case 1000:
  		/* Round up to nearest even milli second */
  		timeout = ERTS_MONOTONIC_TO_MSEC(diff_time - 1) + 1;
  		if (timeout > (ErtsMonotonicTime) INT_MAX)
  		    timeout = (ErtsMonotonicTime) INT_MAX;
  		timeout -= ERTS_PREMATURE_TIMEOUT(timeout, 1000);
  		break;
  	    case 1000000:
  		/* Round up to nearest even micro second */
  		timeout = ERTS_MONOTONIC_TO_USEC(diff_time - 1) + 1;
  		timeout -= ERTS_PREMATURE_TIMEOUT(timeout, 1000*1000);
  		break;
  	    case 1000000000:
  		/* Round up to nearest even nano second */
  		timeout = ERTS_MONOTONIC_TO_NSEC(diff_time - 1) + 1;
  		timeout -= ERTS_PREMATURE_TIMEOUT(timeout, 1000*1000*1000);
  		break;
  	    default:
  		ERTS_INTERNAL_ERROR("Invalid resolution");
  		timeout = 0;
  		break;
  	    }
  	}
      }
      return timeout;
  }
  
  #if ERTS_POLL_USE_SELECT
  
  static ERTS_INLINE int
  get_timeout_timeval(ErtsPollSet *ps,
  		    SysTimeval *tvp,
  		    ErtsMonotonicTime timeout_time)
  {
      ErtsMonotonicTime timeout = get_timeout(ps,
  					    1000*1000,
  					    timeout_time);
  
      if (!timeout) {
  	tvp->tv_sec = 0;
  	tvp->tv_usec = 0;
  
  	return 0;
      }
      else if (timeout == -1) {
          return -1;
      }
      else {
  	ErtsMonotonicTime sec = timeout/(1000*1000);
          if (sec >= ERTS_SELECT_MAX_TV_SEC__) {
              tvp->tv_sec = ERTS_SELECT_MAX_TV_SEC__;
              tvp->tv_usec = 0;
              /*
               * If we actually should time out on
               * this (huge) timeout, the select() call
               * will be restarted with a newly calculated
               * timeout after verifying the timeout...
               */
              return 1;
          }
  	tvp->tv_sec = sec;
  	tvp->tv_usec = timeout - sec*(1000*1000);
  
  	ASSERT(tvp->tv_sec >= 0);
  	ASSERT(tvp->tv_usec >= 0);
  	ASSERT(tvp->tv_usec < 1000*1000);
  
  	return 1;
      }
  
  }
  
  #endif
  
  #if ERTS_POLL_USE_KQUEUE || (ERTS_POLL_USE_POLL && defined(HAVE_PPOLL)) || ERTS_POLL_USE_TIMERFD
  
  static ERTS_INLINE int
  get_timeout_timespec(ErtsPollSet *ps,
  		     struct timespec *tsp,
  		     ErtsMonotonicTime timeout_time)
  {
      ErtsMonotonicTime timeout = get_timeout(ps,
  					    1000*1000*1000,
  					    timeout_time);
  
      if (!timeout) {
  	tsp->tv_sec = 0;
  	tsp->tv_nsec = 0;
  	return 0;
      }
      else if (timeout == -1) {
          return -1;
      }
      else {
  	ErtsMonotonicTime sec = timeout/(1000*1000*1000);
  	tsp->tv_sec = sec;
  	tsp->tv_nsec = timeout - sec*(1000*1000*1000);
  
  	ASSERT(tsp->tv_sec >= 0);
  	ASSERT(tsp->tv_nsec >= 0);
  	ASSERT(tsp->tv_nsec < 1000*1000*1000);
  
  	return 1;
      }
  }
  
  #endif
  
  #if ERTS_POLL_USE_TIMERFD
  
  static ERTS_INLINE int
  get_timeout_itimerspec(ErtsPollSet *ps,
                         struct itimerspec *itsp,
                         ErtsMonotonicTime timeout_time)
  {
  
      itsp->it_interval.tv_sec = 0;
      itsp->it_interval.tv_nsec = 0;
  
      return get_timeout_timespec(ps, &itsp->it_value, timeout_time);
  }
   
  #endif
  
  static ERTS_INLINE int
  check_fd_events(ErtsPollSet *ps, ErtsPollResFd pr[], int max_res, ErtsMonotonicTime timeout_time)
  {
      int res;
      int timeout;
      DEBUG_PRINT_WAIT("Entering check_fd_events(), timeout=%d", ps, timeout_time);
      {
  #if ERTS_POLL_USE_EPOLL                /* --- epoll ------------------------------- */
  #if ERTS_POLL_USE_TIMERFD
          struct itimerspec its;
          timeout = get_timeout_itimerspec(ps, &its, timeout_time);
          if (timeout > 0) {
              timerfd_set(ps, &its);
              res = epoll_wait(ps->kp_fd, pr, max_res, -1);
              res = timerfd_clear(ps, pr, res, max_res);
          } else {
              res = epoll_wait(ps->kp_fd, pr, max_res, timeout);
          }
  #else /* !ERTS_POLL_USE_TIMERFD */
          timeout = (int) get_timeout(ps, 1000, timeout_time);
          res = epoll_wait(ps->kp_fd, pr, max_res, timeout);
  #endif /* !ERTS_POLL_USE_TIMERFD */
  #elif ERTS_POLL_USE_KQUEUE     /* --- kqueue ------------------------------ */
          struct timespec ts;
          struct timespec *tsp;
          timeout = get_timeout_timespec(ps, &ts, timeout_time);
          tsp = timeout < 0 ? NULL : &ts;
          res = kevent(ps->kp_fd, NULL, 0, pr, max_res, tsp);
  #elif ERTS_POLL_USE_DEVPOLL	/* --- devpoll ----------------------------- */
          /*
           * The ioctl() will fail with EINVAL on Solaris 10 if dp_nfds
           * is set too high. dp_nfds should not be set greater than
           * the maximum number of file descriptors in the poll set.
           */
          struct dvpoll poll_res;
          int nfds = (int) erts_atomic_read_nob(&ps->no_of_user_fds) + 1 /* wakeup pipe */;
          poll_res.dp_nfds = nfds < max_res ? nfds : max_res;
          poll_res.dp_fds = pr;
          poll_res.dp_timeout = (int) get_timeout(ps, 1000, timeout_time);
          res = ioctl(ps->kp_fd, DP_POLL, &poll_res);
  #elif ERTS_POLL_USE_POLL && defined(HAVE_PPOLL)	/* --- ppoll ---------------- */
          struct timespec ts;
          struct timespec *tsp = &ts;
          timeout = get_timeout_timespec(ps, &ts, timeout_time);
          if (timeout < 0) tsp = NULL;
          res = ppoll(ps->poll_fds, ps->no_poll_fds, tsp, NULL);
  #elif ERTS_POLL_USE_POLL        /* --- poll --------------------------------- */
          timeout = (int) get_timeout(ps, 1000, timeout_time);
          res = poll(ps->poll_fds, ps->no_poll_fds, timeout);
  #elif ERTS_POLL_USE_SELECT	/* --- select ------------------------------ */
          SysTimeval tv;
          SysTimeval *tvp;
          timeout = get_timeout_timeval(ps, &tv, timeout_time);
          tvp = timeout < 0 ? NULL : &tv;
  
          ERTS_FD_COPY(&ps->input_fds, &ps->res_input_fds);
          ERTS_FD_COPY(&ps->output_fds, &ps->res_output_fds);
  
          res = ERTS_SELECT(ps->max_fd + 1,
                            &ps->res_input_fds,
                            &ps->res_output_fds,
                            NULL,
                            tvp);
  #endif				/* ----------------------------------------- */
      }
      DEBUG_PRINT_WAIT("Leaving check_fd_events(), res=%d", ps, res);
      return res;
  }
  
  int
  ERTS_POLL_EXPORT(erts_poll_wait)(ErtsPollSet *ps,
  				 ErtsPollResFd pr[],
  				 int *len,
                                   ErtsThrPrgrData *tpd,
                                   ErtsMonotonicTime timeout_time)
  {
      int res, no_fds, used_fds = 0;
      int ebadf = 0;
      int do_wait;
      int ps_locked = 0;
      ERTS_MSACC_DECLARE_CACHE();
  
      no_fds = *len;
      *len = 0;
      ASSERT(no_fds > 0);
  
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
      if (ERTS_POLLSET_HAVE_UPDATE_REQUESTS(ps)) {
  	ERTS_POLLSET_LOCK(ps);
  	used_fds = handle_update_requests(ps, pr, no_fds);
  	ERTS_POLLSET_UNLOCK(ps);
  
          if (used_fds == no_fds) {
              *len = used_fds;
              return 0;
          }
      }
  #endif
  
      do_wait = !is_woken(ps) && used_fds == 0 && timeout_time != ERTS_POLL_NO_TIMEOUT;
  
      DEBUG_PRINT_WAIT("Entering %s(), do_wait=%d", ps, __FUNCTION__, do_wait);
  
      if (do_wait) {
          tpd = tpd ? tpd : erts_thr_prgr_data(NULL);
          erts_thr_progress_prepare_wait(tpd);
          ERTS_MSACC_SET_STATE_CACHED(ERTS_MSACC_STATE_SLEEP);
      } else
          timeout_time = ERTS_POLL_NO_TIMEOUT;
  
      while (1) {
          res = check_fd_events(ps, pr + used_fds, no_fds - used_fds, timeout_time);
          if (res != 0)
              break;
          if (timeout_time == ERTS_POLL_NO_TIMEOUT)
              break;
          if (erts_get_monotonic_time(NULL) >= timeout_time)
  	    break;
      }
  
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
      if (res < 0
          && errno == EBADF
          && ERTS_POLLSET_HAVE_UPDATE_REQUESTS(ps)) {
          /*
           * This may have happened because another thread deselected
           * a fd in our poll set and then closed it, i.e. the driver
           * behaved correctly. We wan't to avoid looking for a bad
           * fd, that may even not exist anymore. Therefore, handle
           * update requests and try again. This behaviour should only
           * happen when using SELECT as the polling mechanism.
           */
          ERTS_POLLSET_LOCK(ps);
          used_fds += handle_update_requests(ps, pr + used_fds, no_fds - used_fds);
          if (used_fds == no_fds) {
              *len = used_fds;
              ERTS_POLLSET_UNLOCK(ps);
              return 0;
          }
          res = check_fd_events(ps, pr + used_fds, no_fds - used_fds, ERTS_POLL_NO_TIMEOUT);
          /* Keep the lock over the non-blocking poll in order to not
             get any nasty races happening. */
          ERTS_POLLSET_UNLOCK(ps);
          if (res == 0) {
              errno = EAGAIN;
              res = -1;
          }
      }
  #endif
  
      if (do_wait) {
          erts_thr_progress_finalize_wait(tpd);
          ERTS_MSACC_UPDATE_CACHE();
          ERTS_MSACC_SET_STATE_CACHED(ERTS_MSACC_STATE_CHECK_IO);
      }
  
      woke_up(ps);
  
      if (res < 0) {
  #if ERTS_POLL_USE_SELECT
  	if (errno == EBADF) {
  	    ebadf = 1;
  	    goto save_results;
  	}
  #endif
  	res = errno;
      }
      else if (res == 0) {
          res = used_fds == 0 ? ETIMEDOUT : 0;
  #ifdef HARD_DEBUG
  	check_poll_result(pr, used_fds);
  #endif
          *len = used_fds;
      } else {
  #if ERTS_POLL_USE_SELECT
      save_results:
  #endif
  	ps_locked = 1;
  	ERTS_POLLSET_LOCK(ps);
  
  	used_fds += ERTS_POLL_EXPORT(save_result)(ps, pr + used_fds, no_fds - used_fds, res, ebadf);
  
  #ifdef HARD_DEBUG
  	check_poll_result(pr, used_fds);
  #endif
  
  	res = (used_fds == 0 ? (is_interrupted_reset(ps) ? EINTR : EAGAIN) : 0);
  	*len = used_fds;
      }
  
      if (ps_locked)
  	ERTS_POLLSET_UNLOCK(ps);
  
      DEBUG_PRINT_WAIT("Leaving %s = %s(len = %d)", ps,
                       res == 0 ? "0" : erl_errno_id(res), __FUNCTION__, *len);
  
      return res;
  }
  
  /*
   * --- Interrupt a thread doing erts_poll_wait() -----------------------------
   */
  
  void
  ERTS_POLL_EXPORT(erts_poll_interrupt)(ErtsPollSet *ps, int set)
  {
      DEBUG_PRINT_WAIT("poll_interrupt(%d)", ps, set);
      if (ERTS_POLL_USE_WAKEUP(ps)) {
          if (!set)
              reset_wakeup_state(ps);
          else
              wake_poller(ps, 1);
      }
  }
  
  int
  ERTS_POLL_EXPORT(erts_poll_max_fds)(void)
  {
      return max_fds;
  }
  /*
   * --- Initialization --------------------------------------------------------
   */
  
  void
  ERTS_POLL_EXPORT(erts_poll_init)(int *concurrent_updates)
  {
  
      errno = 0;
  
      if (concurrent_updates) {
  #if ERTS_POLL_USE_CONCURRENT_UPDATE
          *concurrent_updates = 1;
  #else
          *concurrent_updates = 0;
  #endif
      }
  
  #if !defined(NO_SYSCONF)
      max_fds = sysconf(_SC_OPEN_MAX);
  #elif ERTS_POLL_USE_SELECT
      max_fds = NOFILE;
  #else
      max_fds = OPEN_MAX;
  #endif
  
      if (max_fds < 0 && errno == 0) {
          /* On macOS 11 and higher, it possible to have an unlimited
           * number of open files per process.  ERTS will need an actual
           * limit, though, so we will set it to a largish value.  The
           * number below is the hard number of file descriptors per
           * process as returned by `sysctl kern.maxfilesperproc`, which
           * seems to be the limit in practice.
           *
           * Note: The size of the port table will be based on max_fds,
           * so we don't want to set it to a huge value such as
           * MAX_INT.
           */
          max_fds = 24576;
      }
  
  #if ERTS_POLL_USE_SELECT && defined(FD_SETSIZE) && \
  	!defined(_DARWIN_UNLIMITED_SELECT)
      if (max_fds > FD_SETSIZE)
  	max_fds = FD_SETSIZE;
  #endif
  
      if (max_fds < 0)
  	fatal_error("erts_poll_init(): Failed to get max number of files: %s\n",
  		    erl_errno_id(errno));
  
      print_misc_debug_info();
  }
  
  ErtsPollSet *
  ERTS_POLL_EXPORT(erts_poll_create_pollset)(int id)
  {
  #if ERTS_POLL_USE_KERNEL_POLL
      int kp_fd;
  #endif
      ErtsPollSet *ps = erts_alloc(ERTS_ALC_T_POLLSET,
  				sizeof(struct ERTS_POLL_EXPORT(erts_pollset)));
      ps->id = id;
      ps->internal_fd_limit = 0;
      erts_atomic_init_nob(&ps->no_of_user_fds, 0);
  #if ERTS_POLL_USE_KERNEL_POLL
      ps->kp_fd = -1;
  #if ERTS_POLL_USE_EPOLL
      kp_fd = epoll_create(256);
  #elif ERTS_POLL_USE_DEVPOLL
      kp_fd = open("/dev/poll", O_RDWR);
  #elif ERTS_POLL_USE_KQUEUE
      kp_fd = kqueue();
  #endif
      if (kp_fd < 0)
  	fatal_error("erts_poll_create_pollset(): Failed to "
  #if ERTS_POLL_USE_EPOLL
  		    "create epoll set"
  #elif ERTS_POLL_USE_DEVPOLL
  		    "to open /dev/poll"
  #elif ERTS_POLL_USE_KQUEUE
  		    "create kqueue"
  #endif
  		    ": %s (%d)\n",
  		    erl_errno_id(errno), errno);
  #endif /* ERTS_POLL_USE_KERNEL_POLL */
  #if ERTS_POLL_USE_POLL
      ps->next_poll_fds_ix = 0;
      ps->no_poll_fds = 0;
      ps->poll_fds_len = 0;
      ps->poll_fds = NULL;
  #elif ERTS_POLL_USE_SELECT
      ps->next_sel_fd = 0;
      ps->max_fd = -1;
  #ifdef _DARWIN_UNLIMITED_SELECT
      ps->input_fds.sz = 0;
      ps->input_fds.ptr = NULL;
      ps->res_input_fds.sz = 0;
      ps->res_input_fds.ptr = NULL;
      ps->output_fds.sz = 0;
      ps->output_fds.ptr = NULL;
      ps->res_output_fds.sz = 0;
      ps->res_output_fds.ptr = NULL;
  #else
      ERTS_FD_ZERO(&ps->input_fds);
      ERTS_FD_ZERO(&ps->res_input_fds);
      ERTS_FD_ZERO(&ps->output_fds);
      ERTS_FD_ZERO(&ps->res_output_fds);
  #endif
  #endif
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
      ps->fds_status = NULL;
      ps->fds_status_len = 0;
      ps->update_requests.next = NULL;
      ps->update_requests.len = 0;
      ps->curr_upd_req_block = &ps->update_requests;
      erts_atomic32_init_nob(&ps->have_update_requests, 0);
      erts_mtx_init(&ps->mtx, "pollset", NIL, ERTS_LOCK_FLAGS_CATEGORY_IO);
  #endif
  #if ERTS_POLL_USE_KERNEL_POLL
      if (ps->internal_fd_limit <= kp_fd)
  	ps->internal_fd_limit = kp_fd + 1;
      ps->kp_fd = kp_fd;
      if (ps->id == -1)
          ps->oneshot = 0;
      else
          ps->oneshot = 1;
  #endif
  
      create_wakeup_pipe(ps);
  
  #if ERTS_POLL_USE_TIMERFD
      create_timerfd(ps);
  #endif
  
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
      handle_update_requests(ps, NULL, 0);
      cleanup_wakeup_pipe(ps);
  #endif
  #if ERTS_POLL_USE_KERNEL_POLL && (defined(__DARWIN__) || defined(__APPLE__) && defined(__MACH__))
      {
          /*
           * Using kqueue on OS X is a mess of brokenness...
           *
           * On OS X version older than 15.6 (i.e. OS X El Capitan released in July 2015),
           * a thread waiting in kevent is not woken if an event is inserted into the kqueue
           * by another thread and the event becomes ready. However if a new call to kevent
           * is done by the waiting thread, the new event is found.
           *
           * So on effected OS X versions we could trigger the wakeup pipe so that
           * the waiters will be woken and re-issue the kevent. However...
           *
           * On OS X version older then 16 (i.e. OS X Sierra released in September 2016),
           * running the emulator driver_SUITE smp_select testcase consistently causes a
           * kernel panic. I don't know why or what events that trigger it. But it seems
           * like updates of the pollset while another thread is sleeping in it Creates
           * some kind of race that triggers the kernel panic.
           *
           * So to deal with this, the erts configure check what OS X version is run
           * and only enabled kernel poll on OS X 16 or newer. In addition, if someone
           * attempts to compile Erlang on OS X 16 and then run it on OS X 15, we do the
           * run-time check below to disallow this.
           */
          int major, minor, build;
          os_version(&major,&minor,&build);
          if (major < 16) {
              erts_fprintf(stderr,"BROKEN KQUEUE!\n"
                           "Erlang has been compiled with kernel-poll support,\n"
                           "but this OS X version is known to have kernel bugs\n"
                           "when using kernel-poll. You have two options:\n"
                           " 1) update to a newer OS X version (OS X Sierra or newer)\n"
                           " 2) recompile erlang without kernel-poll support\n");
              erts_exit(1, "");
          }
      }
  #endif
      erts_atomic_set_nob(&ps->no_of_user_fds, 0); /* Don't count wakeup pipe and fallback fd */
  
      return ps;
  }
  
  /*
   * --- Info ------------------------------------------------------------------
   */
  
  void
  ERTS_POLL_EXPORT(erts_poll_info)(ErtsPollSet *ps, ErtsPollInfo *pip)
  {
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
      int pending_updates;
  #endif
      Uint size = 0;
  
      ERTS_POLLSET_LOCK(ps);
  
      size += sizeof(struct ERTS_POLL_EXPORT(erts_pollset));
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
      size += ps->fds_status_len*sizeof(ErtsFdStatus);
  #endif
  
  #if ERTS_POLL_USE_POLL
      size += ps->poll_fds_len*sizeof(struct pollfd);
  #elif ERTS_POLL_USE_SELECT
  #ifdef _DARWIN_UNLIMITED_SELECT
      size += ps->input_fds.sz + ps->res_input_fds.sz
  	 + ps->output_fds.sz + ps->res_output_fds.sz;
  #endif
  #endif
  
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
      {
  	ErtsPollSetUpdateRequestsBlock *urqbp = ps->update_requests.next;
  	pending_updates = ps->update_requests.len;
  	while (urqbp) {
  	    size += sizeof(ErtsPollSetUpdateRequestsBlock);
  	    pending_updates += urqbp->len;
  	    urqbp = urqbp->next;
  	}
      }
  #endif
  
      pip->primary =
  #if ERTS_POLL_USE_KQUEUE
  	"kqueue"
  #elif ERTS_POLL_USE_EPOLL
  	"epoll"
  #elif ERTS_POLL_USE_DEVPOLL
  	"/dev/poll"
  #elif ERTS_POLL_USE_POLL
  	"poll"
  #elif ERTS_POLL_USE_SELECT
  	"select"
  #endif
  	;
  
      pip->kernel_poll =
  #if !ERTS_POLL_USE_KERNEL_POLL
  	NULL
  #elif ERTS_POLL_USE_KQUEUE
  	"kqueue"
  #elif ERTS_POLL_USE_EPOLL
  	"epoll"
  #elif ERTS_POLL_USE_DEVPOLL
  	"/dev/poll"
  #endif
  	;
  
      pip->memory_size = size;
  
      pip->poll_set_size = (int) erts_atomic_read_nob(&ps->no_of_user_fds);
      pip->poll_set_size++; /* Wakeup pipe */
  
      pip->lazy_updates =
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
  	1
  #else
  	0
  #endif
  	;
  
      pip->pending_updates = 
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
  	pending_updates
  #else
  	0
  #endif
  	;
  
      pip->concurrent_updates =
  #if ERTS_POLL_USE_CONCURRENT_UPDATE
  	1
  #else
  	0
  #endif
  	;
  
      pip->is_fallback =
  #if ERTS_POLL_IS_FALLBACK
          1
  #else
          0
  #endif
          ;
  
      pip->batch_updates =
  #if ERTS_POLL_USE_DEVPOLL
          1
  #else
          0
  #endif
          ;
  
      pip->max_fds = max_fds;
  
      ERTS_POLLSET_UNLOCK(ps);
  
  }
  
  /*
   * Fatal error...
   */
  
  #ifndef ERTS_GOT_SIGUSR1
  #  define ERTS_GOT_SIGUSR1 0
  #endif
  
  static void
  fatal_error(char *format, ...)
  {
      va_list ap;
  
      if (ERTS_SOMEONE_IS_CRASH_DUMPING || ERTS_GOT_SIGUSR1) {
  	/*
  	 * Crash dump writing and reception of sigusr1 (which will
  	 * result in a crash dump) closes all file descriptors. This
  	 * typically results in a fatal error for erts_poll() (wakeup
  	 * pipes and kernel poll fds are closed).
  	 *
  	 * We ignore the error and let the crash dump writing continue...
  	 */
  	return;
      }
      va_start(ap, format);
      erts_vfprintf(stderr, format, ap);
      va_end(ap);
      abort();
  }
  
  /*
   * --- Debug -----------------------------------------------------------------
   */
  
  #if ERTS_POLL_USE_EPOLL
  uint32_t epoll_events(int kp_fd, int fd)
  {
      /* For epoll we read the information about what is selected upon from the proc fs.*/
      char fname[30];
      char s[256];
      FILE *f;
      unsigned int pos, flags, mnt_id;
      int hdr_lines, line = 1;
      sprintf(fname,"/proc/%d/fdinfo/%d",getpid(), kp_fd);
      f = fopen(fname,"r");
      if (!f) {
          fprintf(stderr,"failed to open file %s, errno = %d\n", fname, errno);
          ASSERT(0);
          return 0;
      }
      hdr_lines = fscanf(f,"pos:\t%x\nflags:\t%x\nmnt_id:\t%x\n",
                         &pos, &flags, &mnt_id);
      if (hdr_lines < 2) {
          fprintf(stderr,"failed to parse file %s, errno = %d\n", fname, errno);
          ASSERT(0);
          return 0;
      }
      line += hdr_lines;
      while (fgets(s, sizeof(s) / sizeof(*s), f)) {
          /* tfd:       10 events: 40000019 data:       180000000a */
          int ev_fd;
          uint32_t events;
          uint64_t data;
          if (sscanf(s,"tfd:%d events:%x data:%llx", &ev_fd, &events,
                     (unsigned long long*)&data) != 3) {
              fprintf(stderr,"failed to parse file %s on line %d, errno = %d\n", fname,
                      line,
                      errno);
              fclose(f);
              return 0;
          }
          if (fd == ev_fd) {
              fclose(f);
              return events;
          }
      }
      fclose(f);
      return 0;
  }
  #endif
  
  void
  ERTS_POLL_EXPORT(erts_poll_get_selected_events)(ErtsPollSet *ps,
  						ErtsPollEvents ev[],
  						int len)
  {
      int fd;
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
      ERTS_POLLSET_LOCK(ps);
      for (fd = 0; fd < len; fd++) {
  	if (fd >= ps->fds_status_len)
  	    ev[fd] = 0;
  	else {
  	    ev[fd] = ps->fds_status[fd].events;
              if (
                  fd == ps->wake_fds[0] || fd == ps->wake_fds[1] ||
  #if ERTS_POLL_USE_KERNEL_POLL
                  fd == ps->kp_fd ||
  #endif
                  0)
                  ev[fd] |= ERTS_POLL_EV_NVAL;
  	}
      }
      ERTS_POLLSET_UNLOCK(ps);
  #elif ERTS_POLL_USE_EPOLL
  
      /* For epoll we read the information about what is selected upon from the proc fs.*/
      char fname[30];
      char s[256];
      FILE *f;
      unsigned int pos, flags, mnt_id;
      int hdr_lines, line = 1;
      sprintf(fname,"/proc/%d/fdinfo/%d",getpid(), ps->kp_fd);
      for (fd = 0; fd < len; fd++)
          ev[fd] = ERTS_POLL_EV_NONE;
      f = fopen(fname,"r");
      if (!f) {
          fprintf(stderr,"failed to open file %s, errno = %d\n", fname, errno);
          return;
      }
      hdr_lines = fscanf(f,"pos:\t%x\nflags:\t%x\nmnt_id:\t%x\n",
                         &pos, &flags, &mnt_id);
      if (hdr_lines < 2) {
          fprintf(stderr,"failed to parse file %s, errno = %d\n", fname, errno);
          ASSERT(0);
          fclose(f);
          return;
      }
      line += hdr_lines;
      while (fgets(s, sizeof(s) / sizeof(*s), f)) {
          /* tfd:       10 events: 40000019 data:       180000000a */
          int fd;
          uint32_t events;
          uint64_t data;
          if (sscanf(s,"tfd:%d events:%x data:%llx", &fd, &events,
                     (unsigned long long*)&data) != 3) {
              fprintf(stderr,"failed to parse file %s on line %d, errno = %d\n",
                      fname, line, errno);
              ASSERT(0);
              fclose(f);
              return;
          }
          if (fd == ps->wake_fds[0] || fd == ps->wake_fds[1])
              continue;
  #if ERTS_POLL_USE_TIMERFD
          if (fd == ps->timer_fd)
              continue;
  #endif
          data &= 0xFFFFFFFF;
          ASSERT(fd == data);
          /* Events are the events that are being monitored, which of course include
             error and hup events, but we are only interested in IN/OUT events */
          ev[fd] = (ERTS_POLL_EV_IN|ERTS_POLL_EV_OUT) & ERTS_POLL_EV_N2E(events);
          line++;
      }
      fclose(f);
  #else
      for (fd = 0; fd < len; fd++)
          ev[fd] = ERTS_POLL_EV_NONE;
  #endif
  }
  
  #ifdef HARD_DEBUG
  
  static void
  check_poll_result(ErtsPollResFd pr[], int len)
  {
      int i, j;
  
      for (i = 0; i < len; i++) {
  	ASSERT(pr[i].fd >= 0);
  	ASSERT(pr[i].fd < max_fds);
  	for (j = 0; j < len; j++) {
  	    ASSERT(i == j || pr[i].fd != pr[j].fd);
  	}
      }
  }
  
  
  #if ERTS_POLL_USE_DEVPOLL && defined(DEBUG)
  
  static void
  check_poll_status(ErtsPollSet *ps)
  {
      int i;
      for (i = 0; i < ps->fds_status_len; i++) {
  	int ires;
  	struct pollfd dp_fd;
  	short events = ERTS_POLL_EV_E2N(ps->fds_status[i].events);
  	
  	dp_fd.fd = i;
  	dp_fd.events = (short) 0;
  	dp_fd.revents = (short) 0;
  
  	ires = ioctl(ps->kp_fd, DP_ISPOLLED, &dp_fd);
  
  	if (ires == 0) {
  	    ASSERT(!events);
  	}
  	else if (ires == 1) {
  	    ASSERT(events);
  	    ASSERT(events == dp_fd.revents);
  	}
  	else {
  	    ASSERT(0);
  	}
  	ASSERT(dp_fd.fd == i);
  	ASSERT(ps->fds_status[i].events == ps->fds_status[i].used_events);
      }
  }
  
  #endif /* ERTS_POLL_USE_DEVPOLL */
  #endif /* HARD_DEBUG */
  
  static void
  print_misc_debug_info(void)
  {
  #if ERTS_POLL_DEBUG_PRINT
      erts_printf("erts_poll using: %s lazy_updates:%s\n",
  #if ERTS_POLL_USE_KQUEUE
  		"kqueue"
  #elif ERTS_POLL_USE_EPOLL
  		"epoll"
  #elif ERTS_POLL_USE_DEVPOLL
  		"/dev/poll"
  #elif ERTS_POLL_USE_POLL
  		"poll"
  #elif ERTS_POLL_USE_SELECT
  		"select"
  #endif
  		,
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
  		"true"
  #else
  		"false"
  #endif
  		);
  
      erts_printf("ERTS_POLL_EV_IN=0x%x\n"
  		"ERTS_POLL_EV_OUT=0x%x\n"
  		"ERTS_POLL_EV_NVAL=0x%x\n"
  		"ERTS_POLL_EV_ERR=0x%x\n",
  		ERTS_POLL_EV_IN,
  		ERTS_POLL_EV_OUT,
  		ERTS_POLL_EV_NVAL,
  		ERTS_POLL_EV_ERR);
  
  #ifdef FD_SETSIZE
      erts_printf("FD_SETSIZE=%d\n", FD_SETSIZE);
  #endif
  #endif
  }
  
  #ifdef ERTS_ENABLE_LOCK_COUNT
  void ERTS_POLL_EXPORT(erts_lcnt_enable_pollset_lock_count)(ErtsPollSet *pollset, int enable)
  {
  #if !ERTS_POLL_USE_CONCURRENT_UPDATE
      if(enable) {
          erts_lcnt_install_new_lock_info(&pollset->mtx.lcnt, "pollset_rm", NIL,
              ERTS_LOCK_TYPE_MUTEX | ERTS_LOCK_FLAGS_CATEGORY_IO);
      } else {
          erts_lcnt_uninstall(&pollset->mtx.lcnt);
      }
  #endif
      return;
  }
  #endif
  