<commit_before>/*
 *
 * Copyright 2016, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <grpc/support/port_platform.h>

#ifdef GPR_LINUX_EPOLL

#include "src/core/lib/iomgr/ev_epoll_linux.h"

#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>

#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <grpc/support/tls.h>
#include <grpc/support/useful.h>

#include "src/core/lib/iomgr/ev_posix.h"
#include "src/core/lib/iomgr/iomgr_internal.h"
#include "src/core/lib/iomgr/wakeup_fd_posix.h"
#include "src/core/lib/profiling/timers.h"
#include "src/core/lib/support/block_annotate.h"

struct polling_island;

static int grpc_poller_kick_signum;

/*******************************************************************************
 * Fd Declarations
 */
struct grpc_fd {
  int fd;
  /* refst format:
       bit 0    : 1=Active / 0=Orphaned
       bits 1-n : refcount
     Ref/Unref by two to avoid altering the orphaned bit */
  gpr_atm refst;

  gpr_mu mu;

  /* Indicates that the fd is shutdown and that any pending read/write closures
     should fail */
  bool shutdown;

  /* The fd is either closed or we relinquished control of it. In either cases,
     this indicates that the 'fd' on this structure is no longer valid */
  bool orphaned;

  /* TODO: sreek - Move this to a lockfree implementation */
  grpc_closure *read_closure;
  grpc_closure *write_closure;

  /* The polling island to which this fd belongs to and the mutex protecting the
     the field */
  gpr_mu pi_mu;
  struct polling_island *polling_island;

  struct grpc_fd *freelist_next;
  grpc_closure *on_done_closure;

  /* The pollset that last noticed that the fd is readable */
  grpc_pollset *read_notifier_pollset;

  grpc_iomgr_object iomgr_object;
};

/* Reference counting for fds */
#ifdef GRPC_FD_REF_COUNT_DEBUG
static void fd_ref(grpc_fd *fd, const char *reason, const char *file, int line);
static void fd_unref(grpc_fd *fd, const char *reason, const char *file,
                     int line);
#define GRPC_FD_REF(fd, reason) fd_ref(fd, reason, __FILE__, __LINE__)
#define GRPC_FD_UNREF(fd, reason) fd_unref(fd, reason, __FILE__, __LINE__)
#else
static void fd_ref(grpc_fd *fd);
static void fd_unref(grpc_fd *fd);
#define GRPC_FD_REF(fd, reason) fd_ref(fd)
#define GRPC_FD_UNREF(fd, reason) fd_unref(fd)
#endif

static void fd_global_init(void);
static void fd_global_shutdown(void);

#define CLOSURE_NOT_READY ((grpc_closure *)0)
#define CLOSURE_READY ((grpc_closure *)1)

/*******************************************************************************
 * Polling-island Declarations
 */
/* TODO: sree: Consider making ref_cnt and merged_to to gpr_atm - This would
 * significantly reduce the number of mutex acquisition calls. */
typedef struct polling_island {
  gpr_mu mu;
  int ref_cnt;

  /* Points to the polling_island this merged into.
   * If merged_to is not NULL, all the remaining fields (except mu and ref_cnt)
   * are invalid and must be ignored */
  struct polling_island *merged_to;

  /* The fd of the underlying epoll set */
  int epoll_fd;

  /* The file descriptors in the epoll set */
  size_t fd_cnt;
  size_t fd_capacity;
  grpc_fd **fds;

  /* Polling islands that are no longer needed are kept in a freelist so that
     they can be reused. This field points to the next polling island in the
     free list */
  struct polling_island *next_free;
} polling_island;

/*******************************************************************************
 * Pollset Declarations
 */
struct grpc_pollset_worker {
  pthread_t pt_id; /* Thread id of this worker */
  struct grpc_pollset_worker *next;
  struct grpc_pollset_worker *prev;
};

struct grpc_pollset {
  gpr_mu mu;
  grpc_pollset_worker root_worker;
  bool kicked_without_pollers;

  bool shutting_down;          /* Is the pollset shutting down ? */
  bool finish_shutdown_called; /* Is the 'finish_shutdown_locked()' called ? */
  grpc_closure *shutdown_done; /* Called after after shutdown is complete */

  /* The polling island to which this pollset belongs to and the mutex
     protecting the field */
  /* TODO: sreek: This lock might actually be adding more overhead to the
     critical path (i.e pollset_work() function). Consider removing this lock
     and just using the overall pollset lock */
  gpr_mu pi_mu;
  struct polling_island *polling_island;
};

/*******************************************************************************
 * Pollset-set Declarations
 */
