/*
 *
 * 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.
 *
 */

#include <proton/condition.h>
#include <proton/connection_driver.h>
#include <proton/engine.h>
#include <proton/message.h>
#include <proton/object.h>
#include <proton/proactor.h>
#include <proton/transport.h>
#include <proton/listener.h>
#include <proton/proactor.h>

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

#include <winsock2.h>
#include <mswsock.h>
#include <Ws2tcpip.h>

#include <iostream>
#include <sstream>

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

/*
 * Proactor for Windows using IO completion ports.
 *
 * There is no specific threading code other than locking and indirect (and
 * brief) calls to the default threadpool via timerqueue timers.
 *
 * The IOCP system remembers what threads have called pn_proactor_wait and
 * assumes they are doing proactor work until they call wait again.  Using a
 * dedicated threadpool to drive the proactor, as in the examples, is
 * recommended.  The use of other long-lived threads to occasionally drive the
 * proactor will hurt the completion port scheduler and cause fewer threads to
 * run.
 *
 * Each proactor connection maintains its own queue of pending completions and
 * its work is serialized on that.  The proactor listener runs parallel where
 * possible, but its event batch is serialized.
 */

// TODO: make all code C++ or all C90-ish
//       change INACTIVE to be from begin_close instead of zombie reap, to be more like Posix
//       make the global write lock window much smaller
//       2 exclusive write buffers per connection
//       make the zombie processing thread safe
//       configurable completion port concurrency
//       proton objects and ref counting: just say no.



// From selector.h
#define PN_READABLE (1)
#define PN_WRITABLE (2)
#define PN_EXPIRED (4)
#define PN_ERROR (8)

typedef SOCKET pn_socket_t;


namespace pn_experimental {
// Code borrowed from old messenger/reactor io.c iocp.c write_pipeline.c select.c
// Mostly unchanged except fro some thread safety (i.e. dropping the old descriptor map)

typedef struct pni_acceptor_t pni_acceptor_t;
typedef struct write_result_t write_result_t;
typedef struct read_result_t read_result_t;
typedef struct write_pipeline_t write_pipeline_t;
typedef struct iocpdesc_t iocpdesc_t;
typedef struct iocp_t iocp_t;


// One per completion port.

struct iocp_t {
  HANDLE completion_port;
  pn_list_t *zombie_list;
  unsigned shared_pool_size;
  char *shared_pool_memory;
  write_result_t **shared_results;
  write_result_t **available_results;
  size_t shared_available_count;
  size_t writer_count;
  int loopback_bufsize;
  bool iocp_trace;
};

// One for each socket.
// It should remain ref counted in the
// zombie_list until ops_in_progress == 0 or the completion port is closed.

struct iocpdesc_t {
  pn_socket_t socket;
  iocp_t *iocp;
  void *active_completer;
  pni_acceptor_t *acceptor;
  pn_error_t *error;
  int ops_in_progress;
  bool read_in_progress;
  write_pipeline_t *pipeline;
  read_result_t *read_result;
  bool bound;          // associated with the completion port
  bool closing;        // close called by application
  bool read_closed;    // EOF or read error
  bool write_closed;   // shutdown sent or write error
  bool poll_error;     // flag posix-like POLLERR/POLLHUP/POLLNVAL
  bool is_mp;          // Special multi thread care required
  bool write_blocked;
  int events;
  pn_timestamp_t reap_time;
};


// Max number of overlapped accepts per listener.  TODO: configurable.
#define IOCP_MAX_ACCEPTS 4

// AcceptEx squishes the local and remote addresses and optional data
// all together when accepting the connection. Reserve enough for
// IPv6 addresses, even if the socket is IPv4. The 16 bytes padding
// per address is required by AcceptEx.
#define IOCP_SOCKADDRMAXLEN (sizeof(sockaddr_in6) + 16)
#define IOCP_SOCKADDRBUFLEN (2 * IOCP_SOCKADDRMAXLEN)

typedef enum { IOCP_ACCEPT, IOCP_CONNECT, IOCP_READ, IOCP_WRITE } iocp_type_t;

typedef struct {
  OVERLAPPED overlapped;
  iocp_type_t type;
  iocpdesc_t *iocpd;
  HRESULT status;
  DWORD num_transferred;
} iocp_result_t;

// Completion keys to distinguish IO from some other user port completions
enum {
    proactor_io = 0,
    proactor_wake_key,
    psocket_wakeup_key,
    recycle_accept_key,
};

struct read_result_t {
  iocp_result_t base;
  size_t drain_count;
  char unused_buf[1];
};

struct write_result_t {
  iocp_result_t base;
  size_t requested;
  bool in_use;
  pn_bytes_t buffer;
};

typedef struct {
  iocp_result_t base;
  char address_buffer[IOCP_SOCKADDRBUFLEN];
  struct addrinfo *addrinfo;
} connect_result_t;

typedef struct {
  iocp_result_t base;
  iocpdesc_t *new_sock;
  char address_buffer[IOCP_SOCKADDRBUFLEN];
  DWORD unused;
} accept_result_t;


void complete_connect(connect_result_t *result, HRESULT status);
void complete_write(write_result_t *result, DWORD xfer_count, HRESULT status);
void complete_read(read_result_t *result, DWORD xfer_count, HRESULT status);
void start_reading(iocpdesc_t *iocpd);
void begin_accept(pni_acceptor_t *acceptor, accept_result_t *result);
void reset_accept_result(accept_result_t *result);
iocpdesc_t *create_same_type_socket(iocpdesc_t *iocpd);
void pni_iocp_reap_check(iocpdesc_t *iocpd);
connect_result_t *connect_result(iocpdesc_t *iocpd, struct addrinfo *addr);
iocpdesc_t *pni_iocpdesc_create(iocp_t *, pn_socket_t s);
iocpdesc_t *pni_deadline_desc(iocp_t *);
void pni_iocpdesc_start(iocpdesc_t *iocpd);
void pni_iocp_drain_completions(iocp_t *);
int pni_iocp_wait_one(iocp_t *, int timeout, pn_error_t *);
void pni_iocp_start_accepting(iocpdesc_t *iocpd);
pn_socket_t pni_iocp_end_accept(iocpdesc_t *ld, sockaddr *addr, socklen_t *addrlen, bool *would_block, pn_error_t *error);
pn_socket_t pni_iocp_begin_connect(iocp_t *, pn_socket_t sock, struct addrinfo *addr, pn_error_t *error);
ssize_t pni_iocp_begin_write(iocpdesc_t *, const void *, size_t, bool *, pn_error_t *);
ssize_t pni_iocp_recv(iocpdesc_t *iocpd, void *buf, size_t size, bool *would_block, pn_error_t *error);
void pni_iocp_begin_close(iocpdesc_t *iocpd);
iocp_t *pni_iocp();

void pni_events_update(iocpdesc_t *iocpd, int events);
write_result_t *pni_write_result(iocpdesc_t *iocpd, const char *buf, size_t buflen);
void pni_zombie_check(iocp_t *, pn_timestamp_t);
pn_timestamp_t pni_zombie_deadline(iocp_t *);

int pni_win32_error(pn_error_t *error, const char *msg, HRESULT code);

pn_timestamp_t pn_i_now2(void);
}

// ======================================================================
// Write pipeline.
// ======================================================================

/*
 * A simple write buffer pool.  Each socket has a dedicated "primary"
 * buffer and can borrow from a shared pool with limited size tuning.
 * Could enhance e.g. with separate pools per network interface and fancier
 * memory tuning based on interface speed, system resources, and
 * number of connections, etc.
 */

namespace pn_experimental {

// Max overlapped writes per socket
#define IOCP_MAX_OWRITES 16
// Write buffer size
#define IOCP_WBUFSIZE 16384

static void pipeline_log(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fflush(stderr);
}

void pni_shared_pool_create(iocp_t *iocp)
{
  // TODO: more pools (or larger one) when using multiple non-loopback interfaces
  iocp->shared_pool_size = 16;
  char *env = getenv("PNI_WRITE_BUFFERS"); // Internal: for debugging
  if (env) {
    int sz = atoi(env);
    if (sz >= 0 && sz < 256) {
      iocp->shared_pool_size = sz;
    }
  }
  iocp->loopback_bufsize = 0;
  env = getenv("PNI_LB_BUFSIZE"); // Internal: for debugging
  if (env) {
    int sz = atoi(env);
    if (sz >= 0 && sz <= 128 * 1024) {
      iocp->loopback_bufsize = sz;
    }
  }

  if (iocp->shared_pool_size) {
    iocp->shared_pool_memory = (char *) VirtualAlloc(NULL, IOCP_WBUFSIZE * iocp->shared_pool_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    HRESULT status = GetLastError();
    if (!iocp->shared_pool_memory) {
      perror("Proton write buffer pool allocation failure\n");
      iocp->shared_pool_size = 0;
      iocp->shared_available_count = 0;
      return;
    }

    iocp->shared_results = (write_result_t **) malloc(iocp->shared_pool_size * sizeof(write_result_t *));
    iocp->available_results = (write_result_t **) malloc(iocp->shared_pool_size * sizeof(write_result_t *));
    iocp->shared_available_count = iocp->shared_pool_size;
    char *mem = iocp->shared_pool_memory;
    for (unsigned i = 0; i < iocp->shared_pool_size; i++) {
      iocp->shared_results[i] = iocp->available_results[i] = pni_write_result(NULL, mem, IOCP_WBUFSIZE);
      mem += IOCP_WBUFSIZE;
    }
  }
}

void pni_shared_pool_free(iocp_t *iocp)
{
  for (unsigned i = 0; i < iocp->shared_pool_size; i++) {
    write_result_t *result = iocp->shared_results[i];
    if (result->in_use)
      pipeline_log("Proton buffer pool leak\n");
    else
      free(result);
  }
  if (iocp->shared_pool_size) {
    free(iocp->shared_results);
    free(iocp->available_results);
    if (iocp->shared_pool_memory) {
      if (!VirtualFree(iocp->shared_pool_memory, 0, MEM_RELEASE)) {
        perror("write buffers release failed");
      }
      iocp->shared_pool_memory = NULL;
    }
  }
}

static void shared_pool_push(write_result_t *result)
{
  iocp_t *iocp = result->base.iocpd->iocp;
  assert(iocp->shared_available_count < iocp->shared_pool_size);
  iocp->available_results[iocp->shared_available_count++] = result;
}

static write_result_t *shared_pool_pop(iocp_t *iocp)
{
  return iocp->shared_available_count ? iocp->available_results[--iocp->shared_available_count] : NULL;
}

struct write_pipeline_t {
  iocpdesc_t *iocpd;
  size_t pending_count;
  write_result_t *primary;
  size_t reserved_count;
  size_t next_primary_index;
  size_t depth;
  bool is_writer;
};

#define write_pipeline_compare NULL
#define write_pipeline_inspect NULL
#define write_pipeline_hashcode NULL

static void write_pipeline_initialize(void *object)
{
  write_pipeline_t *pl = (write_pipeline_t *) object;
  pl->pending_count = 0;
  const char *pribuf = (const char *) malloc(IOCP_WBUFSIZE);
  pl->primary = pni_write_result(NULL, pribuf, IOCP_WBUFSIZE);
  pl->depth = 0;
  pl->is_writer = false;
}

static void write_pipeline_finalize(void *object)
{
  write_pipeline_t *pl = (write_pipeline_t *) object;
  free((void *)pl->primary->buffer.start);
  free(pl->primary);
}

write_pipeline_t *pni_write_pipeline(iocpdesc_t *iocpd)
{
  static const pn_cid_t CID_write_pipeline = CID_pn_void;
  static const pn_class_t clazz = PN_CLASS(write_pipeline);
  write_pipeline_t *pipeline = (write_pipeline_t *) pn_class_new(&clazz, sizeof(write_pipeline_t));
  pipeline->iocpd = iocpd;
  pipeline->primary->base.iocpd = iocpd;
  return pipeline;
}

static void confirm_as_writer(write_pipeline_t *pl)
{
  if (!pl->is_writer) {
    iocp_t *iocp = pl->iocpd->iocp;
    iocp->writer_count++;
    pl->is_writer = true;
  }
}

static void remove_as_writer(write_pipeline_t *pl)
{
  if (!pl->is_writer)
    return;
  iocp_t *iocp = pl->iocpd->iocp;
  assert(iocp->writer_count);
  pl->is_writer = false;
  iocp->writer_count--;
}

/*
 * Optimal depth will depend on properties of the NIC, server, and driver.  For now,
 * just distinguish between loopback interfaces and the rest.  Optimizations in the
 * loopback stack allow decent performance with depth 1 and actually cause major
 * performance hiccups if set to large values.
 */
static void set_depth(write_pipeline_t *pl)
{
  pl->depth = 1;
  sockaddr_storage sa;
  socklen_t salen = sizeof(sa);
  char buf[INET6_ADDRSTRLEN];
  DWORD buflen = sizeof(buf);

  if (getsockname(pl->iocpd->socket,(sockaddr*) &sa, &salen) == 0 &&
      getnameinfo((sockaddr*) &sa, salen, buf, buflen, NULL, 0, NI_NUMERICHOST) == 0) {
    if ((sa.ss_family == AF_INET6 && strcmp(buf, "::1")) ||
        (sa.ss_family == AF_INET && strncmp(buf, "127.", 4))) {
      // not loopback
      pl->depth = IOCP_MAX_OWRITES;
    } else {
      iocp_t *iocp = pl->iocpd->iocp;
      if (iocp->loopback_bufsize) {
        const char *p = (const char *) realloc((void *) pl->primary->buffer.start, iocp->loopback_bufsize);
        if (p) {
          pl->primary->buffer.start = p;
          pl->primary->buffer.size = iocp->loopback_bufsize;
        }
      }
    }
  }
}

static size_t pni_min(size_t a, size_t b) { return a < b ? a : b; }

// Reserve as many buffers as possible for count bytes.
size_t pni_write_pipeline_reserve(write_pipeline_t *pl, size_t count)
{
  if (pl->primary->in_use)
    return 0;  // I.e. io->wouldblock
  if (!pl->depth)
    set_depth(pl);
  if (pl->depth == 1) {
    // always use the primary
    pl->reserved_count = 1;
    pl->next_primary_index = 0;
    return 1;
  }

  iocp_t *iocp = pl->iocpd->iocp;
  confirm_as_writer(pl);
  size_t wanted = (count / IOCP_WBUFSIZE);
  if (count % IOCP_WBUFSIZE)
    wanted++;
  size_t pending = pl->pending_count;
  assert(pending < pl->depth);
  size_t bufs = pni_min(wanted, pl->depth - pending);
  // Can draw from shared pool or the primary... but share with others.
  size_t writers = iocp->writer_count;
  size_t shared_count = (iocp->shared_available_count + writers - 1) / writers;
  bufs = pni_min(bufs, shared_count + 1);
  pl->reserved_count = pending + bufs;

  if (bufs == wanted &&
      pl->reserved_count < (pl->depth / 2) &&
      iocp->shared_available_count > (2 * writers + bufs)) {
    // No shortage: keep the primary as spare for future use
    pl->next_primary_index = pl->reserved_count;
  } else if (bufs == 1) {
    pl->next_primary_index = pending;
  } else {
    // let approx 1/3 drain before replenishing
    pl->next_primary_index = ((pl->reserved_count + 2) / 3) - 1;
    if (pl->next_primary_index < pending)
      pl->next_primary_index = pending;
  }
  return bufs;
}

write_result_t *pni_write_pipeline_next(write_pipeline_t *pl)
{
  size_t sz = pl->pending_count;
  if (sz >= pl->reserved_count)
    return NULL;
  write_result_t *result;
  if (sz == pl->next_primary_index) {
    result = pl->primary;
  } else {
    assert(pl->iocpd->iocp->shared_available_count > 0);
    result = shared_pool_pop(pl->iocpd->iocp);
  }

  result->in_use = true;
  pl->pending_count++;
  return result;
}

void pni_write_pipeline_return(write_pipeline_t *pl, write_result_t *result)
{
  result->in_use = false;
  pl->pending_count--;
  pl->reserved_count = 0;
  if (result != pl->primary)
    shared_pool_push(result);
  if (pl->pending_count == 0)
    remove_as_writer(pl);
}

bool pni_write_pipeline_writable(write_pipeline_t *pl)
{
  // Only writable if not full and we can guarantee a buffer:
  return pl->pending_count < pl->depth && !pl->primary->in_use;
}

size_t pni_write_pipeline_size(write_pipeline_t *pl)
{
  return pl->pending_count;
}

}


// ======================================================================
// IOCP infrastructure code
// ======================================================================

/*
 * Socket IO including graceful or forced zombie teardown.
 *
 * Overlapped writes are used to avoid lengthy stalls between write
 * completion and starting a new write.  Non-overlapped reads are used
 * since Windows accumulates inbound traffic without stalling and
 * managing read buffers would not avoid a memory copy at the pn_read
 * boundary.
 *
 * A socket must not get a Windows closesocket() unless the
 * application has called pn_connection_close on the connection or a
 * global single threaded shutdown is in progress. On error, the
 * internal accounting for write_closed or read_closed may be updated
 * along with the external event notification.  A socket may be
 * directly closed if it is never gets started (accept/listener_close
 * collision) or otherwise turned into a zombie.
 */

namespace pn_experimental {

pn_timestamp_t pn_i_now2(void)
{
  FILETIME now;
  GetSystemTimeAsFileTime(&now);
  ULARGE_INTEGER t;
  t.u.HighPart = now.dwHighDateTime;
  t.u.LowPart = now.dwLowDateTime;
  // Convert to milliseconds and adjust base epoch
  return t.QuadPart / 10000 - 11644473600000;
}

static void iocp_log(const char *fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  vfprintf(stderr, fmt, ap);
  va_end(ap);
  fflush(stderr);
}

static void set_iocp_error_status(pn_error_t *error, int code, HRESULT status)
{
  char buf[512];
  if (FormatMessage(FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_FROM_SYSTEM,
                    0, status, 0, buf, sizeof(buf), 0))
    pn_error_set(error, code, buf);
  else {
    fprintf(stderr, "pn internal Windows error: %lu\n", GetLastError());
  }
}

static void reap_check(iocpdesc_t *);
static void bind_to_completion_port(iocpdesc_t *iocpd);
static void iocp_shutdown(iocpdesc_t *iocpd);
static bool is_listener(iocpdesc_t *iocpd);
static void release_sys_sendbuf(SOCKET s);

int pni_win32_error(pn_error_t *error, const char *msg, HRESULT code)
{
  // Error code can be from GetLastError or WSAGetLastError,
  char err[1024] = {0};
  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
                FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, code, 0, (LPSTR)&err, sizeof(err), NULL);
  return pn_error_format(error, PN_ERR, "%s: %s", msg, err);
}

static void iocpdesc_fail(iocpdesc_t *iocpd, HRESULT status, const char* text)
{
  pni_win32_error(iocpd->error, text, status);
  if (iocpd->iocp->iocp_trace) {
    iocp_log("connection terminated: %s\n", pn_error_text(iocpd->error));
  }
  iocpd->write_closed = true;
  iocpd->read_closed = true;
  iocpd->poll_error = true;
  pni_events_update(iocpd, iocpd->events & ~(PN_READABLE | PN_WRITABLE));
}

static int pni_strcasecmp(const char *a, const char *b)
{
  int diff;
  while (*b) {
    char aa = *a++, bb = *b++;
    diff = tolower(aa)-tolower(bb);
    if ( diff!=0 ) return diff;
  }
  return *a;
}

static void pni_events_update(iocpdesc_t *iocpd, int events)
{
  // If set, a poll error is permanent
  if (iocpd->poll_error)
    events |= PN_ERROR;
  if (iocpd->events == events)
    return;
  // ditch old code to update list of ready selectables
  iocpd->events = events;
}

// Helper functions to use specialized IOCP AcceptEx() and ConnectEx()
static LPFN_ACCEPTEX lookup_accept_ex(SOCKET s)
{
  GUID guid = WSAID_ACCEPTEX;
  DWORD bytes = 0;
  LPFN_ACCEPTEX fn;
  WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
           &fn, sizeof(fn), &bytes, NULL, NULL);
  assert(fn);
  return fn;
}

