/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 *
 */

/*
 The epoll proactor works with multiple concurrent threads.  If there is no work to do,
 one thread temporarily becomes the poller thread, while other inbound threads suspend
 waiting for work.  The poller calls epoll_wait(), generates work lists, resumes suspended
 threads, and grabs work for itself or polls again.

 A serialized grouping of Proton events is a task (connection, listener, proactor).
 Each has multiple pollable fds that make it schedulable.  E.g. a connection could have a
 socket fd, (indirect) timerfd, and (indirect) eventfd all signaled in a single epoll_wait().

 At the conclusion of each
      N = epoll_wait(..., N_MAX, timeout)

 there will be N epoll events and M tasks on a ready list.  M can be very large in a
 server with many active connections. The poller makes the tasks "runnable" if they are
 not already running.  A running task cannot be made runnable again until it completes
 a chunk of work and calls unassign_thread().  (N + M - duplicates) tasks will be
 scheduled.  A new poller will occur when next_runnable() returns NULL.

 A task may have its own dedicated kernel file descriptor (socket, timerfd) which can be seen
 by the poller to make the task runnable.  A task may aslso be scheduled to run by placing it
 on a ready queue which is monitored by the poller via two eventfds.

 Lock ordering - never add locks right to left:
    task -> sched -> ready
    non-proactor-task -> proactor-task
    tslot -> sched

 TODO: doc new work: warm (assigned), earmarked (assigned), runnables (unordered), sched_ready
 list (ordered), resched list (ordered).
 TODO: document role of sched_pending and how sched_XXX (i.e. sched_interrupt)
 transitions from "private to the scheduler" to "visible to the task".
 TODO: document task.working duration can be long: from xxx_process() to xxx_done() or null batch.
 */


/* Enable POSIX features beyond c99 for modern pthread and standard strerror_r() */
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
/* Avoid GNU extensions, in particular the incompatible alternative strerror_r() */
#undef _GNU_SOURCE

#include "epoll-internal.h"
#include "proactor-internal.h"
#include "core/engine-internal.h"
#include "core/logger_private.h"
#include "core/util.h"

#include <proton/condition.h>
#include <proton/connection_driver.h>
#include <proton/engine.h>
#include <proton/proactor.h>
#include <proton/transport.h>
#include <proton/listener.h>
#include <proton/netaddr.h>
#include <proton/raw_connection.h>

#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <fcntl.h>
#include <netinet/tcp.h>
#include <sys/eventfd.h>
#include <limits.h>
#include <time.h>
#include <alloca.h>

#include "./netaddr-internal.h" /* Include after socket/inet headers */

// TODO: logging in general
// SIGPIPE?
// Can some of the mutexes be spinlocks (any benefit over adaptive pthread mutex)?
//   Maybe futex is even better?
// See other "TODO" in code.
//
// Consider case of large number of ready tasks: next_event_batch() could start by
// looking for ready tasks before a kernel call to epoll_wait(), or there
// could be several eventfds with random assignment of wakeables.


/* Like strerror_r but provide a default message if strerror_r fails */
void pstrerror(int err, strerrorbuf msg) {
  int e = strerror_r(err, msg, sizeof(strerrorbuf));
  if (e) snprintf(msg, sizeof(strerrorbuf), "unknown error %d", err);
}

/* epoll_ctl()/epoll_wait() do not form a memory barrier, so cached memory
   writes to struct epoll_extended_t in the EPOLL_ADD thread might not be
   visible to epoll_wait() thread. This function creates a memory barrier,
   called before epoll_ctl() and after epoll_wait()
*/
static void memory_barrier(epoll_extended_t *ee) {
  // Mutex lock/unlock has the side-effect of being a memory barrier.
  lock(&ee->barrier_mutex);
  unlock(&ee->barrier_mutex);
}

/* Read from an event FD */
static uint64_t read_uint64(int fd) {
  uint64_t result = 0;
  ssize_t n = read(fd, &result, sizeof(result));
  if (n != sizeof(result) && !(n < 0 && errno == EAGAIN)) {
    EPOLL_FATAL("eventfd read error", errno);
  }
  return result;
}

// ========================================================================
// Proactor common code
// ========================================================================

const char *AMQP_PORT = "5672";
const char *AMQP_PORT_NAME = "amqp";

// The number of times a connection event batch may be replenished for
// a thread between calls to wait().  Some testing shows that
// increasing this value above 1 actually slows performance slightly
// and increases latency.
#define HOG_MAX 1

/* pn_proactor_t and pn_listener_t are plain C structs with normal memory management.
   Class definitions are for identification as pn_event_t context only.
*/
PN_STRUCT_CLASSDEF(pn_proactor)
PN_STRUCT_CLASSDEF(pn_listener)

bool start_polling(epoll_extended_t *ee, int epollfd) {
  if (ee->polling)
    return false;
  ee->polling = true;
  struct epoll_event ev = {0};
  ev.data.ptr = ee;
  ev.events = ee->wanted | EPOLLONESHOT;
  memory_barrier(ee);
  return (epoll_ctl(epollfd, EPOLL_CTL_ADD, ee->fd, &ev) == 0);
}

void stop_polling(epoll_extended_t *ee, int epollfd) {
  // TODO: check for error, return bool or just log?
  // TODO: is EPOLL_CTL_DEL ever needed beyond auto de-register when ee->fd is closed?
  if (ee->fd == -1 || !ee->polling || epollfd == -1)
    return;
  struct epoll_event ev = {0};
  ev.data.ptr = ee;
  ev.events = 0;
  memory_barrier(ee);
  if (epoll_ctl(epollfd, EPOLL_CTL_DEL, ee->fd, &ev) == -1)
    EPOLL_FATAL("EPOLL_CTL_DEL", errno);
  ee->fd = -1;
  ee->polling = false;
}

void rearm_polling(epoll_extended_t *ee, int epollfd) {
  struct epoll_event ev = {0};
  ev.data.ptr = ee;
  ev.events = ee->wanted | EPOLLONESHOT;
  memory_barrier(ee);
  if (epoll_ctl(epollfd, EPOLL_CTL_MOD, ee->fd, &ev) == -1)
    EPOLL_FATAL("arming polled file descriptor", errno);
}

static void rearm(pn_proactor_t *p, epoll_extended_t *ee) {
  rearm_polling(ee, p->epollfd);
}

/*
 * The proactor maintains a number of serialization tasks: each
 * connection, each listener, the proactor itself.  The serialization
 * is presented to the application via each associated event batch.
 *
 * A task will only ever run in a single thread at a time.
 *
 * Other threads of excution (including user threads) can interact with a
 * particular task (i.e. connection wake or proactor interrupt). Mutexes are
 * needed here for shared access to task data.  schedule()/notify_poller() are
 * used to ensure a task will run to act on the changed data.
 *
 * To minimize trips through the kernel, schedule() is a no-op if the task is
 * already running or about to run.  Conversely, a task must never stop working
 * without checking state that may have been recently changed by another thread.
 *
 * External wake operations, like pn_connection_wake() or expired timers are
 * built on top of this schedule() mechanism.
 *
 * pn_proactor_interrupt() must be async-signal-safe so it has a dedicated
 * eventfd to allow a lock-free pn_proactor_interrupt() implementation.
 */


// Fake thread for temporarily disabling the scheduling of a task.
static struct tslot_t *RESCHEDULE_PLACEHOLDER = (struct tslot_t*) -1;

void task_init(task_t *tsk, task_type_t t, pn_proactor_t *p) {
  memset(tsk, 0, sizeof(*tsk));
  pmutex_init(&tsk->mutex);
  tsk->proactor = p;
  tsk->type = t;
}

/*
 * schedule() strategy with eventfd:
 *  - tasks can be in the ready list only once
 *  - a scheduling thread will only activate the eventfd if ready_list_active is false
 * There is a single rearm between ready list empty and non-empty
 *
 * There can potentially be many tasks with work pending.
 *
 * The ready list is in two parts.  The front is the chunk the
 * poller will process until the next epoll_wait().  sched_ready
 * indicates which chunk it is on. The task may already be running or
 * scheduled to run.
 *
 * The task must be actually running to absorb task_t->ready.
 *
 * The ready list can keep growing while popping ready tasks.  The list between
 * sched_ready_first and sched_ready_last are protected by the sched
 * lock (for pop operations), sched_ready_last to ready_list_last are
 * protected by the eventfd mutex (for add operations).  Both locks
 * are needed to cross or reconcile the two portions of the list.
 */

// Call with sched lock held and sched_ready_count > 0.
static task_t *sched_ready_pop_front(pn_proactor_t *p) {
  // every task on the sched_ready_list is either currently running,
  // or to be scheduled.  schedule() will not "see" any of the ready_next
  // pointers until ready and working have transitioned to 0
  // and false, when a task stops working.
  //
  // every task must transition as:
  //
  // !ready .. schedule() .. on ready_list .. on sched_ready_list .. working task .. !sched_ready && !ready
  //
  // Intervening locks at each transition ensures ready_next has memory coherence throughout the ready task scheduling cycle.
  assert (p->sched_ready_count);
  task_t *tsk = p->sched_ready_first;
  p->sched_ready_count--;
  if (tsk == p->sched_ready_last) {
    p->sched_ready_first = p->sched_ready_last = NULL;
  } else {
    p->sched_ready_first = tsk->ready_next;
  }
  if (p->sched_ready_count == 0) {
    assert(!p->sched_ready_first);
    p->sched_ready_pending = false;
  }
  return tsk;
}

// Call only as the poller task that has already called schedule_ready_list() and already
// incremented p->ready_list_generation.  All list elements before sched_ready_last have
// correct generation from mutex barrier and cannot have tsk->ready_generation set to a
// new generation until after the poller task releases the sched lock and allows tsk to
// run again.
inline static bool on_sched_ready_list(task_t *tsk, pn_proactor_t *p) {
  return tsk->ready_generation && (tsk->ready_generation != p->ready_list_generation);
}

// part1: call with tsk->owner lock held, return true if notify_poller required by caller.
// Nothing to do if the task is currently at work or work is already pending.
bool schedule(task_t *tsk) {
  bool notify = false;

  if (!tsk->ready) {
    if (!tsk->working) {
      tsk->ready = true;
      pn_proactor_t *p = tsk->proactor;
      lock(&p->eventfd_mutex);
      assert(tsk->ready_generation == 0);  // Can't be on list twice
      tsk->ready_next = NULL;
      tsk->ready_generation = p->ready_list_generation;
      p->ready_list_count++;
      if (!p->ready_list_first) {
        p->ready_list_first = p->ready_list_last = tsk;
      } else {
        p->ready_list_last->ready_next = tsk;
        p->ready_list_last = tsk;
      }
      if (!p->ready_list_active) {
        // unblock poller via the eventfd
        p->ready_list_active = true;
        notify = true;
      }
      unlock(&p->eventfd_mutex);
    }
  }

  return notify;
}

// part2: unblock epoll_wait().  Make OS call without lock held.
void notify_poller(pn_proactor_t *p) {
  if (p->eventfd == -1)
    return;
  rearm(p, &p->epoll_schedule);
}

// call with task lock held from xxx_process().
void schedule_done(task_t *tsk) {
  assert(tsk->ready);
  lock(&tsk->proactor->eventfd_mutex);
  assert(tsk->ready_generation != 0);
  tsk->ready_generation = 0;
  unlock(&tsk->proactor->eventfd_mutex);
  tsk->ready = false;
}


/*
 * Scheduler/poller
*/

// Internal use only
/* How long to defer suspending */
static int pni_spins = 0;
/* Prefer immediate running by poller over warm running by suspended thread */
static bool pni_immediate = false;
/* Toggle use of warm scheduling */
static int pni_warm_sched = true;


// Call with sched lock
static void suspend_list_add_tail(pn_proactor_t *p, tslot_t *ts) {
  LL_ADD(p, suspend_list, ts);
}

// Call with sched lock
static void suspend_list_insert_head(pn_proactor_t *p, tslot_t *ts) {
  ts->suspend_list_next = p->suspend_list_head;
  ts->suspend_list_prev = NULL;
  if (p->suspend_list_head)
    p->suspend_list_head->suspend_list_prev = ts;
  else
    p->suspend_list_tail = ts;
  p->suspend_list_head = ts;
}

// Call with sched lock
static void suspend(pn_proactor_t *p, tslot_t *ts) {
  if (ts->state == NEW)
    suspend_list_add_tail(p, ts);
  else
    suspend_list_insert_head(p, ts);
  p->suspend_list_count++;
  ts->state = SUSPENDED;
  ts->scheduled = false;
  unlock(&p->sched_mutex);

  lock(&ts->mutex);
  if (pni_spins && !ts->scheduled) {
    // Medium length spinning tried here.  Raises cpu dramatically,
    // unclear throughput or latency benefit (not seen where most
    // expected, modest at other times).
    bool locked = true;
    for (volatile int i = 0; i < pni_spins; i++) {
      if (locked) {
        unlock(&ts->mutex);
        locked = false;
      }
      if ((i % 1000) == 0) {
        locked = (pthread_mutex_trylock(&ts->mutex) == 0);
      }
      if (ts->scheduled) break;
    }
    if (!locked)
      lock(&ts->mutex);
  }

  ts->suspended = true;
  while (!ts->scheduled) {
    pthread_cond_wait(&ts->cond, &ts->mutex);
  }
  ts->suspended = false;
  unlock(&ts->mutex);
  lock(&p->sched_mutex);
  assert(ts->state == PROCESSING);
}