/* TODO: sreek - Change the pollset_set implementation such that a pollset_set
 * directly points to a polling_island (and adding an fd/pollset/pollset_set to
 * the current pollset_set would result in polling island merges. This would
 * remove the need to maintain fd_count here. This will also significantly
 * simplify the grpc_fd structure since we would no longer need to explicitly
 * maintain the orphaned state */
struct grpc_pollset_set {
  gpr_mu mu;

  size_t pollset_count;
  size_t pollset_capacity;
  grpc_pollset **pollsets;

  size_t pollset_set_count;
  size_t pollset_set_capacity;
  struct grpc_pollset_set **pollset_sets;

  size_t fd_count;
  size_t fd_capacity;
  grpc_fd **fds;
};

/*******************************************************************************
 * Polling island Definitions
 */

/* The wakeup fd that is used to wake up all threads in a Polling island. This
   is useful in the polling island merge operation where we need to wakeup all
   the threads currently polling the smaller polling island (so that they can
   start polling the new/merged polling island)

   NOTE: This fd is initialized to be readable and MUST NOT be consumed i.e the
   threads that woke up MUST NOT call grpc_wakeup_fd_consume_wakeup() */
static grpc_wakeup_fd polling_island_wakeup_fd;

/* Polling island freelist */
static gpr_mu g_pi_freelist_mu;
static polling_island *g_pi_freelist = NULL;

/* The caller is expected to hold pi->mu lock before calling this function */
static void polling_island_add_fds_locked(polling_island *pi, grpc_fd **fds,
                                          size_t fd_count, bool add_fd_refs) {
  int err;
  size_t i;
  struct epoll_event ev;

  for (i = 0; i < fd_count; i++) {
    ev.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET);
    ev.data.ptr = fds[i];
    err = epoll_ctl(pi->epoll_fd, EPOLL_CTL_ADD, fds[i]->fd, &ev);

    if (err < 0) {
      if (errno != EEXIST) {
        /* TODO: sreek - We need a better way to bubble up this error instead of
           just logging a message */
        gpr_log(GPR_ERROR, "epoll_ctl add for fd: %d failed with error: %s",
                fds[i]->fd, strerror(errno));
      }

      continue;
    }

    if (pi->fd_cnt == pi->fd_capacity) {
      pi->fd_capacity = GPR_MAX(pi->fd_capacity + 8, pi->fd_cnt * 3 / 2);
      pi->fds = gpr_realloc(pi->fds, sizeof(grpc_fd *) * pi->fd_capacity);
    }

    pi->fds[pi->fd_cnt++] = fds[i];
    if (add_fd_refs) {
      GRPC_FD_REF(fds[i], "polling_island");
    }
  }
}

/* The caller is expected to hold pi->mu before calling this */
static void polling_island_add_wakeup_fd_locked(polling_island *pi,
                                                grpc_wakeup_fd *wakeup_fd) {
  struct epoll_event ev;
  int err;

  ev.events = (uint32_t)(EPOLLIN | EPOLLET);
  ev.data.ptr = wakeup_fd;
  err = epoll_ctl(pi->epoll_fd, EPOLL_CTL_ADD,
                  GRPC_WAKEUP_FD_GET_READ_FD(wakeup_fd), &ev);
  if (err < 0) {
    gpr_log(GPR_ERROR,
            "Failed to add grpc_wake_up_fd (%d) to the epoll set (epoll_fd: %d)"
            ". Error: %s",
            GRPC_WAKEUP_FD_GET_READ_FD(&grpc_global_wakeup_fd), pi->epoll_fd,
            strerror(errno));
  }
}

/* The caller is expected to hold pi->mu lock before calling this function */
static void polling_island_remove_all_fds_locked(polling_island *pi,
                                                 bool remove_fd_refs) {
  int err;
  size_t i;

  for (i = 0; i < pi->fd_cnt; i++) {
    err = epoll_ctl(pi->epoll_fd, EPOLL_CTL_DEL, pi->fds[i]->fd, NULL);
    if (err < 0 && errno != ENOENT) {
      /* TODO: sreek - We need a better way to bubble up this error instead of
      * just logging a message */
      gpr_log(GPR_ERROR, "epoll_ctl deleting fds[%d]: %d failed with error: %s",
              i, pi->fds[i]->fd, strerror(errno));
    }

    if (remove_fd_refs) {
      GRPC_FD_UNREF(pi->fds[i], "polling_island");
    }
  }

  pi->fd_cnt = 0;
}