static LPFN_CONNECTEX lookup_connect_ex(SOCKET s)
{
  GUID guid = WSAID_CONNECTEX;
  DWORD bytes = 0;
  LPFN_CONNECTEX fn;
  WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
           &fn, sizeof(fn), &bytes, NULL, NULL);
  assert(fn);
  return fn;
}

static LPFN_GETACCEPTEXSOCKADDRS lookup_get_accept_ex_sockaddrs(SOCKET s)
{
  GUID guid = WSAID_GETACCEPTEXSOCKADDRS;
  DWORD bytes = 0;
  LPFN_GETACCEPTEXSOCKADDRS fn;
  WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
           &fn, sizeof(fn), &bytes, NULL, NULL);
  assert(fn);
  return fn;
}

// match accept socket to listener socket
iocpdesc_t *create_same_type_socket(iocpdesc_t *iocpd)
{
  sockaddr_storage sa;
  socklen_t salen = sizeof(sa);
  if (getsockname(iocpd->socket, (sockaddr*)&sa, &salen) == -1)
    return NULL;
  SOCKET s = socket(sa.ss_family, SOCK_STREAM, 0); // Currently only work with SOCK_STREAM
  if (s == INVALID_SOCKET)
    return NULL;
  return pni_iocpdesc_create(iocpd->iocp, s);
}

static bool is_listener(iocpdesc_t *iocpd)
{
  return iocpd && iocpd->acceptor;
}

// === Async accept processing

static accept_result_t *accept_result(iocpdesc_t *listen_sock) {
  accept_result_t *result = (accept_result_t *)calloc(1, sizeof(accept_result_t));
  if (result) {
    result->base.type = IOCP_ACCEPT;
    result->base.iocpd = listen_sock;
  }
  return result;
}

void reset_accept_result(accept_result_t *result) {
  memset(&result->base.overlapped, 0, sizeof (OVERLAPPED));
  memset(&result->address_buffer, 0, IOCP_SOCKADDRBUFLEN);
  result->new_sock = NULL;
}

struct pni_acceptor_t {
  int accept_queue_size;
  pn_list_t *accepts;
  iocpdesc_t *listen_sock;
  bool signalled;
  LPFN_ACCEPTEX fn_accept_ex;
  LPFN_GETACCEPTEXSOCKADDRS fn_get_accept_ex_sockaddrs;
};

#define pni_acceptor_compare NULL
#define pni_acceptor_inspect NULL
#define pni_acceptor_hashcode NULL

static void pni_acceptor_initialize(void *object)
{
  pni_acceptor_t *acceptor = (pni_acceptor_t *) object;
  acceptor->accepts = pn_list(PN_VOID, IOCP_MAX_ACCEPTS);
}

static void pni_acceptor_finalize(void *object)
{
  pni_acceptor_t *acceptor = (pni_acceptor_t *) object;
  size_t len = pn_list_size(acceptor->accepts);
  for (size_t i = 0; i < len; i++)
    free(pn_list_get(acceptor->accepts, i));
  pn_free(acceptor->accepts);
}

static pni_acceptor_t *pni_acceptor(iocpdesc_t *iocpd)
{
  static const pn_cid_t CID_pni_acceptor = CID_pn_void;
  static const pn_class_t clazz = PN_CLASS(pni_acceptor);
  pni_acceptor_t *acceptor = (pni_acceptor_t *) pn_class_new(&clazz, sizeof(pni_acceptor_t));
  acceptor->listen_sock = iocpd;
  acceptor->accept_queue_size = 0;
  acceptor->signalled = false;
  pn_socket_t sock = acceptor->listen_sock->socket;
  acceptor->fn_accept_ex = lookup_accept_ex(sock);
  acceptor->fn_get_accept_ex_sockaddrs = lookup_get_accept_ex_sockaddrs(sock);
  return acceptor;
}

void begin_accept(pni_acceptor_t *acceptor, accept_result_t *result)
{
  // flag to divide this routine's logic into locked/unlocked mp portions
  bool mp = acceptor->listen_sock->is_mp;
  bool created = false;

  if (acceptor->listen_sock->closing) {
    if (result) {
      if (mp && result->new_sock && result->new_sock->socket != INVALID_SOCKET)
        closesocket(result->new_sock->socket);
      free(result);
      acceptor->accept_queue_size--;
    }
    if (acceptor->accept_queue_size == 0)
      acceptor->signalled = true;
    return;
  }

  if (result) {
    if (!mp)
      reset_accept_result(result);
  } else {
    if (acceptor->accept_queue_size < IOCP_MAX_ACCEPTS && (mp ||
           pn_list_size(acceptor->accepts) == acceptor->accept_queue_size )) {
      result = accept_result(acceptor->listen_sock);
      acceptor->accept_queue_size++;
      created = true;
    } else {
      // an async accept is still pending or max concurrent accepts already hit
      return;
    }
  }

  if (created || !mp)
    result->new_sock = create_same_type_socket(acceptor->listen_sock);
  if (result->new_sock) {
    // Not yet connected.
    result->new_sock->read_closed = true;
    result->new_sock->write_closed = true;

    bool success = acceptor->fn_accept_ex(acceptor->listen_sock->socket, result->new_sock->socket,
                     result->address_buffer, 0, IOCP_SOCKADDRMAXLEN, IOCP_SOCKADDRMAXLEN,
                     &result->unused, (LPOVERLAPPED) result);
    if (!success) {
      DWORD err = WSAGetLastError();
      if (err != ERROR_IO_PENDING) {
        if (err == WSAECONNRESET) {
          // other side gave up.  Ignore and try again.
          begin_accept(acceptor, result);
          return;
        }
        else {
          iocpdesc_fail(acceptor->listen_sock, err, "AcceptEX call failure");
          return;
        }
      }
      acceptor->listen_sock->ops_in_progress++;
      // This socket is equally involved in the async operation.
      result->new_sock->ops_in_progress++;
    }
  } else {
    iocpdesc_fail(acceptor->listen_sock, WSAGetLastError(), "create accept socket");
  }
}

static void complete_accept(accept_result_t *result, HRESULT status)
{
  result->new_sock->ops_in_progress--;
  iocpdesc_t *ld = result->base.iocpd;
  if (ld->read_closed) {
    if (!result->new_sock->closing)
      pni_iocp_begin_close(result->new_sock);
    pn_decref(result->new_sock);
    free(result);    // discard
    reap_check(ld);
  } else {
    assert(!ld->is_mp);  // Non mp only
    result->base.status = status;
    pn_list_add(ld->acceptor->accepts, result);
    pni_events_update(ld, ld->events | PN_READABLE);
  }
}


// === Async connect processing


#define connect_result_initialize NULL
#define connect_result_compare NULL
#define connect_result_inspect NULL
#define connect_result_hashcode NULL

static void connect_result_finalize(void *object)
{
  connect_result_t *result = (connect_result_t *) object;
  // Do not release addrinfo until ConnectEx completes
  if (result->addrinfo)
    freeaddrinfo(result->addrinfo);
}

connect_result_t *connect_result(iocpdesc_t *iocpd, struct addrinfo *addr) {
  static const pn_cid_t CID_connect_result = CID_pn_void;
  static const pn_class_t clazz = PN_CLASS(connect_result);
  connect_result_t *result = (connect_result_t *) pn_class_new(&clazz, sizeof(connect_result_t));
  if (result) {
    memset(result, 0, sizeof(connect_result_t));
    result->base.type = IOCP_CONNECT;
    result->base.iocpd = iocpd;
    result->addrinfo = addr;
  }
  return result;
}