// Call with no lock
static void resume(pn_proactor_t *p, tslot_t *ts) {
  lock(&ts->mutex);
  ts->scheduled = true;
  if (ts->suspended) {
    pthread_cond_signal(&ts->cond);
  }
  unlock(&ts->mutex);
}

// Call with no lock
void pni_resume(pn_proactor_t *p, tslot_t *ts) {
  resume(p, ts);
}

// Call with shed_lock held
// Caller must resume() return value if not null
static tslot_t *resume_one_thread(pn_proactor_t *p) {
  // If pn_proactor_get has an early return, we need to resume one suspended thread (if any)
  // to be the new poller.

  tslot_t *ts = p->suspend_list_head;
  if (ts) {
    LL_REMOVE(p, suspend_list, ts);
    p->suspend_list_count--;
    ts->state = PROCESSING;
  }
  return ts;
}

// Call with sched lock
static void assign_thread(tslot_t *ts, task_t *tsk) {
  assert(!tsk->runner);
  tsk->runner = ts;
  tsk->prev_runner = NULL;
  tsk->runnables_idx = 0;
  ts->task = tsk;
  ts->prev_task = NULL;
}

// call with sched lock
static inline task_t *resched_pop_front(pn_proactor_t *p) {
  assert(p->resched_cutoff);
  task_t *tsk = p->resched_first;
  p->resched_first = tsk->resched_next;
  p->polled_resched_count--;
  if (!p->resched_first)
    p->resched_last = NULL;
  if (tsk == p->resched_cutoff) {
    assert(p->polled_resched_count == 0);
    p->resched_cutoff = NULL;
  }
  tsk->resched_next = NULL;
  return tsk;
}

// Call with sched lock.
// If true returned, caller must call notify_poller() after releasing the lock.
// If resume_thread is set, the caller must call resume() after releasing the lock.
bool unassign_thread(pn_proactor_t *p, tslot_t *ts, tslot_state new_state, tslot_t **resume_thread) {
  task_t *tsk = ts->task;
  bool notify = false;
  bool deleting = (ts->state == DELETING);
  *resume_thread = NULL;
  ts->task = NULL;
  ts->state = new_state;
  if (tsk) {
    tsk->runner = NULL;
    tsk->prev_runner = ts;
  }

  // Check if unseen events or schedule() calls occurred while task was working.

  if (tsk && !deleting) {
    ts->prev_task = tsk;
    if (tsk->sched_pending) {
      // New work arrived, reschedule it:
      tsk->runner = RESCHEDULE_PLACEHOLDER;  // Block tsk from being scheduled untl resched list is processed.
      assert(!tsk->resched_next);
      if (p->resched_last) {
        p->resched_last->resched_next = tsk;
        p->resched_last = tsk;
      } else {
        p->resched_first = p->resched_last = tsk;
      }
      p->resched_count++;
      if (p->poller_suspended) {
        lock(&p->eventfd_mutex);
        if (!p->ready_list_active) {
          p->ready_list_active = true;
          notify = true;
        }
        unlock(&p->eventfd_mutex);
      }
    }
  }

  // Earmark drain accounting.
  if (ts->earmark_override) {
    // This thread "stole" the task previously assigned to thread ts->earmark_override.
    if (ts->earmark_override->generation == ts->earmark_override_gen) {
      // Other (overridden) thread not seen since this thread completed the pending work on the task.
      // Thread is perhaps gone forever, which may leave us short of a poller thread and hanging.
      // Find a thread to resume if available.  Worst case is a spurious resume/suspend by an idle thread.
      *resume_thread = resume_one_thread(p);
    }
    ts->earmark_override = NULL;
  }

  return notify;
}

// Call with sched lock
static void earmark_thread(tslot_t *ts, task_t *tsk) {
  assign_thread(ts, tsk);
  ts->earmarked = true;
  tsk->proactor->earmark_count++;
}

// Call with sched lock
static void remove_earmark(tslot_t *ts) {
  task_t *tsk = ts->task;
  ts->task = NULL;
  tsk->runner = NULL;
  ts->earmarked = false;
  tsk->proactor->earmark_count--;
}

// Call with sched lock
static void make_runnable(task_t *tsk) {
  pn_proactor_t *p = tsk->proactor;
  assert(p->n_runnables <= p->runnables_capacity);
  assert(!tsk->runnables_idx);
  if (tsk->runner) return;

  // Track it as normal or warm or earmarked
  if (pni_warm_sched) {
    tslot_t *ts = tsk->prev_runner;
    if (ts && ts->prev_task == tsk) {
      if (ts->state == SUSPENDED || ts->state == PROCESSING) {
        if (p->n_warm_runnables < p->thread_capacity) {
          p->warm_runnables[p->n_warm_runnables++] = tsk;
          assign_thread(ts, tsk);
        }
        else {
          p->runnables[p->n_runnables] = tsk;
          tsk->runnables_idx = p->n_runnables + 1; // off by one accounting
          p->n_runnables++;
        }
        return;
      }
      if (ts->state == UNUSED && !p->earmark_drain) {
        earmark_thread(ts, tsk);
        p->last_earmark = ts;
        return;
      }
    }
  }
  p->runnables[p->n_runnables] = tsk;
  tsk->runnables_idx = p->n_runnables + 1; // off by one accounting
  p->n_runnables++;
}



void psocket_init(psocket_t* ps, epoll_type_t type)
{
  ps->epoll_io.fd = -1;
  ps->epoll_io.type = type;
  ps->epoll_io.wanted = 0;
  ps->epoll_io.polling = false;
}


/* Protects read/update of pn_connection_t pointer to it's pconnection_t
 *
 * Global because pn_connection_wake()/pn_connection_proactor() navigate from
 * the pn_connection_t before we know the proactor or driver. Critical sections
 * are small: only get/set of the pn_connection_t driver pointer.
 *
 * TODO: replace mutex with atomic load/store
 */
static pthread_mutex_t driver_ptr_mutex = PTHREAD_MUTEX_INITIALIZER;

static pconnection_t *get_pconnection(pn_connection_t* c) {
  if (!c) return NULL;
  lock(&driver_ptr_mutex);
  pn_connection_driver_t *d = *pn_connection_driver_ptr(c);
  unlock(&driver_ptr_mutex);
  if (!d) return NULL;
  return containerof(d, pconnection_t, driver);
}

static void set_pconnection(pn_connection_t* c, pconnection_t *pc) {
  lock(&driver_ptr_mutex);
  *pn_connection_driver_ptr(c) = pc ? &pc->driver : NULL;
  unlock(&driver_ptr_mutex);
}

static pn_event_batch_t *pconnection_process(pconnection_t *pc, uint32_t events, bool sched_ready, bool topup);
static void write_flush(pconnection_t *pc);
static void listener_begin_close(pn_listener_t* l);
static bool poller_do_epoll(struct pn_proactor_t* p, tslot_t *ts, bool can_block);
static void poller_done(struct pn_proactor_t* p, tslot_t *ts);

static inline pconnection_t *psocket_pconnection(psocket_t* ps) {
  return ps->epoll_io.type == PCONNECTION_IO ? containerof(ps, pconnection_t, psocket) : NULL;
}

static inline pn_listener_t *psocket_listener(psocket_t* ps) {
  return ps->epoll_io.type == LISTENER_IO ? containerof(ps, acceptor_t, psocket)->listener : NULL;
}

static inline acceptor_t *psocket_acceptor(psocket_t* ps) {
  return ps->epoll_io.type == LISTENER_IO ? containerof(ps, acceptor_t, psocket) : NULL;
}

static inline pconnection_t *task_pconnection(task_t *t) {
  return t->type == PCONNECTION ? containerof(t, pconnection_t, task) : NULL;
}

static inline pn_listener_t *task_listener(task_t *t) {
  return t->type == LISTENER ? containerof(t, pn_listener_t, task) : NULL;
}

static pn_event_t *listener_batch_next(pn_event_batch_t *batch);
static pn_event_t *proactor_batch_next(pn_event_batch_t *batch);
static pn_event_t *pconnection_batch_next(pn_event_batch_t *batch);

static inline pn_proactor_t *batch_proactor(pn_event_batch_t *batch) {
  return (batch->next_event == proactor_batch_next) ?
    containerof(batch, pn_proactor_t, batch) : NULL;
}

static inline pn_listener_t *batch_listener(pn_event_batch_t *batch) {
  return (batch->next_event == listener_batch_next) ?
    containerof(batch, pn_listener_t, batch) : NULL;
}

static inline pconnection_t *batch_pconnection(pn_event_batch_t *batch) {
  return (batch->next_event == pconnection_batch_next) ?
    containerof(batch, pconnection_t, batch) : NULL;
}

static void psocket_error_str(psocket_t *ps, const char *msg, const char* what) {
  pconnection_t *pc = psocket_pconnection(ps);
  if (pc) {
    pn_connection_driver_t *driver = &pc->driver;
    pn_connection_driver_bind(driver); /* Bind so errors will be reported */
    pni_proactor_set_cond(pn_transport_condition(driver->transport), what, pc->host, pc->port, msg);
    pn_connection_driver_close(driver);
    return;
  }
  pn_listener_t *l = psocket_listener(ps);
  if (l) {
    pni_proactor_set_cond(l->condition, what, l->host, l->port, msg);
    listener_begin_close(l);
    return;
  }
}

static void psocket_error(psocket_t *ps, int err, const char* what) {
  strerrorbuf msg;
  pstrerror(err, msg);
  psocket_error_str(ps, msg, what);
}

static void psocket_gai_error(psocket_t *ps, int gai_err, const char* what) {
  psocket_error_str(ps, gai_strerror(gai_err), what);
}

static void listener_accepted_append(pn_listener_t *listener, accepted_t item) {
  if (listener->pending_first+listener->pending_count >= listener->backlog) return;

  listener->pending_accepteds[listener->pending_first+listener->pending_count] = item;
  listener->pending_count++;
}

accepted_t *listener_accepted_next(pn_listener_t *listener) {
  if (!listener->pending_count) return NULL;

  listener->pending_count--;
  return &listener->pending_accepteds[listener->pending_first++];
}

static void acceptor_list_append(acceptor_t **start, acceptor_t *item) {
  assert(item->next == NULL);
  if (*start) {
    acceptor_t *end = *start;
    while (end->next)
      end = end->next;
    end->next = item;
  }
  else *start = item;
}

static acceptor_t *acceptor_list_next(acceptor_t **start) {
  acceptor_t *item = *start;
  if (*start) *start = (*start)->next;
  if (item) item->next = NULL;
  return item;
}

// Add an overflowing acceptor to the overflow list. Called with listener task lock held.
static void acceptor_set_overflow(acceptor_t *a) {
  a->overflowed = true;
  a->listener->overflow_count++;
  pn_proactor_t *p = a->listener->task.proactor;
  lock(&p->overflow_mutex);
  acceptor_list_append(&p->overflow, a);
  unlock(&p->overflow_mutex);
}

/* TODO aconway 2017-06-08: we should also call proactor_rearm_overflow after a fixed delay,
   even if the proactor has not freed any file descriptors, since other parts of the process
   might have*/

// Activate overflowing listeners, called when there may be available file descriptors.
static void proactor_rearm_overflow(pn_proactor_t *p) {
  lock(&p->overflow_mutex);
  acceptor_t* ovflw = p->overflow;
  p->overflow = NULL;
  unlock(&p->overflow_mutex);
  acceptor_t *a = acceptor_list_next(&ovflw);
  while (a) {
    pn_listener_t *l = a->listener;
    lock(&l->task.mutex);
    bool rearming = !l->task.closing;
    bool notify = false;
    assert(!a->armed);
    assert(a->overflowed);
    a->overflowed = false;
    l->overflow_count++;
    if (rearming) {
      rearm(p, &a->psocket.epoll_io);
      a->armed = true;
    }
    else notify = schedule(&l->task);
    unlock(&l->task.mutex);
    if (notify) notify_poller(p);
    a = acceptor_list_next(&ovflw);
  }
}

// Close an FD and rearm overflow listeners.  Call with no listener locks held.
int pclosefd(pn_proactor_t *p, int fd) {
  int err = close(fd);
  if (!err && !p->shutting_down) proactor_rearm_overflow(p);
  return err;
}


// ========================================================================
// pconnection
// ========================================================================