/* The caller is expected to hold pi->mu lock before calling this function */
static void polling_island_remove_fd_locked(polling_island *pi, grpc_fd *fd,
                                            bool is_fd_closed) {
  int err;
  size_t i;

  /* If fd is already closed, then it would have been automatically been removed
     from the epoll set */
  if (!is_fd_closed) {
    err = epoll_ctl(pi->epoll_fd, EPOLL_CTL_DEL, fd->fd, NULL);
    if (err < 0 && errno != ENOENT) {
      gpr_log(GPR_ERROR, "epoll_ctl deleting fd: %d failed with error; %s",
              fd->fd, strerror(errno));
    }
  }

  for (i = 0; i < pi->fd_cnt; i++) {
    if (pi->fds[i] == fd) {
      pi->fds[i] = pi->fds[--pi->fd_cnt];
      GRPC_FD_UNREF(fd, "polling_island");
      break;
    }
  }
}

static polling_island *polling_island_create(grpc_fd *initial_fd,
                                             int initial_ref_cnt) {
  polling_island *pi = NULL;

  /* Try to get one from the polling island freelist */
  gpr_mu_lock(&g_pi_freelist_mu);
  if (g_pi_freelist != NULL) {
    pi = g_pi_freelist;
    g_pi_freelist = g_pi_freelist->next_free;
    pi->next_free = NULL;
  }
  gpr_mu_unlock(&g_pi_freelist_mu);

  /* Create new polling island if we could not get one from the free list */
  if (pi == NULL) {
    pi = gpr_malloc(sizeof(*pi));
    gpr_mu_init(&pi->mu);
    pi->fd_cnt = 0;
    pi->fd_capacity = 0;
    pi->fds = NULL;
  }

  pi->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
  if (pi->epoll_fd < 0) {
    gpr_log(GPR_ERROR, "epoll_create1() failed with error: %s",
            strerror(errno));
  }
  GPR_ASSERT(pi->epoll_fd >= 0);

  polling_island_add_wakeup_fd_locked(pi, &grpc_global_wakeup_fd);

  pi->ref_cnt = initial_ref_cnt;
  pi->merged_to = NULL;
  pi->next_free = NULL;

  if (initial_fd != NULL) {
    /* It is not really needed to get the pi->mu lock here. If this is a newly
       created polling island (or one that we got from the freelist), no one
       else would be holding a lock to it anyway */
    gpr_mu_lock(&pi->mu);
    polling_island_add_fds_locked(pi, &initial_fd, 1, true);
    gpr_mu_unlock(&pi->mu);
  }

  return pi;
}

static void polling_island_delete(polling_island *pi) {
  GPR_ASSERT(pi->ref_cnt == 0);
  GPR_ASSERT(pi->fd_cnt == 0);

  close(pi->epoll_fd);
  pi->epoll_fd = -1;

  pi->merged_to = NULL;

  gpr_mu_lock(&g_pi_freelist_mu);
  pi->next_free = g_pi_freelist;
  g_pi_freelist = pi;
  gpr_mu_unlock(&g_pi_freelist_mu);
}

void polling_island_unref_and_unlock(polling_island *pi, int unref_by) {
  pi->ref_cnt -= unref_by;
  int ref_cnt = pi->ref_cnt;
  GPR_ASSERT(ref_cnt >= 0);

  gpr_mu_unlock(&pi->mu);

  if (ref_cnt == 0) {
    polling_island_delete(pi);
  }
}

polling_island *polling_island_update_and_lock(polling_island *pi, int unref_by,
                                               int add_ref_by) {
  polling_island *next = NULL;
  gpr_mu_lock(&pi->mu);
  while (pi->merged_to != NULL) {
    next = pi->merged_to;
    polling_island_unref_and_unlock(pi, unref_by);
    pi = next;
    gpr_mu_lock(&pi->mu);
  }

  pi->ref_cnt += add_ref_by;
  return pi;
}