pn_socket_t pni_iocp_begin_connect(iocp_t *iocp, pn_socket_t sock, struct addrinfo *addr, pn_error_t *error)
{
  // addr lives for the duration of the async connect.  Caller has passed ownership here.
  // See connect_result_finalize().
  // Use of Windows-specific ConnectEx() requires our socket to be "loosely" pre-bound:
  sockaddr_storage sa;
  memset(&sa, 0, sizeof(sa));
  sa.ss_family = addr->ai_family;
  if (bind(sock, (SOCKADDR *) &sa, addr->ai_addrlen)) {
    pni_win32_error(error, "begin async connection", WSAGetLastError());
    if (iocp->iocp_trace)
      iocp_log("%s\n", pn_error_text(error));
    closesocket(sock);
    freeaddrinfo(addr);
    return INVALID_SOCKET;
  }

  iocpdesc_t *iocpd = pni_iocpdesc_create(iocp, sock);
  bind_to_completion_port(iocpd);
  LPFN_CONNECTEX fn_connect_ex = lookup_connect_ex(iocpd->socket);
  connect_result_t *result = connect_result(iocpd, addr);
  DWORD unused;
  bool success = fn_connect_ex(iocpd->socket, result->addrinfo->ai_addr, result->addrinfo->ai_addrlen,
                               NULL, 0, &unused, (LPOVERLAPPED) result);
  if (!success && WSAGetLastError() != ERROR_IO_PENDING) {
    pni_win32_error(error, "ConnectEx failure", WSAGetLastError());
    pn_free(result);
    iocpd->write_closed = true;
    iocpd->read_closed = true;
    if (iocp->iocp_trace)
      iocp_log("%s\n", pn_error_text(error));
  } else {
    iocpd->ops_in_progress++;
  }
  return sock;
}

void complete_connect(connect_result_t *result, HRESULT status)
{
  iocpdesc_t *iocpd = result->base.iocpd;
  if (iocpd->closing) {
    pn_free(result);
    reap_check(iocpd);
    return;
  }

  if (status) {
    iocpdesc_fail(iocpd, status, "Connect failure");
    // Posix sets selectable events as follows:
    pni_events_update(iocpd, PN_READABLE | PN_EXPIRED);
  } else {
    release_sys_sendbuf(iocpd->socket);
    if (setsockopt(iocpd->socket, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT,  NULL, 0)) {
      iocpdesc_fail(iocpd, WSAGetLastError(), "Internal connect failure (update context)");
    } else {
      pni_events_update(iocpd, PN_WRITABLE);
      start_reading(iocpd);
    }
  }
  pn_free(result);
  return;
}


// === Async writes

static bool write_in_progress(iocpdesc_t *iocpd)
{
  return pni_write_pipeline_size(iocpd->pipeline) != 0;
}

write_result_t *pni_write_result(iocpdesc_t *iocpd, const char *buf, size_t buflen)
{
  write_result_t *result = (write_result_t *) calloc(sizeof(write_result_t), 1);
  if (result) {
    result->base.type = IOCP_WRITE;
    result->base.iocpd = iocpd;
    result->buffer.start = buf;
    result->buffer.size = buflen;
  }
  return result;
}

static int submit_write(write_result_t *result, const void *buf, size_t len)
{
  WSABUF wsabuf;
  wsabuf.buf = (char *) buf;
  wsabuf.len = len;
  memset(&result->base.overlapped, 0, sizeof (OVERLAPPED));
  return WSASend(result->base.iocpd->socket, &wsabuf, 1, NULL, 0,
                 (LPOVERLAPPED) result, 0);
}

ssize_t pni_iocp_begin_write(iocpdesc_t *iocpd, const void *buf, size_t len, bool *would_block, pn_error_t *error)
{
  if (len == 0) return 0;
  *would_block = false;
  if (is_listener(iocpd)) {
    set_iocp_error_status(error, PN_ERR, WSAEOPNOTSUPP);
    return INVALID_SOCKET;
  }
  if (iocpd->closing) {
    set_iocp_error_status(error, PN_ERR, WSAESHUTDOWN);
    return SOCKET_ERROR;
  }
  if (iocpd->write_closed) {
    assert(pn_error_code(iocpd->error));
    pn_error_copy(error, iocpd->error);
    if (iocpd->iocp->iocp_trace)
      iocp_log("write error: %s\n", pn_error_text(error));
    return SOCKET_ERROR;
  }
  if (len == 0) return 0;
  if (!(iocpd->events & PN_WRITABLE)) {
    *would_block = true;
    return SOCKET_ERROR;
  }

  size_t written = 0;
  size_t requested = len;
  const char *outgoing = (const char *) buf;
  size_t available = pni_write_pipeline_reserve(iocpd->pipeline, len);
  if (!available) {
    *would_block = true;
    return SOCKET_ERROR;
  }

  for (size_t wr_count = 0; wr_count < available; wr_count++) {
    write_result_t *result = pni_write_pipeline_next(iocpd->pipeline);
    assert(result);
    result->base.iocpd = iocpd;
    ssize_t actual_len = len;
    if (len > result->buffer.size) actual_len = result->buffer.size;
    result->requested = actual_len;
    memmove((void *)result->buffer.start, outgoing, actual_len);
    outgoing += actual_len;
    written += actual_len;
    len -= actual_len;

    int werror = submit_write(result, result->buffer.start, actual_len);
    if (werror && WSAGetLastError() != ERROR_IO_PENDING) {
      pni_write_pipeline_return(iocpd->pipeline, result);
      iocpdesc_fail(iocpd, WSAGetLastError(), "overlapped send");
      return SOCKET_ERROR;
    }
    iocpd->ops_in_progress++;
  }

  if (!pni_write_pipeline_writable(iocpd->pipeline))
    pni_events_update(iocpd, iocpd->events & ~PN_WRITABLE);
  return written;
}

/*
 * Note: iocp write completion is not "bytes on the wire", it is "peer
 * acked the sent bytes".  Completion can be seconds on a slow
 * consuming peer.
 */
void complete_write(write_result_t *result, DWORD xfer_count, HRESULT status)
{
  iocpdesc_t *iocpd = result->base.iocpd;
  if (iocpd->closing) {
    pni_write_pipeline_return(iocpd->pipeline, result);
    if (!iocpd->write_closed && !write_in_progress(iocpd))
      iocp_shutdown(iocpd);
    reap_check(iocpd);
    return;
  }
  if (status == 0 && xfer_count > 0) {
    if (xfer_count != result->requested) {
      // Is this recoverable?  How to preserve order if multiple overlapped writes?
      pni_write_pipeline_return(iocpd->pipeline, result);
      iocpdesc_fail(iocpd, WSA_OPERATION_ABORTED, "Partial overlapped write on socket");
      return;
    } else {
      // Success.
      pni_write_pipeline_return(iocpd->pipeline, result);
      if (pni_write_pipeline_writable(iocpd->pipeline))
        pni_events_update(iocpd, iocpd->events | PN_WRITABLE);
      return;
    }
  }
  // Other error
  pni_write_pipeline_return(iocpd->pipeline, result);
  if (status == WSAECONNABORTED || status == WSAECONNRESET || status == WSAENOTCONN
      || status == ERROR_NETNAME_DELETED) {
    iocpd->write_closed = true;
    iocpd->poll_error = true;
    pni_events_update(iocpd, iocpd->events & ~PN_WRITABLE);
    pni_win32_error(iocpd->error, "Remote close or timeout", status);
  } else {
    iocpdesc_fail(iocpd, status, "IOCP async write error");
  }
}


// === Async reads


static read_result_t *read_result(iocpdesc_t *iocpd)
{
  read_result_t *result = (read_result_t *) calloc(sizeof(read_result_t), 1);
  if (result) {
    result->base.type = IOCP_READ;
    result->base.iocpd = iocpd;
  }
  return result;
}

static void begin_zero_byte_read(iocpdesc_t *iocpd)
{
  if (iocpd->read_in_progress) return;
  if (iocpd->read_closed) {
    pni_events_update(iocpd, iocpd->events | PN_READABLE);
    return;
  }

  read_result_t *result = iocpd->read_result;
  memset(&result->base.overlapped, 0, sizeof (OVERLAPPED));
  DWORD flags = 0;
  WSABUF wsabuf;
  wsabuf.buf = result->unused_buf;
  wsabuf.len = 0;
  int rc = WSARecv(iocpd->socket, &wsabuf, 1, NULL, &flags,
                       &result->base.overlapped, 0);
  if (rc && WSAGetLastError() != ERROR_IO_PENDING) {
    iocpdesc_fail(iocpd, WSAGetLastError(), "IOCP read error");
    return;
  }
  iocpd->ops_in_progress++;
  iocpd->read_in_progress = true;
}

static void drain_until_closed(iocpdesc_t *iocpd) {
  size_t max_drain = 16 * 1024;
  char buf[512];
  read_result_t *result = iocpd->read_result;
  while (result->drain_count < max_drain) {
    int rv = recv(iocpd->socket, buf, 512, 0);
    if (rv > 0)
      result->drain_count += rv;
    else if (rv == 0) {
      iocpd->read_closed = true;
      return;
    } else if (WSAGetLastError() == WSAEWOULDBLOCK) {
      // wait a little longer
      start_reading(iocpd);
      return;
    }
    else
      break;
  }
  // Graceful close indication unlikely, force the issue
  if (iocpd->iocp->iocp_trace)
    if (result->drain_count >= max_drain)
      iocp_log("graceful close on reader abandoned (too many chars)\n");
    else
      iocp_log("graceful close on reader abandoned: %d\n", WSAGetLastError());
  iocpd->read_closed = true;
}


void complete_read(read_result_t *result, DWORD xfer_count, HRESULT status)
{
  iocpdesc_t *iocpd = result->base.iocpd;
  iocpd->read_in_progress = false;

  if (iocpd->closing) {
    // Application no longer reading, but we are looking for a zero length read
    if (!iocpd->read_closed)
      drain_until_closed(iocpd);
    reap_check(iocpd);
    return;
  }

  if (status == 0 && xfer_count == 0) {
    // Success.
    pni_events_update(iocpd, iocpd->events | PN_READABLE);
  } else {
    iocpdesc_fail(iocpd, status, "IOCP read complete error");
  }
}

ssize_t pni_iocp_recv(iocpdesc_t *iocpd, void *buf, size_t size, bool *would_block, pn_error_t *error)
{
  if (size == 0) return 0;
  *would_block = false;
  if (is_listener(iocpd)) {
    set_iocp_error_status(error, PN_ERR, WSAEOPNOTSUPP);
    return SOCKET_ERROR;
  }
  if (iocpd->closing) {
    // Previous call to pn_close()
    set_iocp_error_status(error, PN_ERR, WSAESHUTDOWN);
    return SOCKET_ERROR;
  }
  if (iocpd->read_closed) {
    if (pn_error_code(iocpd->error))
      pn_error_copy(error, iocpd->error);
    else
      set_iocp_error_status(error, PN_ERR, WSAENOTCONN);
    return SOCKET_ERROR;
  }

  int count = recv(iocpd->socket, (char *) buf, size, 0);
  if (count > 0) {
    pni_events_update(iocpd, iocpd->events & ~PN_READABLE);
    // caller must initiate     begin_zero_byte_read(iocpd);
    return (ssize_t) count;
  } else if (count == 0) {
    iocpd->read_closed = true;
    return 0;
  }
  if (WSAGetLastError() == WSAEWOULDBLOCK)
    *would_block = true;
  else {
    set_iocp_error_status(error, PN_ERR, WSAGetLastError());
    iocpd->read_closed = true;
  }
  return SOCKET_ERROR;
}

void start_reading(iocpdesc_t *iocpd)
{
  begin_zero_byte_read(iocpd);
}


// === The iocp descriptor

static void pni_iocpdesc_initialize(void *object)
{
  iocpdesc_t *iocpd = (iocpdesc_t *) object;
  memset(iocpd, 0, sizeof(iocpdesc_t));
  iocpd->socket = INVALID_SOCKET;
}

static void pni_iocpdesc_finalize(void *object)
{
  iocpdesc_t *iocpd = (iocpdesc_t *) object;
  pn_free(iocpd->acceptor);
  pn_error_free(iocpd->error);
   if (iocpd->pipeline)
    if (write_in_progress(iocpd))
      iocp_log("iocp descriptor write leak\n");
    else
      pn_free(iocpd->pipeline);
  if (iocpd->read_in_progress)
    iocp_log("iocp descriptor read leak\n");
  else
    free(iocpd->read_result);
}

static uintptr_t pni_iocpdesc_hashcode(void *object)
{
  iocpdesc_t *iocpd = (iocpdesc_t *) object;
  return iocpd->socket;
}

#define pni_iocpdesc_compare NULL
#define pni_iocpdesc_inspect NULL

// Reference counted in the iocpdesc map, zombie_list, selector.
static iocpdesc_t *pni_iocpdesc(pn_socket_t s)
{
  static const pn_cid_t CID_pni_iocpdesc = CID_pn_void;
  static pn_class_t clazz = PN_CLASS(pni_iocpdesc);
  iocpdesc_t *iocpd = (iocpdesc_t *) pn_class_new(&clazz, sizeof(iocpdesc_t));
  assert(iocpd);
  iocpd->socket = s;
  return iocpd;
}

static bool is_listener_socket(pn_socket_t s)
{
  BOOL tval = false;
  int tvalsz = sizeof(tval);
  int code = getsockopt(s, SOL_SOCKET, SO_ACCEPTCONN, (char *)&tval, &tvalsz);
  return code == 0 && tval;
}

iocpdesc_t *pni_iocpdesc_create(iocp_t *iocp, pn_socket_t s) {
  assert (s != INVALID_SOCKET);
  bool listening = is_listener_socket(s);
  iocpdesc_t *iocpd = pni_iocpdesc(s);
  iocpd->iocp = iocp;
  if (iocpd) {
    iocpd->error = pn_error();
    if (listening) {
      iocpd->acceptor = pni_acceptor(iocpd);
    } else {
      iocpd->pipeline = pni_write_pipeline(iocpd);
      iocpd->read_result = read_result(iocpd);
    }
  }
  return iocpd;
}