static void pconnection_tick(pconnection_t *pc);

static const char *pconnection_setup(pconnection_t *pc, pn_proactor_t *p, pn_connection_t *c, pn_transport_t *t, bool server, const char *addr, size_t addrlen)
{
  memset(pc, 0, sizeof(*pc));

  if (pn_connection_driver_init(&pc->driver, c, t) != 0) {
    free(pc);
    return "pn_connection_driver_init failure";
  }
  if (!(pc->timer = pni_timer(&p->timer_manager, pc))) {
    free(pc);
    return "connection timer creation failure";
  }


  task_init(&pc->task, PCONNECTION, p);
  psocket_init(&pc->psocket, PCONNECTION_IO);
  pni_parse_addr(addr, pc->addr_buf, addrlen+1, &pc->host, &pc->port);
  pc->new_events = 0;
  pc->tick_pending = false;
  pc->queued_disconnect = false;
  pc->disconnect_condition = NULL;

  pc->current_arm = 0;
  pc->connected = false;
  pc->read_blocked = true;
  pc->write_blocked = true;
  pc->disconnected = false;
  pc->output_drained = false;
  pc->wbuf_completed = 0;
  pc->wbuf_remaining = 0;
  pc->wbuf_current = NULL;
  pc->hog_count = 0;
  pc->batch.next_event = pconnection_batch_next;

  if (server) {
    pn_transport_set_server(pc->driver.transport);
  }

  pmutex_init(&pc->rearm_mutex);

  /* Set the pconnection_t backpointer last.
     Connections that were released by pn_proactor_release_connection() must not reveal themselves
     to be re-associated with a proactor till setup is complete.
   */
  set_pconnection(pc->driver.connection, pc);

  return NULL;
}

// Call with lock held and closing == true (i.e. pn_connection_driver_finished() == true), no pending timer.
// Return true when all possible outstanding epoll events associated with this pconnection have been processed.
static inline bool pconnection_is_final(pconnection_t *pc) {
  return !pc->current_arm && !pc->task.ready && !pc->tick_pending;
}

static void pconnection_final_free(pconnection_t *pc) {
  // Ensure any lingering pconnection_rearm is all done.
  lock(&pc->rearm_mutex);  unlock(&pc->rearm_mutex);

  if (pc->driver.connection) {
    set_pconnection(pc->driver.connection, NULL);
  }
  if (pc->addrinfo) {
    freeaddrinfo(pc->addrinfo);
  }
  pmutex_finalize(&pc->rearm_mutex);
  pn_condition_free(pc->disconnect_condition);
  pn_connection_driver_destroy(&pc->driver);
  pni_timer_free(pc->timer);
  task_finalize(&pc->task);
  free(pc);
}


// call without lock
static void pconnection_cleanup(pconnection_t *pc) {
  assert(pconnection_is_final(pc));
  int fd = pc->psocket.epoll_io.fd;
  stop_polling(&pc->psocket.epoll_io, pc->task.proactor->epollfd);
  if (fd != -1)
    pclosefd(pc->task.proactor, fd);

  lock(&pc->task.mutex);
  bool can_free = proactor_remove(&pc->task);
  unlock(&pc->task.mutex);
  if (can_free)
    pconnection_final_free(pc);
  // else proactor_disconnect logic owns psocket and its final free
}

static void set_wbuf(pconnection_t *pc, const char *start, size_t sz) {
  pc->wbuf_completed = 0;
  pc->wbuf_current = start;
  pc->wbuf_remaining = sz;
}

// Never call with any locks held.
static void ensure_wbuf(pconnection_t *pc) {
  // next connection_driver call is the expensive output generator
  pn_bytes_t bytes = pn_connection_driver_write_buffer(&pc->driver);
  set_wbuf(pc, bytes.start, bytes.size);
  if (bytes.size == 0)
    pc->output_drained = true;
}

// Call with lock held or from forced_shutdown
static void pconnection_begin_close(pconnection_t *pc) {
  if (!pc->task.closing) {
    pc->task.closing = true;
    pc->tick_pending = false;
    if (pc->current_arm) {
      // Force EPOLLHUP callback(s)
      shutdown(pc->psocket.epoll_io.fd, SHUT_RDWR);
    }
    pn_connection_driver_close(&pc->driver);
  }
}

static void pconnection_forced_shutdown(pconnection_t *pc) {
  // Called by proactor_free, no competing threads, no epoll activity.
  pc->current_arm = 0;
  pc->new_events = 0;
  pconnection_begin_close(pc);
  // pconnection_process will never be called again.  Zero everything.
  pc->task.ready = 0;
  pn_collector_release(pc->driver.collector);
  assert(pconnection_is_final(pc));
  pconnection_cleanup(pc);
}

// Called from timer_manager with no locks.
void pni_pconnection_timeout(pconnection_t  *pc) {
  bool notify = false;
  pn_proactor_t *p = pc->task.proactor;
  uint64_t now = pn_proactor_now_64();
  lock(&pc->task.mutex);
  if (!pc->task.closing) {
    // confirm no simultaneous timeout change from another thread.
    if (pc->expected_timeout && now >= pc->expected_timeout) {
      pc->tick_pending = true;
      pc->expected_timeout = 0;
      notify = schedule(&pc->task);
    }
  }
  unlock(&pc->task.mutex);
  if (notify)
    notify_poller(p);
}

static pn_event_t *pconnection_batch_next(pn_event_batch_t *batch) {
  pconnection_t *pc = batch_pconnection(batch);
  if (!pc->driver.connection) return NULL;
  pn_event_t *e = pn_connection_driver_next_event(&pc->driver);
  if (!e) {
    pn_proactor_t *p = pc->task.proactor;
    bool idle_threads;
    lock(&p->sched_mutex);
    idle_threads = (p->suspend_list_head != NULL);
    unlock(&p->sched_mutex);
    if (idle_threads && !pc->write_blocked && !pc->read_blocked) {
      write_flush(pc);  // May generate transport event
      pconnection_process(pc, 0, false, true);
      e = pn_connection_driver_next_event(&pc->driver);
    }
    else {
      write_flush(pc);  // May generate transport event
      e = pn_connection_driver_next_event(&pc->driver);
      if (!e && pc->hog_count < HOG_MAX) {
        pconnection_process(pc, 0, false, true);
        e = pn_connection_driver_next_event(&pc->driver);
      }
    }
  }
  if (e) {
    pc->output_drained = false;
    pc->current_event_type = pn_event_type(e);
  }
  return e;
}

/* Shortcuts */
static inline bool pconnection_rclosed(pconnection_t  *pc) {
  return pn_connection_driver_read_closed(&pc->driver);
}

static inline bool pconnection_wclosed(pconnection_t  *pc) {
  return pn_connection_driver_write_closed(&pc->driver);
}

/* Call only from working task (no competitor for pc->current_arm or
   connection driver).  If true returned, caller must do
   pconnection_rearm().

   Never rearm(0 | EPOLLONESHOT), since this really means
   rearm(EPOLLHUP | EPOLLERR | EPOLLONESHOT) and leaves doubt that the
   EPOLL_CTL_DEL can prevent a parallel HUP/ERR error notification during
   close/shutdown.  Let read()/write() return 0 or -1 to trigger cleanup logic.
*/
static int pconnection_rearm_check(pconnection_t *pc) {
  if ((pconnection_rclosed(pc) && pconnection_wclosed(pc)) || pc->psocket.epoll_io.fd == -1) {
    return 0;
  }
  uint32_t wanted_now = (pc->read_blocked && !pconnection_rclosed(pc)) ? EPOLLIN : 0;
  if (!pconnection_wclosed(pc)) {
    if (pc->write_blocked)
      wanted_now |= EPOLLOUT;
    else {
      if (pc->wbuf_remaining > 0)
        wanted_now |= EPOLLOUT;
    }
  }
  if (!wanted_now) return 0;
  if (wanted_now == pc->current_arm) return 0;

  return wanted_now;
}

static inline void pconnection_rearm(pconnection_t *pc, int wanted_now) {
  lock(&pc->rearm_mutex);
  pc->current_arm = pc->psocket.epoll_io.wanted = wanted_now;
  rearm(pc->task.proactor, &pc->psocket.epoll_io);
  unlock(&pc->rearm_mutex);
  // Return immediately.  pc may have just been freed by another thread.
}

/* Call with task lock and having done a write_flush() to "know" the value of wbuf_remaining */
static inline bool pconnection_work_pending(pconnection_t *pc) {
  if (pc->new_events || pni_task_wake_pending(&pc->task) || pc->tick_pending || pc->queued_disconnect)
    return true;
  if (!pc->read_blocked && !pconnection_rclosed(pc))
    return true;
  return (pc->wbuf_remaining > 0 && !pc->write_blocked);
}

/* Call with no locks. */
static void pconnection_done(pconnection_t *pc) {
  pn_proactor_t *p = pc->task.proactor;
  tslot_t *ts = pc->task.runner;
  write_flush(pc);
  bool notify = false;
  bool self_sched = false;
  lock(&pc->task.mutex);
  pc->task.working = false;  // So we can schedule() ourself if necessary.  We remain the de facto
                             // working task instance while the lock is held.
  pc->hog_count = 0;
  bool has_event = pconnection_has_event(pc);

  if (has_event || pconnection_work_pending(pc)) {
    self_sched = true;
  } else if (pn_connection_driver_finished(&pc->driver)) {
    pconnection_begin_close(pc);
    if (pconnection_is_final(pc)) {
      unlock(&pc->task.mutex);
      pconnection_cleanup(pc);
      // pc may be undefined now
      lock(&p->sched_mutex);
      tslot_t *resume_thread;
      notify = unassign_thread(p, ts, UNUSED, &resume_thread);
      unlock(&p->sched_mutex);
      if (notify) notify_poller(p);
      if (resume_thread) resume(p, resume_thread);
      return;
    }
  }
  if (self_sched)
    notify = schedule(&pc->task);

  int wanted = pconnection_rearm_check(pc);
  unlock(&pc->task.mutex);

  if (wanted) pconnection_rearm(pc, wanted);  // May free pc on another thread.  Return without touching pc again.
  lock(&p->sched_mutex);
  tslot_t *resume_thread;
  if (unassign_thread(p, ts, UNUSED, &resume_thread))
    notify = true;
  unlock(&p->sched_mutex);
  if (notify) notify_poller(p);
  if (resume_thread) resume(p, resume_thread);
  return;
}

// Return true unless error
static bool pconnection_write(pconnection_t *pc) {
  size_t wbuf_size = pc->wbuf_remaining;
  ssize_t n = send(pc->psocket.epoll_io.fd, pc->wbuf_current, wbuf_size, MSG_NOSIGNAL);
  if (n > 0) {
    pc->wbuf_completed += n;
    pc->wbuf_remaining -= n;
    pc->io_doublecheck = false;
    if (pc->wbuf_remaining) {
      pc->write_blocked = true;
      pc->wbuf_current += n;
    }
    else {
      // write_done also calls pn_transport_pending(), so the transport knows all current output
      pn_bytes_t bytes = pn_connection_driver_write_done(&pc->driver, pc->wbuf_completed);
      set_wbuf(pc, bytes.start, bytes.size);
      if (bytes.size == 0)
        pc->output_drained = true;
      // TODO: revise transport API to allow similar efficient access to transport output
    }
  } else if (errno == EWOULDBLOCK) {
    pc->write_blocked = true;
  } else if (!(errno == EAGAIN || errno == EINTR)) {
    pc->wbuf_remaining = 0;
    return false;
  }
  return true;
}

// Never call with any locks held.
static void write_flush(pconnection_t *pc) {
  size_t prev_wbuf_remaining = 0;

  while(!pc->write_blocked && !pc->output_drained && !pconnection_wclosed(pc)) {
    if (pc->wbuf_remaining == 0) {
      ensure_wbuf(pc);
    } else {
      // Check if we are doing multiple small writes in a row, possibly worth growing the transport output buffer.
      if (prev_wbuf_remaining
          && prev_wbuf_remaining == pc->wbuf_remaining         // two max outputs in a row
          && pc->wbuf_remaining < 131072) {
        ensure_wbuf(pc);  // second call -> unchanged wbuf or transport buffer size doubles and more bytes added
        prev_wbuf_remaining = 0;
      } else {
        prev_wbuf_remaining = pc->wbuf_remaining;
      }
    }
    if (pc->wbuf_remaining > 0) {
      if (!pconnection_write(pc)) {
        psocket_error(&pc->psocket, errno, pc->disconnected ? "disconnected" : "on write to");
      }
      // pconnection_write side effect: wbuf may be replenished, and if not, output_drained may be set.
    }
    else {
      if (pn_connection_driver_write_closed(&pc->driver)) {
        shutdown(pc->psocket.epoll_io.fd, SHUT_WR);
        pc->write_blocked = true;
      }
    }
  }
}