void polling_island_pair_update_and_lock(polling_island **p,
                                         polling_island **q) {
  polling_island *pi_1 = *p;
  polling_island *pi_2 = *q;
  polling_island *temp = NULL;
  bool pi_1_locked = false;
  bool pi_2_locked = false;
  int num_swaps = 0;

  /* Loop until either pi_1 == pi_2 or until we acquired locks on both pi_1
     and pi_2 */
  while (pi_1 != pi_2 && !(pi_1_locked && pi_2_locked)) {
    /* The following assertions are true at this point:
       - pi_1 != pi_2  (else, the while loop would have exited)
       - pi_1 MAY be locked
       - pi_2 is NOT locked */

    /* To maintain lock order consistency, always lock polling_island node with
       lower address first.
       First, make sure pi_1 < pi_2 before proceeding any further. If it turns
       out that pi_1 > pi_2, unlock pi_1 if locked (because pi_2 is not locked
       at this point and having pi_1 locked would violate the lock order) and
       swap pi_1 and pi_2 so that pi_1 becomes less than pi_2 */
    if (pi_1 > pi_2) {
      if (pi_1_locked) {
        gpr_mu_unlock(&pi_1->mu);
        pi_1_locked = false;
      }

      GPR_SWAP(polling_island *, pi_1, pi_2);
      num_swaps++;
    }

    /* The following assertions are true at this point:
       - pi_1 != pi_2
       - pi_1 < pi_2  (address of pi_1 is less than that of pi_2)
       - pi_1 MAYBE locked
       - pi_2 is NOT locked */

    /* Lock pi_1 (if pi_1 is pointing to the terminal node in the list) */
    if (!pi_1_locked) {
      gpr_mu_lock(&pi_1->mu);
      pi_1_locked = true;

      /* If pi_1 is not terminal node (i.e pi_1->merged_to != NULL), we are not
         done locking this polling_island yet. Release the lock on this node and
         advance pi_1 to the next node in the list; and go to the beginning of
         the loop (we can't proceed to locking pi_2 unless we locked pi_1 first)
         */
      if (pi_1->merged_to != NULL) {
        temp = pi_1->merged_to;
        polling_island_unref_and_unlock(pi_1, 1);
        pi_1 = temp;
        pi_1_locked = false;

        continue;
      }
    }

    /* The following assertions are true at this point:
       - pi_1 is locked
       - pi_2 is unlocked
       - pi_1 != pi_2 */

    gpr_mu_lock(&pi_2->mu);
    pi_2_locked = true;

    /* If pi_2 is not terminal node, we are not done locking this polling_island
       yet. Release the lock and update pi_2 to the next node in the list */
    if (pi_2->merged_to != NULL) {
      temp = pi_2->merged_to;
      polling_island_unref_and_unlock(pi_2, 1);
      pi_2 = temp;
      pi_2_locked = false;
    }
  }

  /* At this point, either pi_1 == pi_2 AND/OR we got both locks */
  if (pi_1 == pi_2) {
    /* We may or may not have gotten the lock. If we didn't, walk the rest of
      the polling_island list and get the lock */
    GPR_ASSERT(pi_1_locked || (!pi_1_locked && !pi_2_locked));
    if (!pi_1_locked) {
      pi_1 = pi_2 = polling_island_update_and_lock(pi_1, 2, 0);
    }
  } else {
    GPR_ASSERT(pi_1_locked && pi_2_locked);
    /* If we swapped pi_1 and pi_2 odd number of times, do one more swap so that
       pi_1 and pi_2 point to the same polling_island lists they started off
       with at the beginning of this function (i.e *p and *q respectively) */
    if (num_swaps % 2 > 0) {
      GPR_SWAP(polling_island *, pi_1, pi_2);
    }
  }

  *p = pi_1;
  *q = pi_2;
}

polling_island *polling_island_merge(polling_island *p, polling_island *q) {
  /* Get locks on both the polling islands */
  polling_island_pair_update_and_lock(&p, &q);

  /* TODO: sreek: Think about this scenario some more */
  if (p == q) {
    /* Nothing needs to be done here */
    gpr_mu_unlock(&p->mu);
    return p;
  }

  /* Make sure that p points to the polling island with fewer fds than q */
  if (p->fd_cnt > q->fd_cnt) {
    GPR_SWAP(polling_island *, p, q);
  }

  /* "Merge" p with q i.e move all the fds from p (The one with fewer fds) to q
      )Note that the refcounts on the fds being moved will not change here. This
      is why the last parameter in the following two functions is 'false') */
  polling_island_add_fds_locked(q, p->fds, p->fd_cnt, false);
  polling_island_remove_all_fds_locked(p, false);

  /* Wakeup all the pollers (if any) on p so that they can pickup this change */
  polling_island_add_wakeup_fd_locked(p, &polling_island_wakeup_fd);

  /* The merged polling island inherits all the ref counts of the island merging
     with it */
  q->ref_cnt += p->ref_cnt;

  gpr_mu_unlock(&p->mu);
  gpr_mu_unlock(&q->mu);

  return q;
}