static void bind_to_completion_port(iocpdesc_t *iocpd)
{
  if (iocpd->bound) return;
  if (!iocpd->iocp->completion_port) {
    iocpdesc_fail(iocpd, WSAEINVAL, "Incomplete setup, no completion port.");
    return;
  }

  if (CreateIoCompletionPort ((HANDLE) iocpd->socket, iocpd->iocp->completion_port, proactor_io, 0))
    iocpd->bound = true;
  else {
    iocpdesc_fail(iocpd, GetLastError(), "IOCP socket setup.");
  }
}

static void release_sys_sendbuf(SOCKET s)
{
  // Set the socket's send buffer size to zero.
  int sz = 0;
  int status = setsockopt(s, SOL_SOCKET, SO_SNDBUF, (const char *)&sz, sizeof(int));
  assert(status == 0);
}

void pni_iocpdesc_start(iocpdesc_t *iocpd)
{
  if (iocpd->bound) return;
  bind_to_completion_port(iocpd);
  if (is_listener(iocpd)) {
    begin_accept(iocpd->acceptor, NULL);
  }
  else {
    release_sys_sendbuf(iocpd->socket);
    pni_events_update(iocpd, PN_WRITABLE);
    start_reading(iocpd);
  }
}

static void complete(iocp_result_t *result, bool success, DWORD num_transferred) {
  result->iocpd->ops_in_progress--;
  DWORD status = success ? 0 : GetLastError();

  switch (result->type) {
  case IOCP_ACCEPT:
    complete_accept((accept_result_t *) result, status);
    break;
  case IOCP_CONNECT:
    complete_connect((connect_result_t *) result, status);
    break;
  case IOCP_WRITE:
    complete_write((write_result_t *) result, num_transferred, status);
    break;
  case IOCP_READ:
    complete_read((read_result_t *) result, num_transferred, status);
    break;
  default:
    assert(false);
  }
}

void pni_iocp_drain_completions(iocp_t *iocp)
{
  while (true) {
    DWORD timeout_ms = 0;
    DWORD num_transferred = 0;
    ULONG_PTR completion_key = 0;
    OVERLAPPED *overlapped = 0;

    bool good_op = GetQueuedCompletionStatus (iocp->completion_port, &num_transferred,
                                               &completion_key, &overlapped, timeout_ms);
    if (!overlapped)
      return;  // timed out
    iocp_result_t *result = (iocp_result_t *) overlapped;
    if (!completion_key)
      complete(result, good_op, num_transferred);
  }
}

// returns: -1 on error, 0 on timeout, 1 successful completion
// proactor layer uses completion_key, but we don't, so ignore those (shutdown in progress)
int pni_iocp_wait_one(iocp_t *iocp, int timeout, pn_error_t *error) {
  DWORD win_timeout = (timeout < 0) ? INFINITE : (DWORD) timeout;
  DWORD num_transferred = 0;
  ULONG_PTR completion_key = 0;
  OVERLAPPED *overlapped = 0;

  bool good_op = GetQueuedCompletionStatus (iocp->completion_port, &num_transferred,
                                            &completion_key, &overlapped, win_timeout);
  if (!overlapped)
    if (GetLastError() == WAIT_TIMEOUT)
      return 0;
    else {
      if (error)
        pni_win32_error(error, "GetQueuedCompletionStatus", GetLastError());
      return -1;
    }

  if (completion_key)
    return pni_iocp_wait_one(iocp, timeout, error);
  iocp_result_t *result = (iocp_result_t *) overlapped;
  complete(result, good_op, num_transferred);
  return 1;
}

// === Close (graceful and otherwise)

// zombie_list is for sockets transitioning out of iocp on their way to zero ops_in_progress
// and fully closed.

static void zombie_list_add(iocpdesc_t *iocpd)
{
  assert(iocpd->closing);
  if (!iocpd->ops_in_progress) {
    // No need to make a zombie.
    if (iocpd->socket != INVALID_SOCKET) {
      closesocket(iocpd->socket);
      iocpd->socket = INVALID_SOCKET;
      iocpd->read_closed = true;
    }
    return;
  }
  // Allow 2 seconds for graceful shutdown before releasing socket resource.
  iocpd->reap_time = pn_i_now2() + 2000;
  pn_list_add(iocpd->iocp->zombie_list, iocpd);
}

static void reap_check(iocpdesc_t *iocpd)
{
  if (iocpd->closing && !iocpd->ops_in_progress) {
    if (iocpd->socket != INVALID_SOCKET) {
      closesocket(iocpd->socket);
      iocpd->socket = INVALID_SOCKET;
    }
    pn_list_remove(iocpd->iocp->zombie_list, iocpd);
    // iocpd is decref'ed and possibly released
  }
}

void pni_iocp_reap_check(iocpdesc_t *iocpd) {
  reap_check(iocpd);
}

pn_timestamp_t pni_zombie_deadline(iocp_t *iocp)
{
  if (pn_list_size(iocp->zombie_list)) {
    iocpdesc_t *iocpd = (iocpdesc_t *) pn_list_get(iocp->zombie_list, 0);
    return iocpd->reap_time;
  }
  return 0;
}

void pni_zombie_check(iocp_t *iocp, pn_timestamp_t now)
{
  pn_list_t *zl = iocp->zombie_list;
  // Look for stale zombies that should have been reaped by "now"
  for (size_t idx = 0; idx < pn_list_size(zl); idx++) {
    iocpdesc_t *iocpd = (iocpdesc_t *) pn_list_get(zl, idx);
    if (iocpd->reap_time > now)
      return;
    if (iocpd->socket == INVALID_SOCKET)
      continue;
    if (iocp->iocp_trace)
      iocp_log("async close: graceful close timeout exceeded\n");
    closesocket(iocpd->socket);
    iocpd->socket = INVALID_SOCKET;
    iocpd->read_closed = true;
    // outstanding ops should complete immediately now
  }
}

static void drain_zombie_completions(iocp_t *iocp)
{
  // No more pn_selector_select() from App, but zombies still need care and feeding
  // until their outstanding async actions complete.
  pni_iocp_drain_completions(iocp);

  // Discard any that have no pending async IO
  size_t sz = pn_list_size(iocp->zombie_list);
  for (size_t idx = 0; idx < sz;) {
    iocpdesc_t *iocpd = (iocpdesc_t *) pn_list_get(iocp->zombie_list, idx);
    if (!iocpd->ops_in_progress) {
      pn_list_del(iocp->zombie_list, idx, 1);
      sz--;
    } else {
      idx++;
    }
  }

  unsigned shutdown_grace = 2000;
  char *override = getenv("PN_SHUTDOWN_GRACE");
  if (override) {
    int grace = atoi(override);
    if (grace > 0 && grace < 60000)
      shutdown_grace = (unsigned) grace;
  }
  pn_timestamp_t now = pn_i_now2();
  pn_timestamp_t deadline = now + shutdown_grace;

  while (pn_list_size(iocp->zombie_list)) {
    if (now >= deadline)
      break;
    int rv = pni_iocp_wait_one(iocp, deadline - now, NULL);
    if (rv < 0) {
      iocp_log("unexpected IOCP failure on Proton IO shutdown %d\n", GetLastError());
      break;
    }
    now = pn_i_now2();
  }
  if (now >= deadline && pn_list_size(iocp->zombie_list) && iocp->iocp_trace)
    // Should only happen if really slow TCP handshakes, i.e. total network failure
    iocp_log("network failure on Proton shutdown\n");
}

static void zombie_list_hard_close_all(iocp_t *iocp)
{
  pni_iocp_drain_completions(iocp);
  size_t zs = pn_list_size(iocp->zombie_list);
  for (size_t i = 0; i < zs; i++) {
    iocpdesc_t *iocpd = (iocpdesc_t *) pn_list_get(iocp->zombie_list, i);
    if (iocpd->socket != INVALID_SOCKET) {
      closesocket(iocpd->socket);
      iocpd->socket = INVALID_SOCKET;
      iocpd->read_closed = true;
      iocpd->write_closed = true;
    }
  }
  pni_iocp_drain_completions(iocp);

  // Zombies should be all gone.  Do a sanity check.
  zs = pn_list_size(iocp->zombie_list);
  int remaining = 0;
  int ops = 0;
  for (size_t i = 0; i < zs; i++) {
    iocpdesc_t *iocpd = (iocpdesc_t *) pn_list_get(iocp->zombie_list, i);
    remaining++;
    ops += iocpd->ops_in_progress;
  }
  if (remaining)
    iocp_log("Proton: %d unfinished close operations (ops count = %d)\n", remaining, ops);
}

static void iocp_shutdown(iocpdesc_t *iocpd)
{
  if (iocpd->socket == INVALID_SOCKET)
    return;    // Hard close in progress
  if (shutdown(iocpd->socket, SD_SEND)) {
    int err = WSAGetLastError();
    if (err != WSAECONNABORTED && err != WSAECONNRESET && err != WSAENOTCONN)
      if (iocpd->iocp->iocp_trace)
        iocp_log("socket shutdown failed %d\n", err);
  }
  iocpd->write_closed = true;
}

void pni_iocp_begin_close(iocpdesc_t *iocpd)
{
  assert (!iocpd->closing);
  if (is_listener(iocpd)) {
    // Listening socket is easy.  Close the socket which will cancel async ops.
    pn_socket_t old_sock = iocpd->socket;
    iocpd->socket = INVALID_SOCKET;
    iocpd->closing = true;
    iocpd->read_closed = true;
    iocpd->write_closed = true;
    closesocket(old_sock);
    // Pending accepts will now complete.  Zombie can die when all consumed.
    zombie_list_add(iocpd);
  } else {
    // Continue async operation looking for graceful close confirmation or timeout.
    pn_socket_t old_sock = iocpd->socket;
    iocpd->closing = true;
    if (!iocpd->write_closed && !write_in_progress(iocpd))
      iocp_shutdown(iocpd);
    zombie_list_add(iocpd);
  }
}


// === iocp_t

#define pni_iocp_hashcode NULL
#define pni_iocp_compare NULL
#define pni_iocp_inspect NULL

void pni_iocp_initialize(void *obj)
{
  iocp_t *iocp = (iocp_t *) obj;
  memset(iocp, 0, sizeof(iocp_t));
  pni_shared_pool_create(iocp);
  iocp->completion_port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
  assert(iocp->completion_port != NULL);
  iocp->zombie_list = pn_list(PN_OBJECT, 0);
  iocp->iocp_trace = false;
}

void pni_iocp_finalize(void *obj)
{
  iocp_t *iocp = (iocp_t *) obj;
  // Move sockets to closed state
  drain_zombie_completions(iocp);    // Last chance for graceful close
  zombie_list_hard_close_all(iocp);
  CloseHandle(iocp->completion_port);  // This cancels all our async ops
  iocp->completion_port = NULL;
  // Now safe to free everything that might be touched by a former async operation.
  pn_free(iocp->zombie_list);
  pni_shared_pool_free(iocp);
}

iocp_t *pni_iocp()
{
  static const pn_cid_t CID_pni_iocp = CID_pn_void;
  static const pn_class_t clazz = PN_CLASS(pni_iocp);
  iocp_t *iocp = (iocp_t *) pn_class_new(&clazz, sizeof(iocp_t));
  return iocp;
}

}


// ======================================================================
// Proton Proactor support
// ======================================================================

#include "../core/log_private.h"
#include "./proactor-internal.h"

class csguard {
  public:
    csguard(CRITICAL_SECTION *cs) : cs_(cs), set_(true) { EnterCriticalSection(cs_); }
    ~csguard() { if (set_) LeaveCriticalSection(cs_); }
    void release() {
        if (set_) {
            set_ = false;
            LeaveCriticalSection(cs_);
        }
    }
  private:
    LPCRITICAL_SECTION cs_;
    bool set_;
};


// Get string from error status
std::string errno_str2(DWORD status) {
  char buf[512];
  if (FormatMessage(FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_FROM_SYSTEM,
                    0, status, 0, buf, sizeof(buf), 0))
      return std::string(buf);
  return std::string("internal proactor error");
}


std::string errno_str(const std::string& msg, bool is_wsa) {
  DWORD e = is_wsa ? WSAGetLastError() : GetLastError();
  return msg + ": " + errno_str2(e);
}



using namespace pn_experimental;

static 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) ? WSAGetLastError() : 0;
}

const char *COND_NAME = "proactor";
PN_HANDLE(PN_PROACTOR)

// The number of times a connection event batch may be replenished for
// a thread between calls to wait().
// TODO: consider some instrumentation to determine an optimal number
// or switch to cpu time based limit.
#define HOG_MAX 3

/* 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, CID_pn_proactor)
PN_STRUCT_CLASSDEF(pn_listener, CID_pn_listener)

/* Completion serialization context common to connection and listener. */
/* And also the reaper singleton (which has no socket */

typedef enum {
  PROACTOR,
  PCONNECTION,
  LISTENER,
  WAKEABLE } pcontext_type_t;

typedef struct pcontext_t {
  CRITICAL_SECTION cslock;
  pn_proactor_t *proactor;  /* Immutable */
  void *owner;              /* Instance governed by the context */
  pcontext_type_t type;
  bool working;
  bool wake_pending;
  int completion_ops;  // uncompleted ops that are not socket IO related
  struct pcontext_t *wake_next; // wake list, guarded by proactor eventfd_mutex
  bool closing;
  // Next 4 are protected by the proactor mutex
  struct pcontext_t* next;  /* Protected by proactor.mutex */
  struct pcontext_t* prev;  /* Protected by proactor.mutex */
  int disconnect_ops;           /* ops remaining before disconnect complete */
  bool disconnecting;           /* pn_proactor_disconnect */
} pcontext_t;