static void pconnection_connected_lh(pconnection_t *pc);
static void pconnection_maybe_connect_lh(pconnection_t *pc);

static pn_event_batch_t *pconnection_process(pconnection_t *pc, uint32_t events, bool sched_ready, bool topup) {
  bool waking = false;
  bool tick_required = false;
  bool immediate_write = false;
  lock(&pc->task.mutex);
  if (!topup) { // Save some state in case of crash investigation.
    pc->process_events = events;
    pc->process_args = (pc->tick_pending << 1) | sched_ready;
  }
  if (events) {
    pc->new_events = events;
    pc->current_arm = 0;
    events = 0;
  }
  if (sched_ready) schedule_done(&pc->task);

  if (topup) {
    // Only called by the batch owner.  Does not loop, just "tops up"
    // once.  May be back depending on hog_count.
    assert(pc->task.working);
  }
  else {
    if (pc->task.working) {
      // Another thread is the working task.  Should be impossible with new scheduler.
      EPOLL_FATAL("internal epoll proactor error: two worker threads", 0);
    }
    pc->task.working = true;
  }

  // Confirmed as working thread.  Review state and unlock ASAP.

 retry:

  if (pc->queued_disconnect) {  // From pn_proactor_disconnect()
    pc->queued_disconnect = false;
    if (!pc->task.closing) {
      if (pc->disconnect_condition) {
        pn_condition_copy(pn_transport_condition(pc->driver.transport), pc->disconnect_condition);
      }
      pn_connection_driver_close(&pc->driver);
    }
  }

  if (pconnection_has_event(pc)) {
    unlock(&pc->task.mutex);
    return &pc->batch;
  }
  bool closed = pconnection_rclosed(pc) && pconnection_wclosed(pc);
  if (pni_task_wake_pending(&pc->task)) {
    waking = !closed;
    pni_task_wake_done(&pc->task);
  }
  if (pc->tick_pending) {
    pc->tick_pending = false;
    tick_required = !closed;
  }

  if (pc->new_events) {
    uint32_t update_events = pc->new_events;
    pc->current_arm = 0;
    pc->new_events = 0;
    if (!pc->task.closing) {
      if ((update_events & (EPOLLHUP | EPOLLERR)) && !pconnection_rclosed(pc) && !pconnection_wclosed(pc))
        pconnection_maybe_connect_lh(pc);
      else
        pconnection_connected_lh(pc); /* Non error event means we are connected */
      if (update_events & EPOLLOUT) {
        pc->write_blocked = false;
        if (pc->wbuf_remaining > 0)
          immediate_write = true;
      }
      if (update_events & EPOLLIN)
        pc->read_blocked = false;
    }
  }

  if (pc->task.closing && pconnection_is_final(pc)) {
    unlock(&pc->task.mutex);
    pconnection_cleanup(pc);
    return NULL;
  }

  unlock(&pc->task.mutex);
  pc->hog_count++; // working task doing work

  if (waking) {
    pn_connection_t *c = pc->driver.connection;
    pn_collector_put(pn_connection_collector(c), PN_OBJECT, c, PN_CONNECTION_WAKE);
    waking = false;
  }

  if (immediate_write) {
    immediate_write = false;
    write_flush(pc);
  }

  if (!pconnection_rclosed(pc)) {
    pn_rwbytes_t rbuf = pn_connection_driver_read_buffer(&pc->driver);
    if (rbuf.size > 0 && !pc->read_blocked) {
      ssize_t n = read(pc->psocket.epoll_io.fd, rbuf.start, rbuf.size);
      if (n > 0) {
        pn_connection_driver_read_done(&pc->driver, n);
        // If n == rbuf.size then we should enlarge the buffer and see if there is more to read
        if ((size_t)n==rbuf.size) {
          rbuf = pn_connection_driver_read_buffer_sized(&pc->driver, n*2);
          if (rbuf.size > 0) {
            n = read(pc->psocket.epoll_io.fd, rbuf.start, rbuf.size);
            if (n > 0) {
              pn_connection_driver_read_done(&pc->driver, n);
            }
          }
        }
        // If we didn't read a full buffer (either in the first or second read) then we are blocked
        if ((size_t)n < rbuf.size && !pn_connection_driver_read_closed(&pc->driver)) {
          pc->read_blocked = true;
        }
        pc->output_drained = false;
        pconnection_tick(pc);         /* check for tick changes. */
        tick_required = false;
        pc->io_doublecheck = false;
      }
      // Need to check for EOF and errors for either read
      if (n == 0) {
        pc->read_blocked = true;
        pn_connection_driver_read_close(&pc->driver);
      } else if (n < 0) {
        if (errno == EWOULDBLOCK) {
          pc->read_blocked = true;
        } else if (!(errno == EAGAIN || errno == EINTR)) {
          psocket_error(&pc->psocket, errno, pc->disconnected ? "disconnected" : "on read from");
        }
      }
    }
  } else {
    pc->read_blocked = true;
  }

  if (tick_required) {
    pconnection_tick(pc);         /* check for tick changes. */
    tick_required = false;
    pc->output_drained = false;
  }

  if (topup) {
    // If there was anything new to topup, we have it by now.
    return NULL;  // caller already owns the batch
  }

  if (pconnection_has_event(pc)) {
    pc->output_drained = false;
    return &pc->batch;
  }

  write_flush(pc);

  lock(&pc->task.mutex);
  if (pc->task.closing && pconnection_is_final(pc)) {
    unlock(&pc->task.mutex);
    pconnection_cleanup(pc);
    return NULL;
  }

  // Never stop working while work remains.  hog_count exception to this rule is elsewhere.

  if (pconnection_work_pending(pc)) {
    goto retry;  // TODO: get rid of goto without adding more locking
  }

  pc->task.working = false;
  pc->hog_count = 0;
  if (pn_connection_driver_finished(&pc->driver)) {
    pconnection_begin_close(pc);
    if (pconnection_is_final(pc)) {
      unlock(&pc->task.mutex);
      pconnection_cleanup(pc);
      return NULL;
    }
  }

  // check one last time for new io before context switch
  bool workers_free;
  pn_proactor_t *p = pc->task.proactor;
  lock(&p->sched_mutex);
  workers_free = (p->suspend_list_head != NULL);
  unlock(&p->sched_mutex);
  if (workers_free && !pc->task.closing && !pc->io_doublecheck) {
    pc->io_doublecheck = true;
    pc->read_blocked = false;
    pc->write_blocked = false;
    pc->task.working = true;
    goto retry;
  }

  int wanted = pconnection_rearm_check(pc);  // holds rearm_mutex until pconnection_rearm() below

  unlock(&pc->task.mutex);
  if (wanted) pconnection_rearm(pc, wanted);  // May free pc on another thread.  Return right away.
  return NULL;
}

void configure_socket(int sock) {
  int flags = fcntl(sock, F_GETFL);
  flags |= O_NONBLOCK;
  (void)fcntl(sock, F_SETFL, flags); // TODO: check for error

  int tcp_nodelay = 1;
  (void)setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*) &tcp_nodelay, sizeof(tcp_nodelay));
}

/* Called with task.lock held */
void pconnection_connected_lh(pconnection_t *pc) {
  if (!pc->connected) {
    pc->connected = true;
    if (pc->addrinfo) {
      freeaddrinfo(pc->addrinfo);
      pc->addrinfo = NULL;
    }
    pc->ai = NULL;
    socklen_t len = sizeof(pc->remote.ss);
    (void)getpeername(pc->psocket.epoll_io.fd, (struct sockaddr*)&pc->remote.ss, &len);
  }
}

/* multi-address connections may call pconnection_start multiple times with diffferent FDs  */
static void pconnection_start(pconnection_t *pc, int fd) {
  int efd = pc->task.proactor->epollfd;
  /* Get the local socket name now, get the peer name in pconnection_connected */
  socklen_t len = sizeof(pc->local.ss);
  (void)getsockname(fd, (struct sockaddr*)&pc->local.ss, &len);

  epoll_extended_t *ee = &pc->psocket.epoll_io;
  if (ee->polling) {     /* This is not the first attempt, stop polling and close the old FD */
    int fd = ee->fd;     /* Save fd, it will be set to -1 by stop_polling */
    stop_polling(ee, efd);
    pclosefd(pc->task.proactor, fd);
  }
  ee->fd = fd;
  pc->current_arm = ee->wanted = EPOLLIN | EPOLLOUT;
  start_polling(ee, efd);  // TODO: check for error
}

/* Called on initial connect, and if connection fails to try another address */
static void pconnection_maybe_connect_lh(pconnection_t *pc) {
  errno = 0;
  if (!pc->connected) {         /* Not yet connected */
    while (pc->ai) {            /* Have an address */
      struct addrinfo *ai = pc->ai;
      pc->ai = pc->ai->ai_next; /* Move to next address in case this fails */
      int fd = socket(ai->ai_family, SOCK_STREAM, 0);
      if (fd >= 0) {
        configure_socket(fd);
        if (!connect(fd, ai->ai_addr, ai->ai_addrlen) || errno == EINPROGRESS) {
          pconnection_start(pc, fd);
          return;               /* Async connection started */
        } else {
          close(fd);
        }
      }
      /* connect failed immediately, go round the loop to try the next addr */
    }
    freeaddrinfo(pc->addrinfo);
    pc->addrinfo = NULL;
    /* If there was a previous attempted connection, let the poller discover the
       errno from its socket, otherwise set the current error. */
    if (pc->psocket.epoll_io.fd < 0) {
      psocket_error(&pc->psocket, errno ? errno : ENOTCONN, "on connect");
    }
  }
  pc->disconnected = true;
}

int pgetaddrinfo(const char *host, const char *port, int flags, struct addrinfo **res)
{
  struct addrinfo hints = { 0 };
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | flags;
  return getaddrinfo(host, port, &hints, res);
}

static inline bool is_inactive(pn_proactor_t *p) {
  return (!p->tasks && !p->disconnects_pending && !p->timeout_set && !p->shutting_down);
}

/* If inactive set need_inactive and return true if poller needs to be unblocked */
bool schedule_if_inactive(pn_proactor_t *p) {
  if (is_inactive(p)) {
    p->need_inactive = true;
    return schedule(&p->task);
  }
  return false;
}

void pn_proactor_connect2(pn_proactor_t *p, pn_connection_t *c, pn_transport_t *t, const char *addr) {
  size_t addrlen = strlen(addr);
  pconnection_t *pc = (pconnection_t*) malloc(sizeof(pconnection_t)+addrlen);
  assert(pc); // TODO: memory safety
  const char *err = pconnection_setup(pc, p, c, t, false, addr, addrlen);
  if (err) {    /* TODO aconway 2017-09-13: errors must be reported as events */
    PN_LOG_DEFAULT(PN_SUBSYSTEM_EVENT, PN_LEVEL_ERROR, "pn_proactor_connect failure: %s", err);
    return;
  }
  // TODO: check case of proactor shutting down

  lock(&pc->task.mutex);
  proactor_add(&pc->task);
  pn_connection_open(pc->driver.connection); /* Auto-open */

  bool notify = false;

  if (pc->disconnected) {
    notify = schedule(&pc->task);    /* Error during initialization */
  } else {
    int gai_error = pgetaddrinfo(pc->host, pc->port, 0, &pc->addrinfo);
    if (!gai_error) {
      pn_connection_open(pc->driver.connection); /* Auto-open */
      pc->ai = pc->addrinfo;
      pconnection_maybe_connect_lh(pc); /* Start connection attempts */
      if (pc->disconnected) notify = schedule(&pc->task);
    } else {
      psocket_gai_error(&pc->psocket, gai_error, "connect to ");
      notify = schedule(&pc->task);
      lock(&p->task.mutex);
      notify |= schedule_if_inactive(p);
      unlock(&p->task.mutex);
    }
  }
  /* We need to issue INACTIVE on immediate failure */
  unlock(&pc->task.mutex);
  if (notify) notify_poller(p);
}

static void pconnection_tick(pconnection_t *pc) {
  pn_transport_t *t = pc->driver.transport;
  if (pn_transport_get_idle_timeout(t) || pn_transport_get_remote_idle_timeout(t)) {
    uint64_t now = pn_proactor_now_64();
    uint64_t next = pn_transport_tick(t, now);
    if (next) {
      lock(&pc->task.mutex);
      pc->expected_timeout = next;
      unlock(&pc->task.mutex);
      if (pni_timer_set(pc->timer, next))
        notify_poller(pc->task.proactor);
    }
  }
}

void pn_connection_wake(pn_connection_t* c) {
  pconnection_t *pc = get_pconnection(c);
  if (pc) {
    pn_proactor_t *p = pc->task.proactor;
    bool notify = false;

    lock(&pc->task.mutex);
    if (!pc->task.closing) {
      notify = pni_task_wake(&pc->task);
    }
    unlock(&pc->task.mutex);
    if (notify) notify_poller(p);
  }
}