static void polling_island_global_init() {
  gpr_mu_init(&g_pi_freelist_mu);<commit_msg>Fix refcounting bug in polling_island_merge<commit_after>/*
 *
 * Copyright 2016, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <grpc/support/port_platform.h>

#ifdef GPR_LINUX_EPOLL

#include "src/core/lib/iomgr/ev_epoll_linux.h"

#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>

#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <grpc/support/tls.h>
#include <grpc/support/useful.h>

#include "src/core/lib/iomgr/ev_posix.h"
#include "src/core/lib/iomgr/iomgr_internal.h"
#include "src/core/lib/iomgr/wakeup_fd_posix.h"
#include "src/core/lib/profiling/timers.h"
#include "src/core/lib/support/block_annotate.h"

struct polling_island;

static int grpc_poller_kick_signum;

/*******************************************************************************
 * Fd Declarations
 */
struct grpc_fd {
  int fd;
  /* refst format:
       bit 0    : 1=Active / 0=Orphaned
       bits 1-n : refcount
     Ref/Unref by two to avoid altering the orphaned bit */
  gpr_atm refst;

  gpr_mu mu;

  /* Indicates that the fd is shutdown and that any pending read/write closures
     should fail */
  bool shutdown;

  /* The fd is either closed or we relinquished control of it. In either cases,
     this indicates that the 'fd' on this structure is no longer valid */
  bool orphaned;

  /* TODO: sreek - Move this to a lockfree implementation */
  grpc_closure *read_closure;
  grpc_closure *write_closure;

  /* The polling island to which this fd belongs to and the mutex protecting the
     the field */
  gpr_mu pi_mu;
  struct polling_island *polling_island;

  struct grpc_fd *freelist_next;
  grpc_closure *on_done_closure;

  /* The pollset that last noticed that the fd is readable */
  grpc_pollset *read_notifier_pollset;

  grpc_iomgr_object iomgr_object;
};

/* Reference counting for fds */
#ifdef GRPC_FD_REF_COUNT_DEBUG
static void fd_ref(grpc_fd *fd, const char *reason, const char *file, int line);
static void fd_unref(grpc_fd *fd, const char *reason, const char *file,
                     int line);
#define GRPC_FD_REF(fd, reason) fd_ref(fd, reason, __FILE__, __LINE__)
#define GRPC_FD_UNREF(fd, reason) fd_unref(fd, reason, __FILE__, __LINE__)
#else
static void fd_ref(grpc_fd *fd);
static void fd_unref(grpc_fd *fd);
#define GRPC_FD_REF(fd, reason) fd_ref(fd)
#define GRPC_FD_UNREF(fd, reason) fd_unref(fd)
#endif

static void fd_global_init(void);
static void fd_global_shutdown(void);

#define CLOSURE_NOT_READY ((grpc_closure *)0)
#define CLOSURE_READY ((grpc_closure *)1)

/*******************************************************************************
 * Polling-island Declarations
 */
/* TODO: sree: Consider making ref_cnt and merged_to to gpr_atm - This would
 * significantly reduce the number of mutex acquisition calls. */
typedef struct polling_island {
  gpr_mu mu;
  int ref_cnt;

  /* Points to the polling_island this merged into.
   * If merged_to is not NULL, all the remaining fields (except mu and ref_cnt)
   * are invalid and must be ignored */
  struct polling_island *merged_to;

  /* The fd of the underlying epoll set */
  int epoll_fd;

  /* The file descriptors in the epoll set */
  size_t fd_cnt;
  size_t fd_capacity;
  grpc_fd **fds;

  /* Polling islands that are no longer needed are kept in a freelist so that
     they can be reused. This field points to the next polling island in the
     free list */
  struct polling_island *next_free;
} polling_island;

/*******************************************************************************
 * Pollset Declarations
 */
struct grpc_pollset_worker {
  pthread_t pt_id; /* Thread id of this worker */
  struct grpc_pollset_worker *next;
  struct grpc_pollset_worker *prev;
};

struct grpc_pollset {
  gpr_mu mu;
  grpc_pollset_worker root_worker;
  bool kicked_without_pollers;

  bool shutting_down;          /* Is the pollset shutting down ? */
  bool finish_shutdown_called; /* Is the 'finish_shutdown_locked()' called ? */
  grpc_closure *shutdown_done; /* Called after after shutdown is complete */

  /* The polling island to which this pollset belongs to and the mutex
     protecting the field */
  /* TODO: sreek: This lock might actually be adding more overhead to the
     critical path (i.e pollset_work() function). Consider removing this lock
     and just using the overall pollset lock */
  gpr_mu pi_mu;
  struct polling_island *polling_island;
};

/*******************************************************************************
 * Pollset-set Declarations
 */
/* TODO: sreek - Change the pollset_set implementation such that a pollset_set
 * directly points to a polling_island (and adding an fd/pollset/pollset_set to
 * the current pollset_set would result in polling island merges. This would
 * remove the need to maintain fd_count here. This will also significantly
 * simplify the grpc_fd structure since we would no longer need to explicitly
 * maintain the orphaned state */
struct grpc_pollset_set {
  gpr_mu mu;

  size_t pollset_count;
  size_t pollset_capacity;
  grpc_pollset **pollsets;

  size_t pollset_set_count;
  size_t pollset_set_capacity;
  struct grpc_pollset_set **pollset_sets;

  size_t fd_count;
  size_t fd_capacity;
  grpc_fd **fds;
};

/*******************************************************************************
 * Polling island Definitions
 */

/* The wakeup fd that is used to wake up all threads in a Polling island. This
   is useful in the polling island merge operation where we need to wakeup all
   the threads currently polling the smaller polling island (so that they can
   start polling the new/merged polling island)

   NOTE: This fd is initialized to be readable and MUST NOT be consumed i.e the
   threads that woke up MUST NOT call grpc_wakeup_fd_consume_wakeup() */
static grpc_wakeup_fd polling_island_wakeup_fd;

/* Polling island freelist */
static gpr_mu g_pi_freelist_mu;
static polling_island *g_pi_freelist = NULL;

/* The caller is expected to hold pi->mu lock before calling this function */
static void polling_island_add_fds_locked(polling_island *pi, grpc_fd **fds,
                                          size_t fd_count, bool add_fd_refs) {
  int err;
  size_t i;
  struct epoll_event ev;

  for (i = 0; i < fd_count; i++) {
    ev.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET);
    ev.data.ptr = fds[i];
    err = epoll_ctl(pi->epoll_fd, EPOLL_CTL_ADD, fds[i]->fd, &ev);

    if (err < 0) {
      if (errno != EEXIST) {
        /* TODO: sreek - We need a better way to bubble up this error instead of
           just logging a message */
        gpr_log(GPR_ERROR, "epoll_ctl add for fd: %d failed with error: %s",
                fds[i]->fd, strerror(errno));
      }

      continue;
    }

    if (pi->fd_cnt == pi->fd_capacity) {
      pi->fd_capacity = GPR_MAX(pi->fd_capacity + 8, pi->fd_cnt * 3 / 2);
      pi->fds = gpr_realloc(pi->fds, sizeof(grpc_fd *) * pi->fd_capacity);
    }

    pi->fds[pi->fd_cnt++] = fds[i];
    if (add_fd_refs) {
      GRPC_FD_REF(fds[i], "polling_island");
    }
  }
}