static void pcontext_init(pcontext_t *ctx, pcontext_type_t t, pn_proactor_t *p, void *o) {
  memset(ctx, 0, sizeof(*ctx));
  InitializeCriticalSectionAndSpinCount(&ctx->cslock, 4000);
  ctx->proactor = p;
  ctx->owner = o;
  ctx->type = t;
}

static void pcontext_finalize(pcontext_t* ctx) {
  DeleteCriticalSection(&ctx->cslock);
}

typedef struct psocket_t {
  iocpdesc_t *iocpd;            /* NULL if reaper, or socket open failure. */
  pn_listener_t *listener;      /* NULL for a connection socket */
  pn_netaddr_t listen_addr;     /* Not filled in for connection sockets */
  char addr_buf[PN_MAX_ADDR];
  const char *host, *port;
  bool is_reaper;
} psocket_t;

static void psocket_init(psocket_t* ps, pn_listener_t *listener, bool is_reaper, const char *addr) {
  ps->is_reaper = is_reaper;
  if (is_reaper) return;
  ps->listener = listener;
  pni_parse_addr(addr, ps->addr_buf, sizeof(ps->addr_buf), &ps->host, &ps->port);
}

struct pn_proactor_t {
  pcontext_t context;
  CRITICAL_SECTION write_lock;
  CRITICAL_SECTION timer_lock;
  CRITICAL_SECTION bind_lock;
  HANDLE timer_queue;
  HANDLE timeout_timer;
  iocp_t *iocp;
  class reaper *reaper;
  pn_collector_t *collector;
  pcontext_t *contexts;         /* in-use contexts for PN_PROACTOR_INACTIVE and cleanup */
  pn_event_batch_t batch;
  size_t disconnects_pending;   /* unfinished proactor disconnects*/

  // need_xxx flags indicate we should generate PN_PROACTOR_XXX on the next update_batch()
  bool need_interrupt;
  bool need_inactive;
  bool need_timeout;
  bool timeout_set; /* timeout has been set by user and not yet cancelled or generated event */
  bool timeout_processed;  /* timout event dispatched in the most recent event batch */
  bool delayed_interrupt;
  bool shutting_down;
};

typedef struct pconnection_t {
  psocket_t psocket;
  pcontext_t context;
  pn_connection_driver_t driver;
  std::queue<iocp_result_t *> *completion_queue;
  std::queue<iocp_result_t *> *work_queue;
  pn_condition_t *disconnect_condition;
  pn_event_batch_t batch;
  int wake_count;
  int hog_count; // thread hogging limiter
  bool server;                /* accept, not connect */
  bool started;
  bool connecting;
  bool tick_pending;
  bool queued_disconnect;     /* deferred from pn_proactor_disconnect() */
  bool bound;
  bool stop_timer_required;
  bool can_wake;
  HANDLE tick_timer;
  struct pn_netaddr_t local, remote; /* Actual addresses */
  struct addrinfo *addrinfo;         /* Resolved address list */
  struct addrinfo *ai;               /* Current connect address */
} pconnection_t;

struct pn_listener_t {
  psocket_t *psockets;          /* Array of listening sockets */
  size_t psockets_size;
  pcontext_t context;
  std::queue<accept_result_t *> *pending_accepts;  // sockets awaiting a pn_listener_accept
  int pending_events;          // number of PN_LISTENER_ACCEPT events to be delivered
  pn_condition_t *condition;
  pn_collector_t *collector;
  pn_event_batch_t batch;
  pn_record_t *attachments;
  void *listener_context;
  size_t backlog;
  bool close_dispatched;
};


static bool proactor_remove(pcontext_t *ctx);
static void listener_done(pn_listener_t *l);
static bool proactor_update_batch(pn_proactor_t *p);
static pn_event_batch_t *listener_process(pn_listener_t *l, iocp_result_t *result);
static void recycle_result(accept_result_t *accept_result);
static void proactor_add(pcontext_t *p);
static void release_pending_accepts(pn_listener_t *l);
static void proactor_shutdown(pn_proactor_t *p);

static void post_completion(iocp_t *iocp, ULONG_PTR arg1, void *arg2) {
    // Despite the vagueness of the official documentation, the
    // following args to Post are passed undisturbed and unvalidated
    // to GetQueuedCompletionStatus().  In particular, arg2 does not
    // have to be an OVERLAPPED struct and may be NULL.

    DWORD nxfer = 0;            // We could pass this through too if needed.
    PostQueuedCompletionStatus(iocp->completion_port, nxfer, arg1, (LPOVERLAPPED) arg2);
}

static inline bool is_write_result(iocp_result_t *r) {
    return r && r->type == IOCP_WRITE;
}

static inline bool is_read_result(iocp_result_t *r) {
    return r && r->type == IOCP_READ;
}

static inline bool is_connect_result(iocp_result_t *r) {
    return r && r->type == IOCP_CONNECT;
}

// From old io.c
static void pni_configure_sock_2(pn_socket_t sock) {
  //
  // Disable the Nagle algorithm on TCP connections.
  //

  int flag = 1;
  if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)) != 0) {
      //TODO: log/err
  }

  u_long nonblock = 1;
  if (ioctlsocket(sock, FIONBIO, &nonblock)) {
      // TODO: log/err
  }
}

static LPFN_CONNECTEX lookup_connect_ex2(SOCKET s)
{
  GUID guid = WSAID_CONNECTEX;
  DWORD bytes = 0;
  LPFN_CONNECTEX fn;
  WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
           &fn, sizeof(fn), &bytes, NULL, NULL);
  assert(fn);
  return fn;
}


// File descriptor wrapper that calls ::close in destructor.
class unique_socket {
  public:
    unique_socket(pn_socket_t socket) : socket_(socket) {}
    ~unique_socket() { if (socket_ != INVALID_SOCKET) ::closesocket(socket_); }
    operator pn_socket_t() const { return socket_; }
    pn_socket_t release() { pn_socket_t ret = socket_; socket_ = INVALID_SOCKET; return ret; }

  protected:
    pn_socket_t socket_;
};

void do_complete(iocp_result_t *result) {
  iocpdesc_t *iocpd = result->iocpd;  // connect result gets deleted
  switch (result->type) {

  case IOCP_ACCEPT:
    /* accept is now processed inline to do in parallel, except on teardown */
    assert(iocpd->closing);
    complete_accept((accept_result_t *) result, result->status);  // free's result and retires new_sock
    break;
  case IOCP_CONNECT:
    complete_connect((connect_result_t *) result, result->status);
    break;
  case IOCP_WRITE:
    complete_write((write_result_t *) result, result->num_transferred, result->status);
    break;
  case IOCP_READ:
    complete_read((read_result_t *) result, result->num_transferred, result->status);
    break;
  default:
    assert(false);
  }

  iocpd->ops_in_progress--;  // Set in each begin_xxx call
}


static inline pconnection_t *as_pconnection_t(psocket_t* ps) {
  return !(ps->is_reaper || ps->listener) ? (pconnection_t*)ps : NULL;
}

static inline pn_listener_t *as_listener(psocket_t* ps) {
  return ps->listener;
}

static inline pconnection_t *pcontext_pconnection(pcontext_t *c) {
  return c->type == PCONNECTION ?
    (pconnection_t*)((char*)c - offsetof(pconnection_t, context)) : NULL;
}
static inline pn_listener_t *pcontext_listener(pcontext_t *c) {
  return c->type == LISTENER ?
    (pn_listener_t*)((char*)c - offsetof(pn_listener_t, context)) : NULL;
}

static pcontext_t *psocket_context(psocket_t *ps) {
  if (ps->listener)
    return &ps->listener->context;
  pconnection_t *pc = as_pconnection_t(ps);
  return &pc->context;
}


// Call wih lock held
static inline void wake_complete(pcontext_t *ctx) {
  ctx->wake_pending = false;
  ctx->completion_ops--;
}

// Call wih lock held
static void wakeup(psocket_t *ps) {
  pcontext_t *ctx = psocket_context(ps);
  if (!ctx->working && !ctx->wake_pending) {
    ctx->wake_pending = true;
    ctx->completion_ops++;
    post_completion(ctx->proactor->iocp, psocket_wakeup_key, ps);
  }
}

// Call wih lock held
static inline void proactor_wake_complete(pn_proactor_t *p) {
  wake_complete(&p->context);
}

// Call wih lock held
static void proactor_wake(pn_proactor_t *p) {
  if (!p->context.working && !p->context.wake_pending) {
    p->context.wake_pending = true;
    p->context.completion_ops++;
    post_completion(p->iocp, proactor_wake_key, p);
  }
}

VOID CALLBACK reap_check_cb(PVOID arg, BOOLEAN /* ignored*/ );

// Serialize handling listener and connection closing IO completions
// after the engine has lost interest.  A temporary convenience while
// using the old single threaded iocp/io/select driver code.
class reaper {
  public:
    reaper(pn_proactor_t *p, CRITICAL_SECTION *wlock, iocp_t *iocp)
      : iocp_(iocp), global_wlock_(wlock), timer_(NULL), running(true) {
      InitializeCriticalSectionAndSpinCount(&lock_, 4000);
      timer_queue_ = CreateTimerQueue();
      if (!timer_queue_) {
          perror("CreateTimerQueue");
          abort();
      }
    }

  // Connection or listener lock must also be held by caller.
    bool add(iocpdesc_t *iocpd) {
        if (!iocpd) return false;
        csguard g(&lock_);
        if (iocpd->closing) return false;
        bool rval = !iocpd->ops_in_progress;
        pni_iocp_begin_close(iocpd); // sets iocpd->closing
        pn_decref(iocpd);            // may still be ref counted on zombie list
        reap_timer();
        return rval;
    }

    // For cases where the close will be immediate.  I.E. after a failed
    // connection attempt where there is no follow-on IO.
    void fast_reap(iocpdesc_t *iocpd) {
        assert(iocpd && iocpd->ops_in_progress == 0 && !iocpd->closing);
        csguard g(&lock_);
        pni_iocp_begin_close(iocpd);
        pn_decref(iocpd);
    }

    bool process(iocp_result_t *result) {
        // No queue of completions for the reaper.  Just process
        // serialized by the lock assuming all actions are "short".
        // That may be wrong, and if so the real fix is not a
        // consumer/producer setup but just replace the reaper with a
        // multi threaded alternative.
        csguard g(&lock_);
        iocpdesc_t *iocpd = result->iocpd;
        if (is_write_result(result)) {
            csguard wg(global_wlock_);
            do_complete(result);
        }
        else do_complete(result);
        // result may now be NULL
        bool rval = (iocpd->ops_in_progress == 0);
        pni_iocp_reap_check(iocpd);
        return rval;
    }

    // Called when all competing threads have terminated except our own reap_check timer.
    void final_shutdown() {
        running = false;
        DeleteTimerQueueEx(timer_queue_, INVALID_HANDLE_VALUE);
        // No pending or active timers from thread pool remain.  Truly single threaded now.
        pn_free((void *) iocp_); // calls pni_iocp_finalize(); cleans up all sockets, completions, completion port.
        DeleteCriticalSection(&lock_);
    }

    void reap_check() {
        csguard g(&lock_);
        DeleteTimerQueueTimer(timer_queue_, timer_, NULL);
        timer_ = NULL;
        reap_timer();
    }


  private:
    void reap_timer() {
        // Call with lock
        if (timer_ || !running)
            return;
        pn_timestamp_t now = pn_i_now2();
        pni_zombie_check(iocp_, now);
        pn_timestamp_t zd = pni_zombie_deadline(iocp_);
        if (zd) {
            DWORD tm = (zd > now) ? zd - now : 1;
            if (!CreateTimerQueueTimer(&timer_, timer_queue_, reap_check_cb, this, tm,
                                       0, WT_EXECUTEONLYONCE)) {
                perror("CreateTimerQueueTimer");
                abort();
            }
        }
    }

    iocp_t *iocp_;
    CRITICAL_SECTION lock_;
    CRITICAL_SECTION *global_wlock_;
    HANDLE timer_queue_;
    HANDLE timer_;
    bool running;
};

VOID CALLBACK reap_check_cb(PVOID arg, BOOLEAN /* ignored*/ ) {
    // queue timer callback
    reaper *r = static_cast<reaper *>(arg);
    r->reap_check();
}

static void listener_begin_close(pn_listener_t* l);
static void connect_step_done(pconnection_t *pc, connect_result_t *result);
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) ?
    (pn_proactor_t*)((char*)batch - offsetof(pn_proactor_t, batch)) : NULL;
}

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

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

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

static inline bool listener_has_event(pn_listener_t *l) {
  return pn_collector_peek(l->collector);
}

static inline bool proactor_has_event(pn_proactor_t *p) {
  return pn_collector_peek(p->collector);
}

static pn_event_t *log_event(void* p, pn_event_t *e) {
  if (e) {
    pn_logf("[%p]:(%s)", (void*)p, pn_event_type_name(pn_event_type(e)));
  }
  return e;
}

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

static void psocket_error(psocket_t *ps, int err, const char* what) {
  psocket_error_str(ps, errno_str2(err).c_str(), what);
}



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

/* Make a pn_class for pconnection_t since it is attached to a pn_connection_t record */
#define CID_pconnection CID_pn_object
#define pconnection_inspect NULL
#define pconnection_initialize NULL
#define pconnection_hashcode NULL
#define pconnection_compare NULL

static void pconnection_finalize(void *vp_pconnection) {
  pconnection_t *pc = (pconnection_t*)vp_pconnection;
  pcontext_finalize(&pc->context);
}