void pn_proactor_release_connection(pn_connection_t *c) {
  pconnection_t *pc = get_pconnection(c);
  if (pc) {
    bool notify = false;
    pn_proactor_t *p = pc->task.proactor;
    set_pconnection(c, NULL);
    lock(&pc->task.mutex);
    pn_connection_driver_release_connection(&pc->driver);
    pconnection_begin_close(pc);
    notify = schedule(&pc->task);
    unlock(&pc->task.mutex);
    if (notify) notify_poller(p);
  }
}

// ========================================================================
// listener
// ========================================================================

pn_listener_t *pn_event_listener(pn_event_t *e) {
  return (pn_event_class(e) == PN_CLASSCLASS(pn_listener)) ? (pn_listener_t*)pn_event_context(e) : NULL;
}

pn_listener_t *pn_listener() {
  pn_listener_t *l = (pn_listener_t*)calloc(1, sizeof(pn_listener_t));
  if (l) {
    l->batch.next_event = listener_batch_next;
    l->collector = pn_collector();
    l->condition = pn_condition();
    l->attachments = pn_record();
    if (!l->condition || !l->collector || !l->attachments) {
      pn_listener_free(l);
      return NULL;
    }
    pn_proactor_t *unknown = NULL;  // won't know until pn_proactor_listen
    task_init(&l->task, LISTENER, unknown);
  }
  return l;
}

void pn_proactor_listen(pn_proactor_t *p, pn_listener_t *l, const char *addr, int backlog)
{
  // TODO: check listener not already listening for this or another proactor
  lock(&l->task.mutex);
  l->task.proactor = p;

  l->pending_accepteds = (accepted_t*)calloc(backlog, sizeof(accepted_t));
  assert(l->pending_accepteds);
  l->backlog = backlog;

  pni_parse_addr(addr, l->addr_buf, sizeof(l->addr_buf), &l->host, &l->port);

  struct addrinfo *addrinfo = NULL;
  int gai_err = pgetaddrinfo(l->host, l->port, AI_PASSIVE | AI_ALL, &addrinfo);
  if (!gai_err) {
    /* Count addresses, allocate enough space for sockets */
    size_t len = 0;
    for (struct addrinfo *ai = addrinfo; ai; ai = ai->ai_next) {
      ++len;
    }
    assert(len > 0);            /* guaranteed by getaddrinfo */
    l->acceptors = (acceptor_t*)calloc(len, sizeof(acceptor_t));
    assert(l->acceptors);      /* TODO aconway 2017-05-05: memory safety */
    l->acceptors_size = 0;
    uint16_t dynamic_port = 0;  /* Record dynamic port from first bind(0) */
    /* Find working listen addresses */
    for (struct addrinfo *ai = addrinfo; ai; ai = ai->ai_next) {
      if (dynamic_port) set_port(ai->ai_addr, dynamic_port);
      int fd = socket(ai->ai_family, SOCK_STREAM, ai->ai_protocol);
      static int on = 1;
      if (fd >= 0) {
        configure_socket(fd);
        if (!setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) &&
            /* We listen to v4/v6 on separate sockets, don't let v6 listen for v4 */
            (ai->ai_family != AF_INET6 ||
             !setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on))) &&
            !bind(fd, ai->ai_addr, ai->ai_addrlen) &&
            !listen(fd, backlog))
        {
          acceptor_t *acceptor = &l->acceptors[l->acceptors_size++];
          /* Get actual address */
          socklen_t len = pn_netaddr_socklen(&acceptor->addr);
          (void)getsockname(fd, (struct sockaddr*)(&acceptor->addr.ss), &len);
          if (acceptor == l->acceptors) { /* First acceptor, check for dynamic port */
            dynamic_port = check_dynamic_port(ai->ai_addr, pn_netaddr_sockaddr(&acceptor->addr));
          } else {              /* Link addr to previous addr */
            (acceptor-1)->addr.next = &acceptor->addr;
          }

          acceptor->listener = l;
          psocket_t *ps = &acceptor->psocket;
          psocket_init(ps, LISTENER_IO);
          ps->epoll_io.fd = fd;
          ps->epoll_io.wanted = EPOLLIN;
          ps->epoll_io.polling = false;
          start_polling(&ps->epoll_io, l->task.proactor->epollfd);  // TODO: check for error
          l->active_count++;
          acceptor->armed = true;
        } else {
          close(fd);
        }
      }
    }
  }
  if (addrinfo) {
    freeaddrinfo(addrinfo);
  }
  bool notify = schedule(&l->task);

  if (l->acceptors_size == 0) { /* All failed, create dummy socket with an error */
    l->acceptors = (acceptor_t*)realloc(l->acceptors, sizeof(acceptor_t));
    l->acceptors_size = 1;
    memset(l->acceptors, 0, sizeof(acceptor_t));
    psocket_init(&l->acceptors[0].psocket, LISTENER_IO);
    l->acceptors[0].listener = l;
    if (gai_err) {
      psocket_gai_error(&l->acceptors[0].psocket, gai_err, "listen on");
    } else {
      psocket_error(&l->acceptors[0].psocket, errno, "listen on");
    }
  } else {
    pn_collector_put(l->collector, PN_CLASSCLASS(pn_listener), l, PN_LISTENER_OPEN);
  }
  proactor_add(&l->task);
  unlock(&l->task.mutex);
  if (notify) notify_poller(p);
  return;
}

// call with lock held and task.working false
static inline bool listener_can_free(pn_listener_t *l) {
  return l->task.closing && l->close_dispatched && !l->task.ready && !l->active_count && !l->overflow_count;
}

static inline void listener_final_free(pn_listener_t *l) {
  task_finalize(&l->task);
  free(l->acceptors);
  free(l->pending_accepteds);
  free(l);
}

void pn_listener_free(pn_listener_t *l) {
  /* Note at this point either the listener has never been used (freed by user)
     or it has been closed, so all its sockets are closed.
  */
  if (l) {
    bool can_free = true;
    if (l->collector) pn_collector_free(l->collector);
    if (l->condition) pn_condition_free(l->condition);
    if (l->attachments) pn_free(l->attachments);
    lock(&l->task.mutex);
    if (l->task.proactor) {
      can_free = proactor_remove(&l->task);
    }
    unlock(&l->task.mutex);
    if (can_free)
      listener_final_free(l);
  }
}

/* Always call with lock held so it can be unlocked around overflow processing. */
static void listener_begin_close(pn_listener_t* l) {
  if (!l->task.closing) {
    l->task.closing = true;
    bool polling = !l->task.proactor->shutting_down;  // Is poller still running?

    /* Close all listening sockets */
    for (size_t i = 0; i < l->acceptors_size; ++i) {
      acceptor_t *a = &l->acceptors[i];
      psocket_t *ps = &a->psocket;
      if (ps->epoll_io.fd >= 0) {
        if (a->armed && polling) {
          shutdown(ps->epoll_io.fd, SHUT_RD);  // Force epoll event and callback
        } else {
          int fd = ps->epoll_io.fd;
          stop_polling(&ps->epoll_io, l->task.proactor->epollfd);
          close(fd);
          l->active_count--;
        }
      }
    }
    /* Close all sockets waiting for a pn_listener_accept2() */
    accepted_t *a = listener_accepted_next(l);
    while (a) {
      close(a->accepted_fd);
      a->accepted_fd = -1;
      a = listener_accepted_next(l);
    }
    assert(!l->pending_count);

    pn_collector_put(l->collector, PN_CLASSCLASS(pn_listener), l, PN_LISTENER_CLOSE);
  }
}

void pn_listener_close(pn_listener_t* l) {
  bool notify = false;
  lock(&l->task.mutex);
  pn_proactor_t *p = l->task.proactor;
  if (p && !l->task.closing) {
    listener_begin_close(l);
    notify = schedule(&l->task);
  }
  unlock(&l->task.mutex);
  if (notify) notify_poller(p);
}

static void listener_forced_shutdown(pn_listener_t *l) {
  // Called by proactor_free, no competing threads, no epoll activity.
  lock(&l->task.mutex); // needed because of interaction with proactor_rearm_overflow
  listener_begin_close(l);
  unlock(&l->task.mutex);
  // pconnection_process will never be called again.  Zero everything.
  l->task.ready = 0;
  l->close_dispatched = true;
  l->active_count = 0;
  assert(listener_can_free(l));
  pn_listener_free(l);
}

/* Accept a connection as part of listener_process(). Called with listener task lock held. */
/* Keep on accepting until we fill the backlog, would block or get an error */
static void listener_accept_lh(psocket_t *ps) {
  pn_listener_t *l = psocket_listener(ps);
  while (l->pending_first+l->pending_count < l->backlog) {
    int fd = accept(ps->epoll_io.fd, NULL, 0);
    if (fd >= 0) {
      accepted_t accepted = {.accepted_fd = fd} ;
      listener_accepted_append(l, accepted);
    } else {
      int err = errno;
      if (err == ENFILE || err == EMFILE) {
        acceptor_t *acceptor = psocket_acceptor(ps);
        acceptor_set_overflow(acceptor);
      } else if (err != EWOULDBLOCK) {
        psocket_error(ps, err, "accept");
      }
      return;
    }
  }
}

/* Process a listening socket */
static pn_event_batch_t *listener_process(pn_listener_t *l, int n_events, bool tsk_ready) {
  // TODO: some parallelization of the accept mechanism.
//  pn_listener_t *l = psocket_listener(ps);
//  acceptor_t *a = psocket_acceptor(ps);

  lock(&l->task.mutex);
  if (n_events) {
    for (size_t i = 0; i < l->acceptors_size; i++) {
      psocket_t *ps = &l->acceptors[i].psocket;
      if (ps->working_io_events) {
        uint32_t events = ps->working_io_events;
        ps->working_io_events = 0;
        if (l->task.closing) {
          l->acceptors[i].armed = false;
          int fd = ps->epoll_io.fd;
          stop_polling(&ps->epoll_io, l->task.proactor->epollfd);
          close(fd);
          l->active_count--;
        } else {
          l->acceptors[i].armed = false;
          if (events & EPOLLRDHUP) {
            /* Calls listener_begin_close which closes all the listener's sockets */
            psocket_error(ps, errno, "listener epoll");
          } else if (!l->task.closing && events & EPOLLIN) {
            listener_accept_lh(ps);
          }
        }
      }
    }
  }
  if (tsk_ready) {
    schedule_done(&l->task); // callback accounting
  }
  pn_event_batch_t *lb = NULL;
  if (!l->task.working) {
    l->task.working = true;
    if (listener_has_event(l))
      lb = &l->batch;
    else {
      l->task.working = false;
      if (listener_can_free(l)) {
        unlock(&l->task.mutex);
        pn_listener_free(l);
        return NULL;
      }
    }
  }
  unlock(&l->task.mutex);
  return lb;
}

static pn_event_t *listener_batch_next(pn_event_batch_t *batch) {
  pn_listener_t *l = batch_listener(batch);
  lock(&l->task.mutex);
  pn_event_t *e = pn_collector_next(l->collector);
  if (!e && l->pending_count) {
    // empty collector means pn_collector_put() will not coalesce
    pn_collector_put(l->collector, PN_CLASSCLASS(pn_listener), l, PN_LISTENER_ACCEPT);
    e = pn_collector_next(l->collector);
  }
  if (e && pn_event_type(e) == PN_LISTENER_CLOSE)
    l->close_dispatched = true;
  unlock(&l->task.mutex);
  return pni_log_event(l, e);
}

static void listener_done(pn_listener_t *l) {
  pn_proactor_t *p = l->task.proactor;
  tslot_t *ts = l->task.runner;

  lock(&l->task.mutex);

  if (l->close_dispatched && l->overflow_count) {
    unlock(&l->task.mutex);
    /* Remove all acceptors from the overflow list.  closing flag prevents re-insertion.*/
    proactor_rearm_overflow(pn_listener_proactor(l));
    lock(&l->task.mutex);
    assert(l->overflow_count == 0);
  }

  // Just in case the app didn't accept all the pending accepts
  // Shuffle the list back to start at 0
  memmove(&l->pending_accepteds[0], &l->pending_accepteds[l->pending_first], l->pending_count * sizeof(accepted_t));
  l->pending_first = 0;

  if (!l->task.closing) {
    for (size_t i = 0; i < l->acceptors_size; i++) {
      acceptor_t *a = &l->acceptors[i];
      psocket_t *ps = &a->psocket;

      // Rearm acceptor when appropriate
      if (ps->epoll_io.polling && l->pending_count==0 && !a->overflowed) {
        if (!a->armed) {
          rearm(l->task.proactor, &ps->epoll_io);
          a->armed = true;
        }
      }
    }
  }

  bool notify = false;
  l->task.working = false;

  if (listener_can_free(l)) {
    unlock(&l->task.mutex);
    pn_listener_free(l);
    lock(&p->sched_mutex);
    tslot_t *resume_thread;
    notify = unassign_thread(p, ts, UNUSED, &resume_thread);
    unlock(&p->sched_mutex);
    if (notify) notify_poller(p);
    if (resume_thread) resume(p, resume_thread);
    return;
  } else if (listener_has_event(l))
    notify = schedule(&l->task);
  unlock(&l->task.mutex);

  lock(&p->sched_mutex);
  tslot_t *resume_thread;
  if (unassign_thread(p, ts, UNUSED, &resume_thread))
    notify = true;
  unlock(&p->sched_mutex);
  if (notify) notify_poller(p);
  if (resume_thread) resume(p, resume_thread);
}