/* The caller is expected to hold pi->mu before calling this */
static void polling_island_add_wakeup_fd_locked(polling_island *pi,
                                                grpc_wakeup_fd *wakeup_fd) {
  struct epoll_event ev;
  int err;

  ev.events = (uint32_t)(EPOLLIN | EPOLLET);
  ev.data.ptr = wakeup_fd;
  err = epoll_ctl(pi->epoll_fd, EPOLL_CTL_ADD,
                  GRPC_WAKEUP_FD_GET_READ_FD(wakeup_fd), &ev);
  if (err < 0) {
    gpr_log(GPR_ERROR,
            "Failed to add grpc_wake_up_fd (%d) to the epoll set (epoll_fd: %d)"
            ". Error: %s",
            GRPC_WAKEUP_FD_GET_READ_FD(&grpc_global_wakeup_fd), pi->epoll_fd,
            strerror(errno));
  }
}

/* The caller is expected to hold pi->mu lock before calling this function */
static void polling_island_remove_all_fds_locked(polling_island *pi,
                                                 bool remove_fd_refs) {
  int err;
  size_t i;

  for (i = 0; i < pi->fd_cnt; i++) {
    err = epoll_ctl(pi->epoll_fd, EPOLL_CTL_DEL, pi->fds[i]->fd, NULL);
    if (err < 0 && errno != ENOENT) {
      /* TODO: sreek - We need a better way to bubble up this error instead of
      * just logging a message */
      gpr_log(GPR_ERROR, "epoll_ctl deleting fds[%d]: %d failed with error: %s",
              i, pi->fds[i]->fd, strerror(errno));
    }

    if (remove_fd_refs) {
      GRPC_FD_UNREF(pi->fds[i], "polling_island");
    }
  }

  pi->fd_cnt = 0;
}