static const pn_class_t pconnection_class = PN_CLASS(pconnection);

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)
{
  if (pn_connection_driver_init(&pc->driver, c, t) != 0) {
    free(pc);
    return "pn_connection_driver_init failure";
  }
  {
    csguard g(&p->bind_lock);
    pn_record_t *r = pn_connection_attachments(pc->driver.connection);
    if (pn_record_get(r, PN_PROACTOR)) {
      pn_connection_driver_destroy(&pc->driver);
      free(pc);
      return "pn_connection_t already in use";
    }
    pn_record_def(r, PN_PROACTOR, &pconnection_class);
    pn_record_set(r, PN_PROACTOR, pc);
    pc->bound = true;
    pc->can_wake = true;
  }

  pc->completion_queue = new std::queue<iocp_result_t *>();
  pc->work_queue = new std::queue<iocp_result_t *>();
  pcontext_init(&pc->context, PCONNECTION, p, pc);

  psocket_init(&pc->psocket, NULL, false, addr);
  pc->batch.next_event = pconnection_batch_next;
  if (server) {
    pn_transport_set_server(pc->driver.transport);
  }

  pn_decref(pc);                /* Will be deleted when the connection is */
  return NULL;
}

// Either stops a timer before firing or returns after the callback has
// completed (in the threadpool thread).  Never "in doubt".
static bool stop_timer(HANDLE tqueue, HANDLE *timer) {
  if (!*timer) return true;
  if (DeleteTimerQueueTimer(tqueue, *timer, INVALID_HANDLE_VALUE)) {
    *timer = NULL;
    return true;
  }
  return false;  // error
}

static bool start_timer(HANDLE tqueue, HANDLE *timer, WAITORTIMERCALLBACK cb, void *cb_arg, DWORD time) {
  if (*timer) {
    // TODO: log err
    return false;
  }
  return CreateTimerQueueTimer(timer, tqueue, cb, cb_arg, time, 0, WT_EXECUTEONLYONCE);
}

VOID CALLBACK tick_timer_cb(PVOID arg, BOOLEAN /* ignored*/ ) {
  pconnection_t *pc = (pconnection_t *) arg;
  csguard g(&pc->context.cslock);
  if (pc->psocket.iocpd && !pc->psocket.iocpd->closing) {
    pc->tick_pending = true;
    wakeup(&pc->psocket);
  }
}


// Call with no lock held or stop_timer and callback may deadlock
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)) {
    if(!stop_timer(pc->context.proactor->timer_queue, &pc->tick_timer)) {
      // TODO: handle error
    }
    uint64_t now = pn_i_now2();
    uint64_t next = pn_transport_tick(t, now);
    if (next) {
      if (!start_timer(pc->context.proactor->timer_queue, &pc->tick_timer, tick_timer_cb, pc, next - now)) {
        // TODO: handle error
      }
    }
  }
}

static pconnection_t *get_pconnection(pn_connection_t* c) {
  if (!c) return NULL;
  pn_record_t *r = pn_connection_attachments(c);
  return (pconnection_t*) pn_record_get(r, PN_PROACTOR);
}


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

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

// Call after successful accept
static void set_sock_names(pconnection_t *pc) {
  // This works.  Note possible use of GetAcceptExSockaddrs()
  pn_socket_t sock = pc->psocket.iocpd->socket;
  socklen_t len = sizeof(pc->local.ss);
  getsockname(sock, (struct sockaddr*)&pc->local.ss, &len);
  len = sizeof(pc->remote.ss);
  getpeername(sock, (struct sockaddr*)&pc->remote.ss, &len);
}


// Call with lock held when closing and transitioning away from working context
static inline bool pconnection_can_free(pconnection_t *pc) {
  return pc->psocket.iocpd == NULL && pc->context.completion_ops == 0
    && !pc->stop_timer_required && !pconnection_has_event(pc) && !pc->queued_disconnect;
}

static void pconnection_final_free(pconnection_t *pc) {
  if (pc->addrinfo) {
    freeaddrinfo(pc->addrinfo);
  }
  pn_condition_free(pc->disconnect_condition);
  pn_incref(pc);                /* Make sure we don't do a circular free */
  pn_connection_driver_destroy(&pc->driver);
  pn_decref(pc);
  /* Now pc is freed iff the connection is, otherwise remains till the pn_connection_t is freed. */
}

// Call with lock held or from forced shutdown
static void pconnection_begin_close(pconnection_t *pc) {
  if (!pc->context.closing) {
    pc->context.closing = true;
    pn_connection_driver_close(&pc->driver);
    pc->stop_timer_required = true;
    if (pc->context.proactor->reaper->add(pc->psocket.iocpd))
      pc->psocket.iocpd = NULL;
    wakeup(&pc->psocket);
  }
}

// call with lock held.  return true if caller must call pconnection_final_free()
static bool pconnection_cleanup(pconnection_t *pc) {
  delete pc->completion_queue;
  delete pc->work_queue;
  return proactor_remove(&pc->context);
}

static inline bool pconnection_work_pending(pconnection_t *pc) {
  if (pc->completion_queue->size() || pc->wake_count || pc->tick_pending || pc->queued_disconnect)
    return true;
  if (!pc->started)
    return false;
  pn_bytes_t wbuf = pn_connection_driver_write_buffer(&pc->driver);
  return (wbuf.size > 0 && (pc->psocket.iocpd->events & PN_WRITABLE));
}

// Return true unless reaped
static bool pconnection_write(pconnection_t *pc, pn_bytes_t wbuf) {
  ssize_t n;
  bool wouldblock;
  {
    csguard g(&pc->context.proactor->write_lock);
    n = pni_iocp_begin_write(pc->psocket.iocpd, wbuf.start, wbuf.size, &wouldblock, pc->psocket.iocpd->error);
  }
  if (n > 0) {
    pn_connection_driver_write_done(&pc->driver, n);
  } else if (n < 0 && !wouldblock) {
    psocket_error(&pc->psocket, WSAGetLastError(), "on write to");
  }
  else if (wbuf.size == 0 && pn_connection_driver_write_closed(&pc->driver)) {
    if (pc->context.proactor->reaper->add(pc->psocket.iocpd)) {
      pc->psocket.iocpd = NULL;
      return false;
    }
  }
  return true;
}

// Queue the result (if any) for the one worker thread.  Become the worker if possible.
// NULL result is a wakeup or a topup.
// topup signifies that the working thread is the caller looking for additional events.
static pn_event_batch_t *pconnection_process(pconnection_t *pc, iocp_result_t *result, bool topup) {
  bool first = true;
  bool waking = false;
  bool tick_required = false;
  bool open = false;

  while (true) {
    {
      csguard g(&pc->context.cslock);
      if (first) {
        first = false;
        if (result)
          pc->completion_queue->push(result);
        else if (!topup)
          wake_complete(&pc->context);
        if (!topup) {
          if (pc->context.working)
            return NULL;
          pc->context.working = true;
        }
        open = pc->started && !pc->connecting && !pc->context.closing;
      }
      else {
        // Just re-acquired lock after processing IO and engine work
        if (pconnection_has_event(pc))
          return &pc->batch;

        if (!pconnection_work_pending(pc)) {
          pc->context.working = false;
          if (pn_connection_driver_finished(&pc->driver)) {
            pconnection_begin_close(pc);
          }
          if (pc->context.closing && pconnection_can_free(pc)) {
            if (pconnection_cleanup(pc)) {
              g.release();
              pconnection_final_free(pc);
              return NULL;
            } // else disconnect logic has the free obligation
          }
          return NULL;
        }
      }

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

      assert(pc->work_queue->empty());
      if (pc->completion_queue->size())
          std::swap(pc->work_queue, pc->completion_queue);

      if (pc->wake_count) {
        waking = open && pc->can_wake && !pn_connection_driver_finished(&pc->driver);
        pc->wake_count = 0;
      }
      if (pc->tick_pending) {
        pc->tick_pending = false;
        if (open)
          tick_required = true;
      }
    }

    // No lock

    // Drain completions prior to engine work
    while (pc->work_queue->size()) {
      result = (iocp_result_t *) pc->work_queue->front();
      pc->work_queue->pop();
      if (result->iocpd->closing) {
        if (pc->context.proactor->reaper->process(result)) {
          pc->psocket.iocpd = NULL;  // reaped
          open = false;
        }
      }
      else {
        if (is_write_result(result)) {
          csguard g(&pc->context.proactor->write_lock);
          do_complete(result);
        }
        else if (is_connect_result(result)) {
          connect_step_done(pc, (connect_result_t *) result);
          if (pc->psocket.iocpd && (pc->psocket.iocpd->events & PN_WRITABLE)) {
            pc->connecting = false;
            if (pc->started)
              open = true;
          }
        }
        else do_complete(result);
      }
    }

    if (!open) {
      if (pc->stop_timer_required) {
        pc->stop_timer_required = false;
        // Do without context lock to avoid possible deadlock
        stop_timer(pc->context.proactor->timer_queue, &pc->tick_timer);
      }
    } else {

      pn_rwbytes_t rbuf = pn_connection_driver_read_buffer(&pc->driver);
      pn_bytes_t wbuf = pn_connection_driver_write_buffer(&pc->driver);

      /* Ticks and checking buffers can generate events, process before proceeding */
      bool ready = pconnection_has_event(pc);
      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 (ready) {
        continue;
      }

      if (wbuf.size >= 16384 && (pc->psocket.iocpd->events & PN_WRITABLE)) {
        if (!pconnection_write(pc, wbuf))
          continue;
      }
      if (rbuf.size > 0 && !pc->psocket.iocpd->read_in_progress) {
        bool wouldblock;
        ssize_t n = pni_iocp_recv(pc->psocket.iocpd, rbuf.start, rbuf.size, &wouldblock, pc->psocket.iocpd->error);

        if (n > 0) {
          pn_connection_driver_read_done(&pc->driver, n);
          pconnection_tick(pc);         /* check for tick changes. */
          tick_required = false;
        }
        else if (n == 0)
          pn_connection_driver_read_close(&pc->driver);
        else if (!wouldblock)
          psocket_error(&pc->psocket, WSAGetLastError(), "on read from");
      }
      if (!pc->psocket.iocpd->read_in_progress)
        start_reading(pc->psocket.iocpd);


      if (tick_required) {
        pconnection_tick(pc);         /* check for tick changes. */
        tick_required = false;
      }
      wbuf = pn_connection_driver_write_buffer(&pc->driver);
      if (wbuf.size > 0 && (pc->psocket.iocpd->events & PN_WRITABLE)) {
        if (!pconnection_write(pc, wbuf))
          open = false;
      }
    }

    if (topup)
      return NULL;  // regardless if new events made available
  }
}


static pn_event_t *pconnection_batch_next(pn_event_batch_t *batch) {
  pconnection_t *pc = batch_pconnection(batch);
  pn_event_t *e = pn_connection_driver_next_event(&pc->driver);
  if (!e && ++pc->hog_count < HOG_MAX) {
    pconnection_process(pc, NULL, true);  // top up
    e = pn_connection_driver_next_event(&pc->driver);
  }
  if (e && !pc->started && pn_event_type(e) == PN_CONNECTION_BOUND)
    pc->started = true; // SSL will be set up on return and safe to do IO with correct transport layers
  return e;
}

static void pconnection_done(pconnection_t *pc) {
  {
    csguard g(&pc->context.cslock);
    pc->context.working = false;
    pc->hog_count = 0;
    if (pconnection_has_event(pc) || pconnection_work_pending(pc)) {
      wakeup(&pc->psocket);
    } else if (pn_connection_driver_finished(&pc->driver)) {
      pconnection_begin_close(pc);
      wakeup(&pc->psocket);
    }
  }
}

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

// Call whenever transitioning from "definitely active" to "maybe inactive"
static void wake_if_inactive(pn_proactor_t *p) {
  if (is_inactive(p)) {
    p->need_inactive = true;
    proactor_wake(p);
  }
}

void pn_proactor_done(pn_proactor_t *p, pn_event_batch_t *batch) {
  pconnection_t *pc = batch_pconnection(batch);
  if (pc) {
    pconnection_done(pc);
    return;
  }
  pn_listener_t *l = batch_listener(batch);
  if (l) {
    listener_done(l);
    return;
  }
  pn_proactor_t *bp = batch_proactor(batch);
  if (bp == p) {
    csguard g(&p->context.cslock);
    p->context.working = false;
    if (p->delayed_interrupt) {
      p->delayed_interrupt = false;
      p->need_interrupt = true;
    }
    if (p->timeout_processed) {
      p->timeout_processed = false;
      wake_if_inactive(p);
    }
    if (proactor_update_batch(p))
      proactor_wake(p);
    return;
  }
}

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

static pn_event_batch_t *proactor_process(pn_proactor_t *p) {
  csguard g(&p->context.cslock);
  proactor_wake_complete(p);
  if (!p->context.working) {       /* Can generate proactor events */
    if (proactor_update_batch(p)) {
      p->context.working = true;
      return &p->batch;
    }
  }
  return NULL;
}

static pn_event_batch_t *psocket_process(psocket_t *ps, iocp_result_t *result, reaper *rpr) {
  if (ps) {
    pconnection_t *pc = as_pconnection_t(ps);
    if (pc) {
      return pconnection_process(pc, result, false);
    } else {
      pn_listener_t *l = as_listener(ps);
      if (l)
        return listener_process(l, result);
    }
  }
  rpr->process(result);
  return NULL;
}

static pn_event_batch_t *proactor_completion_loop(struct pn_proactor_t* p, bool can_block) {
  // Proact! Process inbound completions of async activity until one
  // of them provides a batch of events.
  while(true) {
    pn_event_batch_t *batch = NULL;

    DWORD win_timeout = can_block ? INFINITE : 0;
    DWORD num_xfer = 0;
    ULONG_PTR completion_key = 0;
    OVERLAPPED *overlapped = 0;

    bool good_op = GetQueuedCompletionStatus (p->iocp->completion_port, &num_xfer,
                                              &completion_key, &overlapped, win_timeout);
    if (!overlapped && !can_block && GetLastError() == WAIT_TIMEOUT)
      return NULL;  // valid timeout

    if (!good_op && !overlapped) {
      // Should never happen.  shutdown?
      // We aren't expecting a timeout, closed completion port, or other error here.
      pn_logf("%s", errno_str("Windows Proton proactor internal failure\n", false).c_str());
      abort();
    }

    switch (completion_key) {
    case proactor_io: {
      // Normal IO case for connections and listeners
      iocp_result_t *result = (iocp_result_t *) overlapped;
      result->status = good_op ? 0 : GetLastError();
      result->num_transferred = num_xfer;
      psocket_t *ps = (psocket_t *) result->iocpd->active_completer;
      batch = psocket_process(ps, result, p->reaper);
      break;
    }
      // completion_key on our completion port is always null unless set by us
      // in PostQueuedCompletionStatus.  In which case, we hijack the overlapped
      // data structure for our own use.
    case psocket_wakeup_key:
        batch = psocket_process((psocket_t *) overlapped, NULL, p->reaper);
        break;
    case proactor_wake_key:
        batch = proactor_process((pn_proactor_t *) overlapped);
        break;
    case recycle_accept_key:
        recycle_result((accept_result_t *) overlapped);
        break;
    default:
        break;
    }
    if (batch) return batch;
    // No event generated.  Try again with next completion.
  }
}

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

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