pn_proactor_t *pn_listener_proactor(pn_listener_t* l) {
  return l ? l->task.proactor : NULL;
}

pn_condition_t* pn_listener_condition(pn_listener_t* l) {
  return l->condition;
}

void *pn_listener_get_context(pn_listener_t *l) {
  return l->listener_context;
}

void pn_listener_set_context(pn_listener_t *l, void *context) {
  l->listener_context = context;
}

pn_record_t *pn_listener_attachments(pn_listener_t *l) {
  return l->attachments;
}

void pn_listener_accept2(pn_listener_t *l, pn_connection_t *c, pn_transport_t *t) {
  pconnection_t *pc = (pconnection_t*) malloc(sizeof(pconnection_t));
  pn_proactor_t *p = pn_listener_proactor(l);
  assert(pc && p); // TODO: memory safety
  const char *err = pconnection_setup(pc, p, c, t, true, "", 0);
  if (err) {
    PN_LOG_DEFAULT(PN_SUBSYSTEM_EVENT, PN_LEVEL_ERROR, "pn_listener_accept failure: %s", err);
    return;
  }
  // TODO: fuller sanity check on input args

  int err2 = 0;
  int fd = -1;
  bool notify = false;
  lock(&l->task.mutex);
  if (l->task.closing)
    err2 = EBADF;
  else {
    accepted_t *a = listener_accepted_next(l);
    if (a) {
      fd = a->accepted_fd;
      a->accepted_fd = -1;
    }
    else err2 = EWOULDBLOCK;
  }

  proactor_add(&pc->task);
  lock(&pc->task.mutex);
  if (fd >= 0) {
    configure_socket(fd);
    pconnection_start(pc, fd);
    pconnection_connected_lh(pc);
  }
  else
    psocket_error(&pc->psocket, err2, "pn_listener_accept");
  if (!l->task.working && listener_has_event(l))
    notify = schedule(&l->task);
  unlock(&pc->task.mutex);
  unlock(&l->task.mutex);
  if (notify) notify_poller(p);
}


// ========================================================================
// proactor
// ========================================================================

// Call with sched_mutex. Alloc calls are expensive but only used when thread_count changes.
static void grow_poller_bufs(pn_proactor_t* p) {
  // call if p->thread_count > p->thread_capacity
  assert(p->thread_count == 0 || p->thread_count > p->thread_capacity);
  do {
    p->thread_capacity += 8;
  } while (p->thread_count > p->thread_capacity);

  p->warm_runnables = (task_t **) realloc(p->warm_runnables, p->thread_capacity * sizeof(task_t *));
  p->resume_list = (tslot_t **) realloc(p->resume_list, p->thread_capacity * sizeof(tslot_t *));

  int old_cap = p->runnables_capacity;
  if (p->runnables_capacity == 0)
    p->runnables_capacity = 16;
  else if (p->runnables_capacity < p->thread_capacity)
    p->runnables_capacity = p->thread_capacity;
  if (p->runnables_capacity != old_cap) {
    p->runnables = (task_t **) realloc(p->runnables, p->runnables_capacity * sizeof(task_t *));
    p->kevents_capacity = p->runnables_capacity;
    size_t sz = p->kevents_capacity * sizeof(struct epoll_event);
    p->kevents = (struct epoll_event *) realloc(p->kevents, sz);
    memset(p->kevents, 0, sz);
  }
}

/* Set up an epoll_extended_t to be used for ready list schedule() or interrupts */
 static void epoll_eventfd_init(epoll_extended_t *ee, int eventfd, int epollfd, bool always_set) {
  ee->fd = eventfd;
  ee->type = EVENT_FD;
  if (always_set) {
    uint64_t increment = 1;
    if (write(eventfd, &increment, sizeof(uint64_t)) != sizeof(uint64_t))
      EPOLL_FATAL("setting eventfd", errno);
    // eventfd is set forever.  No reads, just rearms as needed.
    ee->wanted = 0;
  } else {
    ee->wanted = EPOLLIN;
  }
  ee->polling = false;
  start_polling(ee, epollfd);  // TODO: check for error
  if (always_set)
    ee->wanted = EPOLLIN;      // for all subsequent rearms
}

pn_proactor_t *pn_proactor() {
  if (getenv("PNI_EPOLL_NOWARM")) pni_warm_sched = false;
  if (getenv("PNI_EPOLL_IMMEDIATE")) pni_immediate = true;
  if (getenv("PNI_EPOLL_SPINS")) pni_spins = atoi(getenv("PNI_EPOLL_SPINS"));
  pn_proactor_t *p = (pn_proactor_t*)calloc(1, sizeof(*p));
  if (!p) return NULL;
  p->epollfd = p->eventfd = -1;
  task_init(&p->task, PROACTOR, p);
  pmutex_init(&p->eventfd_mutex);
  pmutex_init(&p->sched_mutex);
  pmutex_init(&p->tslot_mutex);
  pmutex_init(&p->timeout_mutex);

  if ((p->epollfd = epoll_create(1)) >= 0) {
    if ((p->eventfd = eventfd(0, EFD_NONBLOCK)) >= 0) {
      if ((p->interruptfd = eventfd(0, EFD_NONBLOCK)) >= 0) {
        if (pni_timer_manager_init(&p->timer_manager))
          if ((p->collector = pn_collector()) != NULL) {
            p->batch.next_event = &proactor_batch_next;
            start_polling(&p->timer_manager.epoll_timer, p->epollfd);  // TODO: check for error
            epoll_eventfd_init(&p->epoll_schedule, p->eventfd, p->epollfd, true);
            epoll_eventfd_init(&p->epoll_interrupt, p->interruptfd, p->epollfd, false);
            p->tslot_map = pn_hash(PN_VOID, 0, 0.75);
            grow_poller_bufs(p);
            p->ready_list_generation = 1;
            return p;
          }
      }
    }
  }
  if (p->epollfd >= 0) close(p->epollfd);
  if (p->eventfd >= 0) close(p->eventfd);
  if (p->interruptfd >= 0) close(p->interruptfd);
  pni_timer_manager_finalize(&p->timer_manager);
  pmutex_finalize(&p->timeout_mutex);
  pmutex_finalize(&p->tslot_mutex);
  pmutex_finalize(&p->sched_mutex);
  pmutex_finalize(&p->eventfd_mutex);
  if (p->collector) pn_free(p->collector);
  assert(p->thread_count == 0);
  free (p);
  return NULL;
}

void pn_proactor_free(pn_proactor_t *p) {
  //  No competing threads, not even a pending timer
  p->shutting_down = true;
  close(p->epollfd);
  p->epollfd = -1;
  close(p->eventfd);
  p->eventfd = -1;
  close(p->interruptfd);
  p->interruptfd = -1;
  while (p->tasks) {
    task_t *tsk = p->tasks;
    p->tasks = tsk->next;
    switch (tsk->type) {
     case PCONNECTION:
      pconnection_forced_shutdown(task_pconnection(tsk));
      break;
     case LISTENER:
      listener_forced_shutdown(task_listener(tsk));
      break;
     default:
      break;
    }
  }

  pni_timer_manager_finalize(&p->timer_manager);
  pn_collector_free(p->collector);
  pmutex_finalize(&p->timeout_mutex);
  pmutex_finalize(&p->tslot_mutex);
  pmutex_finalize(&p->sched_mutex);
  pmutex_finalize(&p->eventfd_mutex);
  task_finalize(&p->task);
  for (pn_handle_t entry = pn_hash_head(p->tslot_map); entry; entry = pn_hash_next(p->tslot_map, entry)) {
    tslot_t *ts = (tslot_t *) pn_hash_value(p->tslot_map, entry);
    pmutex_finalize(&ts->mutex);
    free(ts);
  }
  pn_free(p->tslot_map);
  free(p->kevents);
  free(p->runnables);
  free(p->warm_runnables);
  free(p->resume_list);
  free(p);
}

pn_proactor_t *pn_event_proactor(pn_event_t *e) {
  if (pn_event_class(e) == PN_CLASSCLASS(pn_proactor)) return (pn_proactor_t*)pn_event_context(e);
  pn_listener_t *l = pn_event_listener(e);
  if (l) return l->task.proactor;
  pn_connection_t *c = pn_event_connection(e);
  if (c) return pn_connection_proactor(c);
  return NULL;
}

static void proactor_add_event(pn_proactor_t *p, pn_event_type_t t) {
  pn_collector_put(p->collector, PN_CLASSCLASS(pn_proactor), p, t);
}

// Call with lock held.  Leave unchanged if events pending.
// There can be multiple interrupts but only one inside the collector to avoid coalescing.
// Return true if there is an event in the collector.
static bool proactor_update_batch(pn_proactor_t *p) {
  if (proactor_has_event(p))
    return true;

  if (p->need_timeout) {
    p->need_timeout = false;
    p->timeout_set = false;
    proactor_add_event(p, PN_PROACTOR_TIMEOUT);
    return true;
  }
  if (p->need_interrupt) {
    p->need_interrupt = false;
    proactor_add_event(p, PN_PROACTOR_INTERRUPT);
    return true;
  }
  if (p->need_inactive) {
    p->need_inactive = false;
    proactor_add_event(p, PN_PROACTOR_INACTIVE);
    return true;
  }
  return false;
}

static pn_event_t *proactor_batch_next(pn_event_batch_t *batch) {
  pn_proactor_t *p = batch_proactor(batch);
  lock(&p->task.mutex);
  proactor_update_batch(p);
  pn_event_t *e = pn_collector_next(p->collector);
  if (e) {
    p->current_event_type = pn_event_type(e);
    if (p->current_event_type == PN_PROACTOR_TIMEOUT)
      p->timeout_processed = true;
  }
  unlock(&p->task.mutex);
  return pni_log_event(p, e);
}

static pn_event_batch_t *proactor_process(pn_proactor_t *p, bool interrupt, bool tsk_ready) {
  if (interrupt) {
    (void)read_uint64(p->interruptfd);
    rearm(p, &p->epoll_interrupt);
  }
  lock(&p->task.mutex);
  if (interrupt) {
    p->need_interrupt = true;
  }
  if (tsk_ready) {
    schedule_done(&p->task);
  }
  if (!p->task.working) {       /* Can generate proactor events */
    if (proactor_update_batch(p)) {
      p->task.working = true;
      unlock(&p->task.mutex);
      return &p->batch;
    }
  }
  unlock(&p->task.mutex);
  return NULL;
}

void proactor_add(task_t *tsk) {
  pn_proactor_t *p = tsk->proactor;
  lock(&p->task.mutex);
  if (p->tasks) {
    p->tasks->prev = tsk;
    tsk->next = p->tasks;
  }
  p->tasks = tsk;
  p->task_count++;
  unlock(&p->task.mutex);
}

// call with psocket's mutex held
// return true if safe for caller to free psocket
bool proactor_remove(task_t *tsk) {
  pn_proactor_t *p = tsk->proactor;
  // Disassociate this task from scheduler
  if (!p->shutting_down) {
    lock(&p->sched_mutex);
    tsk->runner->state = DELETING;
    for (pn_handle_t entry = pn_hash_head(p->tslot_map); entry; entry = pn_hash_next(p->tslot_map, entry)) {
      tslot_t *ts = (tslot_t *) pn_hash_value(p->tslot_map, entry);
      if (ts->task == tsk)
        ts->task = NULL;
      if (ts->prev_task == tsk)
        ts->prev_task = NULL;
    }
    unlock(&p->sched_mutex);
  }

  lock(&p->task.mutex);
  bool can_free = true;
  if (tsk->disconnecting) {
    // No longer on tasks list
    --p->disconnects_pending;
    if (--tsk->disconnect_ops != 0) {
      // procator_disconnect() does the free
      can_free = false;
    }
  }
  else {
    // normal case
    if (tsk->prev)
      tsk->prev->next = tsk->next;
    else {
      p->tasks = tsk->next;
      tsk->next = NULL;
      if (p->tasks)
        p->tasks->prev = NULL;
    }
    if (tsk->next) {
      tsk->next->prev = tsk->prev;
    }
    p->task_count--;
  }
  bool notify = schedule_if_inactive(p);
  unlock(&p->task.mutex);
  if (notify) notify_poller(p);
  return can_free;
}