/* The caller is expected to hold pi->mu lock before calling this function */
static void polling_island_remove_fd_locked(polling_island *pi, grpc_fd *fd,
                                            bool is_fd_closed) {
  int err;
  size_t i;

  /* If fd is already closed, then it would have been automatically been removed
     from the epoll set */
  if (!is_fd_closed) {
    err = epoll_ctl(pi->epoll_fd, EPOLL_CTL_DEL, fd->fd, NULL);
    if (err < 0 && errno != ENOENT) {
      gpr_log(GPR_ERROR, "epoll_ctl deleting fd: %d failed with error; %s",
              fd->fd, strerror(errno));
    }
  }

  for (i = 0; i < pi->fd_cnt; i++) {
    if (pi->fds[i] == fd) {
      pi->fds[i] = pi->fds[--pi->fd_cnt];
      GRPC_FD_UNREF(fd, "polling_island");
      break;
    }
  }
}

static polling_island *polling_island_create(grpc_fd *initial_fd,
                                             int initial_ref_cnt) {
  polling_island *pi = NULL;

  /* Try to get one from the polling island freelist */
  gpr_mu_lock(&g_pi_freelist_mu);
  if (g_pi_freelist != NULL) {
    pi = g_pi_freelist;
    g_pi_freelist = g_pi_freelist->next_free;
    pi->next_free = NULL;
  }
  gpr_mu_unlock(&g_pi_freelist_mu);

  /* Create new polling island if we could not get one from the free list */
  if (pi == NULL) {
    pi = gpr_malloc(sizeof(*pi));
    gpr_mu_init(&pi->mu);
    pi->fd_cnt = 0;
    pi->fd_capacity = 0;
    pi->fds = NULL;
  }

  pi->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
  if (pi->epoll_fd < 0) {
    gpr_log(GPR_ERROR, "epoll_create1() failed with error: %s",
            strerror(errno));
  }
  GPR_ASSERT(pi->epoll_fd >= 0);

  polling_island_add_wakeup_fd_locked(pi, &grpc_global_wakeup_fd);

  pi->ref_cnt = initial_ref_cnt;
  pi->merged_to = NULL;
  pi->next_free = NULL;

  if (initial_fd != NULL) {
    /* It is not really needed to get the pi->mu lock here. If this is a newly
       created polling island (or one that we got from the freelist), no one
       else would be holding a lock to it anyway */
    gpr_mu_lock(&pi->mu);
    polling_island_add_fds_locked(pi, &initial_fd, 1, true);
    gpr_mu_unlock(&pi->mu);
  }

  return pi;
}

static void polling_island_delete(polling_island *pi) {
  GPR_ASSERT(pi->ref_cnt == 0);
  GPR_ASSERT(pi->fd_cnt == 0);

  close(pi->epoll_fd);
  pi->epoll_fd = -1;

  pi->merged_to = NULL;

  gpr_mu_lock(&g_pi_freelist_mu);
  pi->next_free = g_pi_freelist;
  g_pi_freelist = pi;
  gpr_mu_unlock(&g_pi_freelist_mu);
}

void polling_island_unref_and_unlock(polling_island *pi, int unref_by) {
  pi->ref_cnt -= unref_by;
  int ref_cnt = pi->ref_cnt;
  GPR_ASSERT(ref_cnt >= 0);

  gpr_mu_unlock(&pi->mu);

  if (ref_cnt == 0) {
    polling_island_delete(pi);
  }
}

polling_island *polling_island_update_and_lock(polling_island *pi, int unref_by,
                                               int add_ref_by) {
  polling_island *next = NULL;
  gpr_mu_lock(&pi->mu);
  while (pi->merged_to != NULL) {
    next = pi->merged_to;
    polling_island_unref_and_unlock(pi, unref_by);
    pi = next;
    gpr_mu_lock(&pi->mu);
  }

  pi->ref_cnt += add_ref_by;
  return pi;
}