void pn_proactor_interrupt(pn_proactor_t *p) {
  csguard g(&p->context.cslock);
  if (p->context.working)
    p->delayed_interrupt = true;
  else
    p->need_interrupt = true;
  proactor_wake(p);
}

// runs on a threadpool thread.  Must not hold timer_lock.
VOID CALLBACK timeout_cb(PVOID arg, BOOLEAN /* ignored*/ ) {
  pn_proactor_t *p = (pn_proactor_t *) arg;
  csguard gtimer(&p->timer_lock);
  csguard g(&p->context.cslock);
  if (p->timeout_set)
    p->need_timeout = true;  // else cancelled
  p->timeout_set = false;
  if (p->need_timeout)
    proactor_wake(p);
}

void pn_proactor_set_timeout(pn_proactor_t *p, pn_millis_t t) {
  bool ticking = false;
  csguard gtimer(&p->timer_lock);
  {
    csguard g(&p->context.cslock);
    ticking = (p->timeout_timer != NULL);
    if (t == 0) {
      p->need_timeout = true;
      p->timeout_set = false;
      proactor_wake(p);
    }
    else
      p->timeout_set = true;
  }
  // Just timer_lock held
  if (ticking) {
    stop_timer(p->timer_queue, &p->timeout_timer);
  }
  if (t) {
    start_timer(p->timer_queue, &p->timeout_timer, timeout_cb, p, t);
  }
}

void pn_proactor_cancel_timeout(pn_proactor_t *p) {
  bool ticking = false;
  csguard gtimer(&p->timer_lock);
  {
    csguard g(&p->context.cslock);
    p->timeout_set = false;
    ticking = (p->timeout_timer != NULL);
  }
  if (ticking) {
    stop_timer(p->timer_queue, &p->timeout_timer);
    csguard g(&p->context.cslock);
    wake_if_inactive(p);
  }
}

// Return true if connect_step_done()will handle connection status
static bool connect_step(pconnection_t *pc) {
  pn_proactor_t *p = pc->context.proactor;
  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 */
    unique_socket fd(::socket(ai->ai_family, SOCK_STREAM, ai->ai_protocol));
    if (fd != INVALID_SOCKET) {
      // Windows ConnectEx requires loosely bound socket.
      sockaddr_storage sa;
      memset(&sa, 0, sizeof(sa));
      sa.ss_family = ai->ai_family;
      if (!bind(fd, (SOCKADDR *) &sa, ai->ai_addrlen)) {
        pni_configure_sock_2(fd);
        pc->psocket.iocpd = pni_iocpdesc_create(p->iocp, fd);
        assert(pc->psocket.iocpd);
        pc->psocket.iocpd->write_closed = true;
        pc->psocket.iocpd->read_closed = true;
        fd.release();
        iocpdesc_t *iocpd = pc->psocket.iocpd;
        if (CreateIoCompletionPort ((HANDLE) iocpd->socket, iocpd->iocp->completion_port, proactor_io, 0)) {
          LPFN_CONNECTEX fn_connect_ex = lookup_connect_ex2(iocpd->socket);
          // addrinfo is owned by the pconnection so pass NULL to the connect result
          connect_result_t *result = connect_result(iocpd, NULL);
          iocpd->ops_in_progress++;
          iocpd->active_completer = &pc->psocket;
          // getpeername unreliable for outgoing connections, but we know it at this point
          memcpy(&pc->remote.ss, ai->ai_addr, ai->ai_addrlen);
          DWORD unused;
          bool success = fn_connect_ex(iocpd->socket, ai->ai_addr, ai->ai_addrlen,
                                       NULL, 0, &unused, (LPOVERLAPPED) result);
          if (success || WSAGetLastError() == ERROR_IO_PENDING) {
            return true;  // logic resumes at connect_step_done()
          } else {
            iocpd->ops_in_progress--;
            iocpd->active_completer = NULL;
            memset(&pc->remote.ss, 0, sizeof(pc->remote.ss));
          }
          pn_free(result);
        }
      }
      if (pc->psocket.iocpd) {
        pc->context.proactor->reaper->fast_reap(pc->psocket.iocpd);
        pc->psocket.iocpd = NULL;
      }
    }
  }
  pc->context.closing = true;
  return false;
}

static void connect_step_done(pconnection_t *pc, connect_result_t *result) {
  csguard g(&pc->context.cslock);
  DWORD saved_status = result->base.status;
  iocpdesc_t *iocpd = result->base.iocpd;
  iocpd->ops_in_progress--;
  assert(pc->psocket.iocpd == iocpd);
  complete_connect(result, result->base.status);  // frees result, starts regular IO if connected

  if (!saved_status) {
    // Success
    pc->psocket.iocpd->write_closed = false;
    pc->psocket.iocpd->read_closed = false;
    if (pc->addrinfo) {
      socklen_t len = sizeof(pc->local.ss);
      getsockname(pc->psocket.iocpd->socket, (struct sockaddr*)&pc->local.ss, &len);
      freeaddrinfo(pc->addrinfo);
      pc->addrinfo = NULL;
    }
    pc->ai = NULL;
    return;
  }
  else {
    // Descriptor will never be used.  Dispose.
    // Connect failed, no IO started, i.e. no pending iocpd based events
    pc->context.proactor->reaper->fast_reap(iocpd);
    pc->psocket.iocpd = NULL;
    memset(&pc->remote.ss, 0, sizeof(pc->remote.ss));
    // Is there a next connection target in the addrinfo to try?
    if (pc->ai && connect_step(pc)) {
      // Trying the next addrinfo possibility.  Will return here.
      return;
    }
    // Give up
    psocket_error(&pc->psocket, saved_status, "connect to ");
    pc->context.closing = true;
    wakeup(&pc->psocket);
  }
}

void pn_proactor_connect2(pn_proactor_t *p, pn_connection_t *c, pn_transport_t *t, const char *addr) {
  pconnection_t *pc = (pconnection_t*) pn_class_new(&pconnection_class, sizeof(pconnection_t));
  assert(pc); // TODO: memory safety
  const char *err = pconnection_setup(pc, p, c, t, false, addr);
  if (err) {
    pn_logf("pn_proactor_connect failure: %s", err);
    return;
  }
  // TODO: check case of proactor shutting down
  csguard g(&pc->context.cslock);
  pc->connecting = true;
  proactor_add(&pc->context);
  pn_connection_open(pc->driver.connection); /* Auto-open */

  if (!pgetaddrinfo(pc->psocket.host, pc->psocket.port, 0, &pc->addrinfo)) {
    pc->ai = pc->addrinfo;
    if (connect_step(pc)) {
      return;
    }
  }
  psocket_error(&pc->psocket, WSAGetLastError(), "connect to ");
  wakeup(&pc->psocket);
  if (p->reaper->add(pc->psocket.iocpd)) {
    pc->psocket.iocpd = NULL;
  }
}

void pn_proactor_release_connection(pn_connection_t *c) {
  bool notify = false;
  pconnection_t *pc = get_pconnection(c);
  if (pc) {
    csguard g(&pc->context.cslock);
    // reverse lifecycle entanglement of pc and c from new_pconnection_t()
    pn_incref(pc);
    pn_proactor_t *p = pc->context.proactor;
    csguard g2(&p->bind_lock);
    pn_record_t *r = pn_connection_attachments(pc->driver.connection);
    pn_record_set(r, PN_PROACTOR, NULL);
    pn_connection_driver_release_connection(&pc->driver);
    pc->bound = false;  // Transport unbound
    g2.release();
    pconnection_begin_close(pc);
  }
}

void pn_proactor_listen(pn_proactor_t *p, pn_listener_t *l, const char *addr, int backlog)
{
  csguard g(&l->context.cslock);
  l->context.proactor = p;;
  l->backlog = backlog;
  proactor_add(&l->context);

//  l->overflow = NO_OVERFLOW;  TODO, as for epoll

  char addr_buf[PN_MAX_ADDR];
  const char *host, *port;
  pni_parse_addr(addr, addr_buf, PN_MAX_ADDR, &host, &port);

  struct addrinfo *addrinfo = NULL;
  int gai_err = pgetaddrinfo(host, port, AI_PASSIVE | AI_ALL, &addrinfo);
  int wsa_err = 0;
  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->psockets = (psocket_t*)calloc(len, sizeof(psocket_t));
    assert(l->psockets);      /* TODO: memory safety */
    l->psockets_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);
      // Note fd destructor can clear WSAGetLastError()
      unique_socket fd(::socket(ai->ai_family, SOCK_STREAM, ai->ai_protocol));
      if (fd != INVALID_SOCKET) {
        bool yes = 1;
        if (!::bind(fd, ai->ai_addr, ai->ai_addrlen))
          if (!::listen(fd, backlog)) {
            iocpdesc_t *iocpd = pni_iocpdesc_create(p->iocp, fd);
            if (iocpd) {
              pn_socket_t sock = fd.release();
              psocket_t *ps = &l->psockets[l->psockets_size++];
              psocket_init(ps, l, false, addr);
              ps->iocpd = iocpd;
              iocpd->is_mp = true;
              iocpd->active_completer = ps;
              pni_iocpdesc_start(ps->iocpd);
              /* Get actual address */
              socklen_t len = sizeof(ps->listen_addr.ss);
              (void)getsockname(sock, (struct sockaddr*)&ps->listen_addr.ss, &len);
              if (ps == l->psockets) { /* First socket, check for dynamic port */
                dynamic_port = check_dynamic_port(ai->ai_addr, pn_netaddr_sockaddr(&ps->listen_addr));
              } else {
                (ps-1)->listen_addr.next = &ps->listen_addr; /* Link into list */
              }
            }
          }
      }
      wsa_err = WSAGetLastError();  // save it
    }
  }
  if (addrinfo) {
    freeaddrinfo(addrinfo);
  }
  if (l->psockets_size == 0) { /* All failed, create dummy socket with an error */
    l->psockets = (psocket_t*)calloc(sizeof(psocket_t), 1);
    psocket_init(l->psockets, l, false, addr);
    if (gai_err) {
      psocket_error(l->psockets, gai_err, "listen on");
    } else {
      psocket_error(l->psockets, wsa_err, "listen on");
    }
  } else {
    pn_collector_put(l->collector, pn_listener__class(), l, PN_LISTENER_OPEN);
  }
  wakeup(l->psockets);
}

// Assumes listener lock is held
static pn_event_batch_t *batch_owned(pn_listener_t *l) {
  if (l->close_dispatched) return NULL;
  if (!l->context.working) {
    if (listener_has_event(l)) {
      l->context.working = true;
      return &l->batch;
    }
    assert(!(l->context.closing && l->pending_events));
    if (l->pending_events) {
      pn_collector_put(l->collector, pn_listener__class(), l, PN_LISTENER_ACCEPT);
      l->pending_events--;
      l->context.working = true;
      return &l->batch;
    }
  }
  return NULL;
}

static void listener_close_all(pn_listener_t *l) {
  for (size_t i = 0; i < l->psockets_size; ++i) {
    psocket_t *ps = &l->psockets[i];
    if(ps->iocpd && !ps->iocpd->closing)
      if (l->context.proactor->reaper->add(ps->iocpd))
        ps->iocpd = NULL;
  }
}

static bool listener_can_free(pn_listener_t *l) {
  if (!l->close_dispatched) return false;
  if (l->context.working || l->context.completion_ops) return false;
  for (size_t i = 0; i < l->psockets_size; ++i) {
    psocket_t *ps = &l->psockets[i];
    if (ps->iocpd)
      return false;
  }
  return true;
}

/* Call with lock not held */
static inline void listener_final_free(pn_listener_t *l) {
  pcontext_finalize(&l->context);
  free(l->psockets);
  if (l->collector) pn_collector_free(l->collector);
  if (l->condition) pn_condition_free(l->condition);
  if (l->attachments) pn_free(l->attachments);
  free(l);
}

/* Call with listener lock held by lg.*/
static void internal_listener_free(pn_listener_t *l, csguard &g) {
  bool can_free = true;
  if (l->context.proactor) {
    can_free = proactor_remove(&l->context);
  }
  g.release();
  if (can_free)
    listener_final_free(l);
  // else final free is done by proactor_disconnect()
}

static bool listener_maybe_free(pn_listener_t *l, csguard &g) {
  if (listener_can_free(l)) {
    internal_listener_free(l, g);
    return true;
  }
  return false;
}