static tslot_t *find_tslot(pn_proactor_t *p) {
  pthread_t tid = pthread_self();
  void *v = pn_hash_get(p->tslot_map, (uintptr_t) tid);
  if (v)
    return (tslot_t *) v;
  tslot_t *ts = (tslot_t *) calloc(1, sizeof(tslot_t));
  ts->state = NEW;
  pmutex_init(&ts->mutex);

  lock(&p->sched_mutex);
  // keep important tslot related info thread-safe when holding either the sched or tslot mutex
  p->thread_count++;
  pn_hash_put(p->tslot_map, (uintptr_t) tid, ts);
  unlock(&p->sched_mutex);
  return ts;
}

// Called with sched lock, returns with sched lock still held.
static pn_event_batch_t *process(task_t *tsk) {
  bool tsk_ready = false;
  tsk->sched_pending = false;
  if (tsk->sched_ready) {
    // update the ready status before releasing the sched_mutex
    tsk->sched_ready = false;
    tsk_ready = true;
  }
  pn_proactor_t *p = tsk->proactor;
  pn_event_batch_t* batch = NULL;
  switch (tsk->type) {
  case PROACTOR: {
    bool intr = p->sched_interrupt;
    if (intr) p->sched_interrupt = false;
    unlock(&p->sched_mutex);
    batch = proactor_process(p, intr, tsk_ready);
    break;
  }
  case PCONNECTION: {
    pconnection_t *pc = task_pconnection(tsk);
    uint32_t events = pc->psocket.sched_io_events;
    if (events) pc->psocket.sched_io_events = 0;
    unlock(&p->sched_mutex);
    batch = pconnection_process(pc, events, tsk_ready, false);
    break;
  }
  case LISTENER: {
    pn_listener_t *l = task_listener(tsk);
    int n_events = 0;
    for (size_t i = 0; i < l->acceptors_size; i++) {
      psocket_t *ps = &l->acceptors[i].psocket;
      if (ps->sched_io_events) {
        ps->working_io_events = ps->sched_io_events;
        ps->sched_io_events = 0;
      }
      if (ps->working_io_events)
        n_events++;
    }
    unlock(&p->sched_mutex);
    batch = listener_process(l, n_events, tsk_ready);
    break;
  }
  case RAW_CONNECTION: {
    psocket_t *ps = pni_task_raw_psocket(tsk);
    uint32_t events = ps->sched_io_events;
    if (events) ps->sched_io_events = 0;
    unlock(&p->sched_mutex);
    batch = pni_raw_connection_process(tsk, events, tsk_ready);
    break;
  }
  case TIMER_MANAGER: {
    pni_timer_manager_t *tm = &p->timer_manager;
    bool timeout = tm->sched_timeout;
    if (timeout) tm->sched_timeout = false;
    unlock(&p->sched_mutex);
    batch = pni_timer_manager_process(tm, timeout, tsk_ready);
    break;
  }
  default:
    assert(NULL);
  }
  lock(&p->sched_mutex);
  return batch;
}


// Call with both sched_mutex and eventfd_mutex held
static void schedule_ready_list(pn_proactor_t *p) {
  // Append ready_list_first..ready_list_last to end of sched_ready_last
  // May see several in single do_epoll() if EINTR.
  if (p->ready_list_first) {
    if (p->sched_ready_last)
      p->sched_ready_last->ready_next = p->ready_list_first;  // join them
    if (!p->sched_ready_first)
      p->sched_ready_first = p->ready_list_first;
    p->sched_ready_last = p->ready_list_last;
    p->ready_list_first = p->ready_list_last = NULL;

    // Track sched_ready_count to know how many threads may be needed.
    p->sched_ready_count += p->ready_list_count;
    p->ready_list_count = 0;
  }
}

// Call with schedule lock and eventfd lock held.  Called only by poller thread.
// Needs to be quick.
static task_t *post_event(pn_proactor_t *p, struct epoll_event *evp) {
  epoll_extended_t *ee = (epoll_extended_t *) evp->data.ptr;
  task_t *tsk = NULL;

  switch (ee->type) {
  case EVENT_FD:
    if (ee->fd == p->interruptfd) {        /* Interrupts have their own dedicated eventfd */
      p->sched_interrupt = true;
      tsk = &p->task;
      tsk->sched_pending = true;
    }
    // else if (ee->fd == p->eventfd)... schedule_ready_list already performed by poller task.
    break;
  case PCONNECTION_IO: {
    psocket_t *ps = containerof(ee, psocket_t, epoll_io);
    pconnection_t *pc = psocket_pconnection(ps);
    assert(pc);
    tsk = &pc->task;
    ps->sched_io_events = evp->events;
    tsk->sched_pending = true;
    break;
  }
  case LISTENER_IO: {
    psocket_t *ps = containerof(ee, psocket_t, epoll_io);
    pn_listener_t *l = psocket_listener(ps);
    assert(l);
    tsk = &l->task;
    ps->sched_io_events = evp->events;
    tsk->sched_pending = true;
    break;
  }
  case RAW_CONNECTION_IO: {
    psocket_t *ps = containerof(ee, psocket_t, epoll_io);
    tsk = pni_psocket_raw_task(ps);
    ps->sched_io_events = evp->events;
    tsk->sched_pending = true;
    break;
  }
  case TIMER: {
    pni_timer_manager_t *tm = &p->timer_manager;
    tsk = &tm->task;
    tm->sched_timeout = true;
    tsk->sched_pending = true;
    break;
  }
  }
  if (tsk && !tsk->runnables_idx && !tsk->runner && !on_sched_ready_list(tsk, p))
    return tsk;
  return NULL;
}


static inline task_t *post_ready(pn_proactor_t *p, task_t *tsk) {
  tsk->sched_ready = true;
  tsk->sched_pending = true;
  if (!tsk->runnables_idx && !tsk->runner)
    return tsk;
  return NULL;
}

// call with sched_lock held
static task_t *next_drain(pn_proactor_t *p, tslot_t *ts) {
  // This should be called seldomly, best case once per thread removal on shutdown.
  // TODO: how to reduce?  Instrumented near 5 percent of earmarks, 1 in 2000 calls to do_epoll().

  for (pn_handle_t entry = pn_hash_head(p->tslot_map); entry; entry = pn_hash_next(p->tslot_map, entry)) {
    tslot_t *ts2 = (tslot_t *) pn_hash_value(p->tslot_map, entry);
    if (ts2->earmarked) {
      // undo the old assign thread and earmark.  ts2 may never come back
      task_t *switch_tsk = ts2->task;
      remove_earmark(ts2);
      assign_thread(ts, switch_tsk);
      ts->earmark_override = ts2;
      ts->earmark_override_gen = ts2->generation;
      return switch_tsk;
    }
  }
  assert(false);
  return NULL;
}

// call with sched_lock held
static task_t *next_runnable(pn_proactor_t *p, tslot_t *ts) {
  if (ts->task) {
    // Already assigned
    if (ts->earmarked) {
      ts->earmarked = false;
      if (--p->earmark_count == 0)
        p->earmark_drain = false;
    }
    return ts->task;
  }

  // Take any runnables task if it results in a warm pairing.
  task_t *tsk = ts->prev_task;
  if (tsk && (tsk->runnables_idx)) {
    // A task can self delete, so don't allow it to run twice.
    task_t **runnables_slot = &p->runnables[tsk->runnables_idx -1];
    assert(*runnables_slot == tsk);
    *runnables_slot = NULL;
    assign_thread(ts, tsk);
    return tsk;
  }

  // check for remaining runnable tasks
  if (p->n_runnables) {
    // Any unclaimed runnable?
    while (p->n_runnables) {
      tsk = p->runnables[p->next_runnable++];
      if (p->n_runnables == p->next_runnable)
        p->n_runnables = 0;
      if (tsk) {
        assign_thread(ts, tsk);
        return tsk;
      }
    }
  }

  // sched_ready list tasks deferred in poller_do_epoll()
  while (p->sched_ready_pending) {
    tsk = sched_ready_pop_front(p);
    assert(tsk->ready); // eventfd_mutex required post ready set and pre move to sched_ready_list
    if (post_ready(p, tsk)) {
      assert(!tsk->runnables_idx && !tsk->runner);
      assign_thread(ts, tsk);
      return tsk;
    }
  }

  // the resched list
  if (p->polled_resched_count) {
    // Unprocessed resched tasks remain.
    tsk = resched_pop_front(p);
    assert(tsk->sched_pending && !tsk->runnables_idx && tsk->runner == RESCHEDULE_PLACEHOLDER);
    tsk->runner = NULL;
    assign_thread(ts, tsk);
    return tsk;
  }

  if (p->earmark_drain) {
    tsk = next_drain(p, ts);
    if (p->earmark_count == 0)
      p->earmark_drain = false;
    return tsk;
  }

  return NULL;
}

static pn_event_batch_t *next_event_batch(pn_proactor_t* p, bool can_block) {
  lock(&p->tslot_mutex);
  tslot_t * ts = find_tslot(p);
  unlock(&p->tslot_mutex);

  lock(&p->sched_mutex);
  assert(ts->task == NULL || ts->earmarked);
  assert(ts->state == UNUSED || ts->state == NEW);
  ts->state = PROCESSING;
  ts->generation++;  // wrapping OK.  Just looking for any change

  // Process outstanding epoll events until we get a batch or need to block.
  while (true) {
    // First see if there are any tasks waiting to run and perhaps generate new Proton events,
    task_t *tsk = next_runnable(p, ts);
    if (tsk) {
      ts->state = BATCHING;
      pn_event_batch_t *batch = process(tsk);
      if (batch) {
        unlock(&p->sched_mutex);
        return batch;
      }
      tslot_t *resume_thread;
      bool notify = unassign_thread(p, ts, PROCESSING, &resume_thread);
      if (notify || resume_thread) {
        unlock(&p->sched_mutex);
        if (notify) notify_poller(p);
        if (resume_thread) resume(p, resume_thread);
        lock(&p->sched_mutex);
      }
      continue;  // Long time may have passed.  Back to beginning.
    }

    // Poll or wait for a runnable task
    if (p->poller == NULL) {
      bool return_immediately;
      p->poller = ts;
      // Get new epoll events (if any) and mark the relevant tasks as runnable
      return_immediately = poller_do_epoll(p, ts, can_block);
      p->poller = NULL;
      if (return_immediately) {
        // Check if another thread is available to continue epoll-ing.
        tslot_t *res_ts = resume_one_thread(p);
        ts->state = UNUSED;
        unlock(&p->sched_mutex);
        if (res_ts) resume(p, res_ts);
        return NULL;
      }
      poller_done(p, ts);  // put suspended threads to work.
    } else if (!can_block) {
      ts->state = UNUSED;
      unlock(&p->sched_mutex);
      return NULL;
    } else {
      // TODO: loop while !poller_suspended, since new work coming
      suspend(p, ts);
    }
  } // while
}