void polling_island_pair_update_and_lock(polling_island **p,
                                         polling_island **q) {
  polling_island *pi_1 = *p;
  polling_island *pi_2 = *q;
  polling_island *temp = NULL;
  bool pi_1_locked = false;
  bool pi_2_locked = false;
  int num_swaps = 0;

  /* Loop until either pi_1 == pi_2 or until we acquired locks on both pi_1
     and pi_2 */
  while (pi_1 != pi_2 && !(pi_1_locked && pi_2_locked)) {
    /* The following assertions are true at this point:
       - pi_1 != pi_2  (else, the while loop would have exited)
       - pi_1 MAY be locked
       - pi_2 is NOT locked */

    /* To maintain lock order consistency, always lock polling_island node with
       lower address first.
       First, make sure pi_1 < pi_2 before proceeding any further. If it turns
       out that pi_1 > pi_2, unlock pi_1 if locked (because pi_2 is not locked
       at this point and having pi_1 locked would violate the lock order) and
       swap pi_1 and pi_2 so that pi_1 becomes less than pi_2 */
    if (pi_1 > pi_2) {
      if (pi_1_locked) {
        gpr_mu_unlock(&pi_1->mu);
        pi_1_locked = false;
      }

      GPR_SWAP(polling_island *, pi_1, pi_2);
      num_swaps++;
    }

    /* The following assertions are true at this point:
       - pi_1 != pi_2
       - pi_1 < pi_2  (address of pi_1 is less than that of pi_2)
       - pi_1 MAYBE locked
       - pi_2 is NOT locked */

    /* Lock pi_1 (if pi_1 is pointing to the terminal node in the list) */
    if (!pi_1_locked) {
      gpr_mu_lock(&pi_1->mu);
      pi_1_locked = true;

      /* If pi_1 is not terminal node (i.e pi_1->merged_to != NULL), we are not
         done locking this polling_island yet. Release the lock on this node and
         advance pi_1 to the next node in the list; and go to the beginning of
         the loop (we can't proceed to locking pi_2 unless we locked pi_1 first)
         */
      if (pi_1->merged_to != NULL) {
        temp = pi_1->merged_to;
        polling_island_unref_and_unlock(pi_1, 1);
        pi_1 = temp;
        pi_1_locked = false;

        continue;
      }
    }

    /* The following assertions are true at this point:
       - pi_1 is locked
       - pi_2 is unlocked
       - pi_1 != pi_2 */

    gpr_mu_lock(&pi_2->mu);
    pi_2_locked = true;

    /* If pi_2 is not terminal node, we are not done locking this polling_island
       yet. Release the lock and update pi_2 to the next node in the list */
    if (pi_2->merged_to != NULL) {
      temp = pi_2->merged_to;
      polling_island_unref_and_unlock(pi_2, 1);
      pi_2 = temp;
      pi_2_locked = false;
    }
  }

  /* At this point, either pi_1 == pi_2 AND/OR we got both locks */
  if (pi_1 == pi_2) {
    /* We may or may not have gotten the lock. If we didn't, walk the rest of
      the polling_island list and get the lock */
    GPR_ASSERT(pi_1_locked || (!pi_1_locked && !pi_2_locked));
    if (!pi_1_locked) {
      pi_1 = pi_2 = polling_island_update_and_lock(pi_1, 2, 0);
    }
  } else {
    GPR_ASSERT(pi_1_locked && pi_2_locked);
    /* If we swapped pi_1 and pi_2 odd number of times, do one more swap so that
       pi_1 and pi_2 point to the same polling_island lists they started off
       with at the beginning of this function (i.e *p and *q respectively) */
    if (num_swaps % 2 > 0) {
      GPR_SWAP(polling_island *, pi_1, pi_2);
    }
  }

  *p = pi_1;
  *q = pi_2;
}

polling_island *polling_island_merge(polling_island *p, polling_island *q) {
  /* Get locks on both the polling islands */
  polling_island_pair_update_and_lock(&p, &q);

  if (p == q) {
    /* Nothing needs to be done here */
    gpr_mu_unlock(&p->mu);
    return p;
  }

  /* Make sure that p points to the polling island with fewer fds than q */
  if (p->fd_cnt > q->fd_cnt) {
    GPR_SWAP(polling_island *, p, q);
  }

  /* "Merge" p with q i.e move all the fds from p (The one with fewer fds) to q
      Note that the refcounts on the fds being moved will not change here. This
      is why the last parameter in the following two functions is 'false') */
  polling_island_add_fds_locked(q, p->fds, p->fd_cnt, false);
  polling_island_remove_all_fds_locked(p, false);

  /* Wakeup all the pollers (if any) on p so that they can pickup this change */
  polling_island_add_wakeup_fd_locked(p, &polling_island_wakeup_fd);

  /* - The merged polling island (i.e q) inherits all the ref counts of the
       island merging with it (i.e p)
     - The island p will lose a ref count */
  q->ref_cnt += p->ref_cnt;
  p->ref_cnt--;

  gpr_mu_unlock(&p->mu);
  gpr_mu_unlock(&q->mu);

  return q;
}

static void polling_island_global_init() {
  gpr_mu_init(&g_pi_freelist_mu);
{'fraction_non_alphanumeric': 0.11539320822162645, 'fraction_numerical': 0.007540214477211796, 'mean_word_length': 3.1264116155796264, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 206, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 28, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4920739', 'n_tokens_mistral': 12563, 'n_tokens_neox': 12046, 'n_words': 3792}