static pn_event_batch_t *listener_process(pn_listener_t *l, iocp_result_t *result) {
  accept_result_t *accept_result = NULL;
  psocket_t *ps = NULL;
  {
    csguard g(&l->context.cslock);
    if (!result) {
      wake_complete(&l->context);
      if (listener_maybe_free(l, g)) return NULL;
      return batch_owned(l);
    }
    else
      ps = (psocket_t *) result->iocpd->active_completer;

    if (!l->context.closing && result->status) {
      psocket_error(ps, WSAGetLastError(), "listen on "); // initiates close/multi-reap
    }
    if (l->context.closing) {
      if (l->context.proactor->reaper->process(result)) {
        ps->iocpd = NULL;
        if (listener_maybe_free(l, g)) return NULL;
      }
      return batch_owned(l);
    }

    accept_result = (accept_result_t *) result;
    l->context.completion_ops++;  // prevent accidental deletion while lock is temporarily removed
  }

  // No lock

  pn_socket_t accept_sock = accept_result->new_sock->socket;
  // AcceptEx special setsockopt:
  setsockopt(accept_sock, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char*)&ps->iocpd->socket,
             sizeof (SOCKET));
  // Connected.
  iocpdesc_t *conn_iocpd = accept_result->new_sock;
  conn_iocpd->read_closed = false;
  conn_iocpd->write_closed = false;
  pni_configure_sock_2(conn_iocpd->socket);

  {
    csguard g(&l->context.cslock);
    l->context.completion_ops--;
    accept_result->new_sock->ops_in_progress--;
    ps->iocpd->ops_in_progress--;

    // add even if closing to reuse cleanup code
    l->pending_accepts->push(accept_result);
    if (!ps->iocpd->ops_in_progress)
      begin_accept(ps->iocpd->acceptor, NULL);  // Start another, up to IOCP_MAX_ACCEPTS
    l->pending_events++;
    if (l->context.closing)
      release_pending_accepts(l);
    if (listener_maybe_free(l, g)) return NULL;
    return batch_owned(l);
  }
}

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

void pn_connection_wake(pn_connection_t* c) {
  pconnection_t *pc = get_pconnection(c);
  csguard g(&pc->context.cslock);
  if (!pc->context.closing) {
    pc->wake_count++;
    wakeup(&pc->psocket);
  }
}

pn_proactor_t *pn_proactor() {
  HANDLE tq = NULL;
  bool wsa = false;
  iocp_t *iocp = NULL;
  pn_proactor_t *p = NULL;
  pn_collector_t *c = NULL;
  class reaper *r = NULL;

  tq = CreateTimerQueue();
  if (tq) {
    p = (pn_proactor_t*)calloc(1, sizeof(*p));
    c = pn_collector();
    if (p && c) {
      /* Request WinSock 2.2 */
      WORD wsa_ver = MAKEWORD(2, 2);
      WSADATA unused;
      if (!WSAStartup(wsa_ver, &unused)) {
        wsa = true;
        if (iocp = pni_iocp()) {
          InitializeCriticalSectionAndSpinCount(&p->context.cslock, 4000);
          InitializeCriticalSectionAndSpinCount(&p->write_lock, 4000);
          InitializeCriticalSectionAndSpinCount(&p->timer_lock, 4000);
          InitializeCriticalSectionAndSpinCount(&p->bind_lock, 4000);
          try {
            r = new reaper(p, &p->write_lock, iocp);
            // success
            p->iocp = iocp;
            p->reaper = r;
            p->batch.next_event = &proactor_batch_next;
            p->collector = c;
            p->timer_queue = tq;
            return p;
          } catch (...) {}
        }
      }
    }
  }
  fprintf(stderr, "%s\n", errno_str("Windows Proton proactor OS resource failure", false).c_str());
  if (iocp) pn_free((void *) iocp);
  if (wsa) WSACleanup();
  free(c);
  free(p);
  if (tq) DeleteTimerQueueEx(tq, NULL);
  return NULL;
}

void pn_proactor_free(pn_proactor_t *p) {
  DeleteTimerQueueEx(p->timer_queue, INVALID_HANDLE_VALUE);
  DeleteCriticalSection(&p->timer_lock);
  DeleteCriticalSection(&p->bind_lock);
  proactor_shutdown(p);

  delete p->reaper;
  WSACleanup();
  pn_collector_free(p->collector);
  free(p);
}

static pn_event_t *listener_batch_next(pn_event_batch_t *batch) {
  pn_listener_t *l = batch_listener(batch);
  {
    csguard g(&l->context.cslock);
    if (!listener_has_event(l) && l->pending_events) {
      pn_collector_put(l->collector, pn_listener__class(), l, PN_LISTENER_ACCEPT);
      l->pending_events--;
    }
    pn_event_t *e = pn_collector_next(l->collector);
    if (e && pn_event_type(e) == PN_LISTENER_CLOSE)
      l->close_dispatched = true;
    return log_event(l, e);
  }
}

static void listener_done(pn_listener_t *l) {
  {
    csguard g(&l->context.cslock);
    l->context.working = false;
    if (l->close_dispatched) {
      listener_maybe_free(l, g);
      return;
    }
    else
      if (listener_has_event(l))
        wakeup(l->psockets);
  }
}

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();
    l->pending_accepts = new std::queue<accept_result_t *>();
    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
    pcontext_init(&l->context, LISTENER, unknown, l);
  }
  return 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, and all pending operations
     completed, i.e. listener_can_free() is true.
  */
  if (l) {
    csguard g(&l->context.cslock);
    if (l->context.proactor) {
      internal_listener_free(l, g);
      return;
    }
    // freed by user
    g.release();
    listener_final_free(l);
  }
}

static void listener_begin_close(pn_listener_t* l) {
  if (l->context.closing)
    return;
  l->context.closing = true;
  listener_close_all(l);
  release_pending_accepts(l);
  pn_collector_put(l->collector, pn_listener__class(), l, PN_LISTENER_CLOSE);
}

void pn_listener_close(pn_listener_t* l) {
  csguard g(&l->context.cslock);
  listener_begin_close(l);
  wakeup(&l->psockets[0]);
}

pn_proactor_t *pn_listener_proactor(pn_listener_t* l) {
  return l ? l->context.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;
}

static void release_pending_accepts(pn_listener_t *l) {
  // called with lock held or at shutdown
  while (!l->pending_accepts->empty()) {
    accept_result_t *accept_result = l->pending_accepts->front();
    l->pending_accepts->pop();
    psocket_t *ps = (psocket_t *) accept_result->base.iocpd->active_completer;
    accept_result->new_sock->ops_in_progress--;
    ps->iocpd->ops_in_progress--;
    l->context.proactor->reaper->add(accept_result->new_sock);
    begin_accept(accept_result->base.iocpd->acceptor, accept_result); // does proper disposal when closing
  }
  l->pending_events = 0;
}

static void recycle_result(accept_result_t *accept_result) {
  psocket_t *ps = (psocket_t *) accept_result->base.iocpd->active_completer;
  pn_listener_t *l = ps->listener;
  reset_accept_result(accept_result);
  accept_result->new_sock = create_same_type_socket(ps->iocpd);
  {
    csguard g(&l->context.cslock);
    if (l->context.closing && accept_result->new_sock) {
      closesocket(accept_result->new_sock->socket);
      accept_result->new_sock->socket = INVALID_SOCKET;
    }
    begin_accept(ps->iocpd->acceptor, accept_result);  // cleans up if closing
    l->context.completion_ops--;
    if (l->context.closing && listener_maybe_free(l, g))
      return;
  }
}

void pn_listener_accept2(pn_listener_t *l, pn_connection_t *c, pn_transport_t *t) {
  accept_result_t *accept_result = NULL;
  DWORD err = 0;
  psocket_t *ps = NULL;
  pn_proactor_t *p = l->context.proactor;

  {
    csguard g(&l->context.cslock);
    pconnection_t *pc = (pconnection_t*) pn_class_new(&pconnection_class, sizeof(pconnection_t));
    assert(pc);  // TODO: memory safety
    const char *err_str = pconnection_setup(pc, p, c, t, true, "");
    if (err_str) {
      pn_logf("pn_listener_accept failure: %s", err_str);
      return;
    }
    proactor_add(&pc->context);

    if (l->context.closing)
      err = WSAESHUTDOWN;
    else if (l->pending_accepts->empty())
      err = WSAEWOULDBLOCK;
    else {
      accept_result = l->pending_accepts->front();
      l->pending_accepts->pop();
      ps = (psocket_t *) accept_result->base.iocpd->active_completer;
      l->context.completion_ops++; // for recycle_result
      iocpdesc_t *conn_iocpd = accept_result->new_sock;
      pc->psocket.iocpd = conn_iocpd;
      conn_iocpd->active_completer =&pc->psocket;
      set_sock_names(pc);
      pc->started = true;
      csguard g(&pc->context.cslock);
      pni_iocpdesc_start(conn_iocpd);
    }

    if (err) {
      psocket_error(&pc->psocket, err, "listen on");
      wakeup(&pc->psocket);
    }
  }
  // Use another thread to prepare a replacement async accept request
  post_completion(p->iocp, recycle_accept_key, accept_result);
}



// Call with lock held.  Leave unchanged if events pending.
// 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;
    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);
  pn_event_t *e = pn_collector_next(p->collector);
  if (!e) {
    csguard g(&p->context.cslock);
    proactor_update_batch(p);
    e = pn_collector_next(p->collector);
  }
  if (e && pn_event_type(e) == PN_PROACTOR_TIMEOUT)
    p->timeout_processed = true;
  return log_event(p, e);
}

static void proactor_add(pcontext_t *ctx) {
  pn_proactor_t *p = ctx->proactor;
  csguard g(&p->context.cslock);
  if (p->contexts) {
    p->contexts->prev = ctx;
    ctx->next = p->contexts;
  }
  p->contexts = ctx;
}

// call with pcontext lock held
static bool proactor_remove(pcontext_t *ctx) {
  pn_proactor_t *p = ctx->proactor;
  csguard g(&p->context.cslock);
  bool can_free = true;
  if (ctx->disconnecting) {
    // No longer on contexts list
    --p->disconnects_pending;
    if (--ctx->disconnect_ops != 0) {
      // proactor_disconnect() does the free
      can_free = false;
    }
  }
  else {
    // normal case
    if (ctx->prev)
      ctx->prev->next = ctx->next;
    else {
      p->contexts = ctx->next;
      ctx->next = NULL;
      if (p->contexts)
        p->contexts->prev = NULL;
    }
    if (ctx->next) {
      ctx->next->prev = ctx->prev;
    }
  }
  wake_if_inactive(p);
  return can_free;
}

static void pconnection_forced_shutdown(pconnection_t *pc) {
  // Called by proactor_free, no competing threads processing iocp activity.
  pconnection_begin_close(pc);
  // Timer threads may lurk. No lock held, so no deadlock risk
  stop_timer(pc->context.proactor->timer_queue, &pc->tick_timer);
  pconnection_final_free(pc);
}

static void listener_forced_shutdown(pn_listener_t *l) {
  // Called by proactor_free, no competing threads, no iocp activity.
  listener_close_all(l);  // reaper gets all iocp descriptors to cleanup
  listener_final_free(l);
}


static void proactor_shutdown(pn_proactor_t *p) {
  // Called from free(), no competing threads except our own timer queue callbacks.
  p->shutting_down = true;
  while (p->contexts) {
    pcontext_t *ctx = p->contexts;
    p->contexts = ctx->next;
    switch (ctx->type) {
     case PCONNECTION:
      pconnection_forced_shutdown(pcontext_pconnection(ctx));
      break;
     case LISTENER:
      listener_forced_shutdown(pcontext_listener(ctx));
      break;
     default:
      break;
    }
  }

  // Graceful close of the sockets (zombies), closes completion port, free iocp related resources
  p->reaper->final_shutdown();
}

void pn_proactor_disconnect(pn_proactor_t *p, pn_condition_t *cond) {
  pcontext_t *disconnecting_pcontexts = NULL;
  pcontext_t *ctx = NULL;
  {
    csguard g(&p->context.cslock);
    // Move the whole contexts list into a disconnecting state
    disconnecting_pcontexts = p->contexts;
    p->contexts = NULL;
    // First pass: mark each pcontext as disconnecting and update global pending count.
    pcontext_t *ctx = disconnecting_pcontexts;
    while (ctx) {
      ctx->disconnecting = true;
      ctx->disconnect_ops = 2;   // Second pass below and proactor_remove(), in any order.
      p->disconnects_pending++;
      ctx = ctx->next;
    }
  }
  // no lock

  if (!disconnecting_pcontexts) {
    csguard p_guard(&p->context.cslock);
    wake_if_inactive(p);
    return;
  }

  // Second pass: different locking, close the pcontexts, free them if !disconnect_ops
  pcontext_t *next = disconnecting_pcontexts;
  while (next) {
    ctx = next;
    next = ctx->next;           /* Save next pointer in case we free ctx */
    bool do_free = false;
    pconnection_t *pc = pcontext_pconnection(ctx);
    pn_listener_t *l = pc ? NULL : pcontext_listener(ctx);
    CRITICAL_SECTION *ctx_cslock = pc ? &pc->context.cslock : &l->context.cslock;
    csguard ctx_guard(ctx_cslock);
    if (pc) {
      pc->can_wake = false;
      if (!ctx->closing) {
        if (ctx->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 context.
          if (cond) {
            pn_condition_copy(pn_transport_condition(pc->driver.transport), cond);
          }
          pn_connection_driver_close(&pc->driver);
        }
      }
    } else {
      assert(l);
      if (!ctx->closing) {
        if (cond) {
          pn_condition_copy(pn_listener_condition(l), cond);
        }
        listener_begin_close(l);
      }
    }

    csguard p_guard(&p->context.cslock);
    if (--ctx->disconnect_ops == 0) {
      do_free = true;
      wake_if_inactive(p);
    } else {
      // If initiating the close, wake the pcontext to do the free.
      wakeup(pc ? &pc->psocket : l->psockets);
    }
    p_guard.release();
    ctx_guard.release();

    if (do_free) {
      if (pc) pconnection_final_free(pc);
      else listener_final_free(pcontext_listener(ctx));
    }
  }
}

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->psockets ? &l->psockets[0].listen_addr : NULL;
}

pn_millis_t pn_proactor_now(void) {
  FILETIME now;
  GetSystemTimeAsFileTime(&now);
  ULARGE_INTEGER t;
  t.u.HighPart = now.dwHighDateTime;
  t.u.LowPart = now.dwLowDateTime;
  // Convert to milliseconds and adjust base epoch
  return t.QuadPart / 10000 - 11644473600000;
}