// Call with sched lock.  Return true if !can_block and no new events to process.
static bool poller_do_epoll(struct pn_proactor_t* p, tslot_t *ts, bool can_block) {
  // As poller with lots to do, be mindful of hogging the sched lock.  Release when making kernel calls.
  assert(!p->resched_cutoff);
  assert(!p->sched_ready_first && !p->sched_ready_pending);
  int n_events;
  task_t *tsk;
  bool unpolled_work = false;

  while (true) {
    assert(p->n_runnables == 0);
    if (p->thread_count > p->thread_capacity)
      grow_poller_bufs(p);
    p->next_runnable = 0;
    p->n_warm_runnables = 0;
    p->last_earmark = NULL;

    bool unfinished_earmarks = p->earmark_count > 0;
    if (unfinished_earmarks || p->resched_first)
      unpolled_work = true;
    bool epoll_immediate = unpolled_work || !can_block;

    // Determine if notify_poller() can be avoided.
    if (!epoll_immediate) {
      lock(&p->eventfd_mutex);
      if (p->ready_list_first) {
        unpolled_work = true;
        epoll_immediate = true;
      } else {
        // Poller may sleep.  Enable eventfd wakeup.
        p->ready_list_active = false;
      }
      unlock(&p->eventfd_mutex);
    }

    int timeout = (epoll_immediate) ? 0 : -1;
    p->poller_suspended = (timeout == -1);
    unlock(&p->sched_mutex);

    n_events = epoll_wait(p->epollfd, p->kevents, p->kevents_capacity, timeout);

    lock(&p->sched_mutex);
    p->poller_suspended = false;

    if (p->resched_first) {
      // Defer future resched tasks until next do_epoll()
      p->resched_cutoff = p->resched_last;
      assert(p->polled_resched_count == 0);
      p->polled_resched_count = p->resched_count;
      p->resched_count = 0;
      unpolled_work = true;
    }
    if (p->earmark_count > 0) {
      p->earmark_drain = true;
      unpolled_work = true;
    }
    // Take stock of ready list before any post_event()
    lock(&p->eventfd_mutex);
    schedule_ready_list(p);
    if (p->sched_ready_first)
      unpolled_work = true;
    if (++p->ready_list_generation == 0) // wrapping OK, but 0 means unset
      p->ready_list_generation = 1;
    unlock(&p->eventfd_mutex);

    if (n_events < 0) {
      if (errno != EINTR)
        perror("epoll_wait"); // TODO: proper log
      if (!can_block && !unpolled_work)
        return true;
      else
        continue;
    } else if (n_events == 0) {
      if (!can_block && !unpolled_work)
        return true;
      else {
        if (!epoll_immediate)
          perror("epoll_wait unexpected timeout"); // TODO: proper log
        if (!unpolled_work)
          continue;
      }
    }

    break;
  }

  // We have unpolled work or at least one new epoll event.
  // Remember tasks that together constitute new work.  See note at beginning about duplicates.

  lock(&p->eventfd_mutex);

  // Longest hold of eventfd_mutex.  The following must be quick with no external calls:
  // post_event(), make_runnable(), assign_thread(), earmark_thread().
  for (int i = 0; i < n_events; i++) {
    tsk = post_event(p, &p->kevents[i]);
    if (tsk)
      make_runnable(tsk);
  }
  unlock(&p->eventfd_mutex);

  if (n_events > 0)
    memset(p->kevents, 0, sizeof(struct epoll_event) * n_events);

  // The list of ready tasks can be very long.  Tradeoff between slow walk through linked
  // list looking for more warm pairings (while holding the sched lock), or letting
  // threads looking for work grab from the front.  Search less when busy.  TODO:
  // instrument an optimal value or heuristic.
  int warm_tries = p->suspend_list_count - p->n_warm_runnables;
  if (warm_tries < 0)
    warm_tries = 0;

  int max_runnables = p->runnables_capacity;
  while (p->sched_ready_count && p->n_runnables < max_runnables && warm_tries) {
    task_t *ctsk = sched_ready_pop_front(p);
    tsk = post_ready(p, ctsk);
    warm_tries--;
    if (tsk)
      make_runnable(tsk);
  }
  // sched_ready list is now either consumed or partially deferred.
  // Allow next_runnable() to see any remaining sched_ready tasks.
  p->sched_ready_pending = p->sched_ready_count > 0;

  while (p->resched_cutoff && p->n_runnables < max_runnables && warm_tries) {
    task_t *ctsk = resched_pop_front(p);
    assert(ctsk->runner == RESCHEDULE_PLACEHOLDER && !ctsk->runnables_idx);
    ctsk->runner = NULL;  // Allow task to run again.
    warm_tries--;
    make_runnable(ctsk);
  }

  if (pni_immediate && !ts->task) {
    // Poller gets to run if possible
    task_t *ptsk;
    if (p->n_runnables) {
      assert(p->next_runnable == 0);
      ptsk = p->runnables[0];
      if (++p->next_runnable == p->n_runnables)
        p->n_runnables = 0;
    } else if (p->n_warm_runnables) {
      // Immediate doesn't contemplate some other (warm) thread running instead
      ptsk = p->warm_runnables[--p->n_warm_runnables];
      tslot_t *ts2 = ptsk->runner;
      ts2->prev_task = ts2->task = NULL;
      ptsk->runner = NULL;
    } else if (p->last_earmark) {
      ptsk = p->last_earmark->task;
      remove_earmark(p->last_earmark);
      if (p->earmark_count == 0)
        p->earmark_drain = false;
    } else {
      ptsk = NULL;
    }
    if (ptsk) {
      assign_thread(ts, ptsk);
    }
  }
  return false;
}

// Call with sched lock, but only as poller.
static void poller_done(struct pn_proactor_t* p, tslot_t *ts) {
  // Create a list of available threads to put to work.
  // ts is the poller thread
  int resume_list_count = 0;
  tslot_t **resume_list2 = NULL;

  if (p->suspend_list_count) {
    int max_resumes = p->n_warm_runnables + p->n_runnables + p->sched_ready_count + p->polled_resched_count;
    max_resumes = pn_min(max_resumes, p->suspend_list_count);
    if (max_resumes) {
      resume_list2 = (tslot_t **) alloca(max_resumes * sizeof(tslot_t *));
      for (int i = 0; i < p->n_warm_runnables ; i++) {
        task_t *tsk = p->warm_runnables[i];
        tslot_t *tsp = tsk->runner;
        if (tsp->state == SUSPENDED) {
          resume_list2[resume_list_count++] = tsp;
          LL_REMOVE(p, suspend_list, tsp);
          p->suspend_list_count--;
          tsp->state = PROCESSING;
        }
      }

      int can_use = p->suspend_list_count;
      if (!ts->task)
        can_use++;
      // Run as many unpaired runnable tasks as possible and allow for a new poller.
      int new_runners = pn_min(p->n_runnables + 1, can_use);
      if (!ts->task)
        new_runners--;  // poller available and does not need resume

      // Rare corner case on startup.  New inbound threads can make the suspend_list too big for resume list.
      new_runners = pn_min(new_runners, p->thread_capacity - resume_list_count);

      for (int i = 0; i < new_runners; i++) {
        tslot_t *tsp = p->suspend_list_head;
        assert(tsp);
        resume_list2[resume_list_count++] = tsp;
        LL_REMOVE(p, suspend_list, tsp);
        p->suspend_list_count--;
        tsp->state = PROCESSING;
      }
    }
  }
  p->poller = NULL;

  if (resume_list_count) {
    // Allows a new poller to run concurrently.  Touch only stack vars.
    unlock(&p->sched_mutex);
    for (int i = 0; i < resume_list_count; i++) {
      resume(p, resume_list2[i]);
    }
    lock(&p->sched_mutex);
  }
}

pn_event_batch_t *pn_proactor_wait(struct pn_proactor_t* p) {
  return next_event_batch(p, true);
}

pn_event_batch_t *pn_proactor_get(struct pn_proactor_t* p) {
  return next_event_batch(p, false);
}

void pn_proactor_done(pn_proactor_t *p, pn_event_batch_t *batch) {
  pconnection_t *pc = batch_pconnection(batch);
  if (pc) {
    pconnection_done(pc);
    // pc possibly freed/invalid
    return;
  }
  pn_listener_t *l = batch_listener(batch);
  if (l) {
    listener_done(l);
    // l possibly freed/invalid
    return;
  }
  praw_connection_t *rc = pni_batch_raw_connection(batch);
  if (rc) {
    pni_raw_connection_done(rc);
    // rc possibly freed/invalid
    return;
  }
  pn_proactor_t *bp = batch_proactor(batch);
  if (bp == p) {
    bool notify = false;
    lock(&p->task.mutex);
    p->task.working = false;
    if (p->timeout_processed) {
      p->timeout_processed = false;
      if (schedule_if_inactive(p))
        notify = true;
    }
    proactor_update_batch(p);
    if (proactor_has_event(p))
      if (schedule(&p->task))
        notify = true;

    unlock(&p->task.mutex);
    lock(&p->sched_mutex);
    tslot_t *ts = p->task.runner;
    tslot_t *resume_thread;
    if (unassign_thread(p, ts, UNUSED, &resume_thread))
      notify = true;
    unlock(&p->sched_mutex);

    if (notify) notify_poller(p);
    if (resume_thread) resume(p, resume_thread);
    return;
  }
}

void pn_proactor_interrupt(pn_proactor_t *p) {
  if (p->interruptfd == -1)
    return;
  uint64_t increment = 1;
  if (write(p->interruptfd, &increment, sizeof(uint64_t)) != sizeof(uint64_t))
    EPOLL_FATAL("setting eventfd", errno);
}

void pn_proactor_set_timeout(pn_proactor_t *p, pn_millis_t t) {
  bool notify = false;
  lock(&p->timeout_mutex);

  lock(&p->task.mutex);
  p->timeout_set = true;
  if (t == 0) {
    p->need_timeout = true;
    if (schedule(&p->task))
      notify = true;
  }
  unlock(&p->task.mutex);

  if (t == 0) {
    if (pni_timer_set(p->timer, 0))
      notify = true;
  } else {
    if (pni_timer_set(p->timer, t + pn_proactor_now_64()))
      notify = true;
  }
  unlock(&p->timeout_mutex);
  if (notify) notify_poller(p);
}

void pn_proactor_cancel_timeout(pn_proactor_t *p) {
  bool notify = false;
  lock(&p->timeout_mutex);

  lock(&p->task.mutex);
  p->timeout_set = false;
  p->need_timeout = false;
  if (schedule_if_inactive(p))
    notify = true;
  unlock(&p->task.mutex);

  if (pni_timer_set(p->timer, 0))
    notify = true;
  unlock(&p->timeout_mutex);
  if (notify) notify_poller(p);
}

void pni_proactor_timeout(pn_proactor_t *p) {
  bool notify = false;
  lock(&p->task.mutex);
  if (!p->task.closing) {
    p->need_timeout = true;
    notify = schedule(&p->task);
  }
  unlock(&p->task.mutex);
  if (notify) notify_poller(p);
}

pn_proactor_t *pn_connection_proactor(pn_connection_t* c) {
  pconnection_t *pc = get_pconnection(c);
  return pc ? pc->task.proactor : NULL;
}

void pn_proactor_disconnect(pn_proactor_t *p, pn_condition_t *cond) {
  bool notify = false;

  lock(&p->task.mutex);
  // Move the whole tasks list into a disconnecting state
  task_t *disconnecting_tasks = p->tasks;
  p->tasks = NULL;
  // First pass: mark each task as disconnecting and update global pending count.
  task_t *tsk = disconnecting_tasks;
  while (tsk) {
    tsk->disconnecting = true;
    tsk->disconnect_ops = 2;   // Second pass below and proactor_remove(), in any order.
    p->disconnects_pending++;
    tsk = tsk->next;
    p->task_count--;
  }
  notify = schedule_if_inactive(p);
  unlock(&p->task.mutex);
  if (!disconnecting_tasks) {
    if (notify) notify_poller(p);
    return;
  }

  // Second pass: different locking, close the tasks, free them if !disconnect_ops
  task_t *next = disconnecting_tasks;
  while (next) {
    tsk = next;
    next = tsk->next;           /* Save next pointer in case we free tsk */
    bool do_free = false;
    bool tsk_notify = false;
    pmutex *tsk_mutex = NULL;
    // TODO: Need to extend this for raw connections too
    pconnection_t *pc = task_pconnection(tsk);
    if (pc) {
      tsk_mutex = &pc->task.mutex;
      lock(tsk_mutex);
      if (!tsk->closing) {
        tsk_notify = true;
        if (tsk->working) {
          // Must defer
          pc->queued_disconnect = true;
          if (cond) {
            if (!pc->disconnect_condition)
              pc->disconnect_condition = pn_condition();
            pn_condition_copy(pc->disconnect_condition, cond);
          }
        }
        else {
          // No conflicting working task.
          if (cond) {
            pn_condition_copy(pn_transport_condition(pc->driver.transport), cond);
          }
          pn_connection_driver_close(&pc->driver);
        }
      }
    } else {
      pn_listener_t *l = task_listener(tsk);
      assert(l);
      tsk_mutex = &l->task.mutex;
      lock(tsk_mutex);
      if (!tsk->closing) {
        tsk_notify = true;
        if (cond) {
          pn_condition_copy(pn_listener_condition(l), cond);
        }
        listener_begin_close(l);
      }
    }

    lock(&p->task.mutex);
    if (--tsk->disconnect_ops == 0) {
      do_free = true;
      tsk_notify = false;
      notify = schedule_if_inactive(p);
    } else {
      // If initiating the close, schedule the task to do the free.
      if (tsk_notify)
        tsk_notify = schedule(tsk);
      if (tsk_notify)
        notify_poller(p);
    }
    unlock(&p->task.mutex);
    unlock(tsk_mutex);

    // Unsafe to touch tsk after lock release, except if we are the designated final_free
    if (do_free) {
      if (pc) pconnection_final_free(pc);
      else listener_final_free(task_listener(tsk));
    }
  }
  if (notify)
    notify_poller(p);
}

const pn_netaddr_t *pn_transport_local_addr(pn_transport_t *t) {
  pconnection_t *pc = get_pconnection(pn_transport_connection(t));
  return pc? &pc->local : NULL;
}

const pn_netaddr_t *pn_transport_remote_addr(pn_transport_t *t) {
  pconnection_t *pc = get_pconnection(pn_transport_connection(t));
  return pc ? &pc->remote : NULL;
}

const pn_netaddr_t *pn_listener_addr(pn_listener_t *l) {
  return l->acceptors_size > 0 ? &l->acceptors[0].addr : NULL;
}

pn_millis_t pn_proactor_now(void) {
  return (pn_millis_t) pn_proactor_now_64();
}

int64_t pn_proactor_now_64(void) {
  struct timespec t;
  clock_gettime(CLOCK_MONOTONIC, &t);
  return ((int64_t)t.tv_sec) * 1000 + t.tv_nsec / 1000000;
}
