Beispiel #1
0
/* Watch for a change in connectivity state.

   Once the channel connectivity state is different from the last observed
   state, tag will be enqueued on cq with success=1

   If deadline expires BEFORE the state is changed, tag will be enqueued on
   the completion queue with success=0 */
static VALUE grpc_rb_channel_watch_connectivity_state(VALUE self,
                                                      VALUE last_state,
                                                      VALUE cqueue,
                                                      VALUE deadline,
                                                      VALUE tag) {
  grpc_rb_channel *wrapper = NULL;
  grpc_channel *ch = NULL;
  grpc_completion_queue *cq = NULL;

  cq = grpc_rb_get_wrapped_completion_queue(cqueue);
  TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper);
  ch = wrapper->wrapped;
  if (ch == NULL) {
    rb_raise(rb_eRuntimeError, "closed!");
    return Qnil;
  }
  grpc_channel_watch_connectivity_state(
      ch,
      (grpc_connectivity_state)NUM2LONG(last_state),
      grpc_rb_time_timeval(deadline, /* absolute time */ 0),
      cq,
      ROBJECT(tag));

  return Qnil;
}
Beispiel #2
0
// Needs to be called under global_connection_poolling_mu
static void grpc_rb_channel_try_register_connection_polling(
    bg_watched_channel* bg) {
  grpc_connectivity_state conn_state;
  watch_state_op* op = NULL;

  GPR_ASSERT(channel_polling_thread_started || abort_channel_polling);

  if (bg->refcount == 0) {
    GPR_ASSERT(bg->channel_destroyed);
    bg_watched_channel_list_free_and_remove(bg);
    return;
  }
  GPR_ASSERT(bg->refcount == 1);
  if (bg->channel_destroyed || abort_channel_polling) {
    return;
  }

  conn_state = grpc_channel_check_connectivity_state(bg->channel, 0);
  if (conn_state == GRPC_CHANNEL_SHUTDOWN) {
    return;
  }
  GPR_ASSERT(bg_watched_channel_list_lookup(bg));
  // prevent bg from being free'd by GC while background thread is watching it
  bg->refcount++;

  op = gpr_zalloc(sizeof(watch_state_op));
  op->op_type = CONTINUOUS_WATCH;
  op->op.continuous_watch_callback_args.bg = bg;
  grpc_channel_watch_connectivity_state(bg->channel, conn_state,
                                        gpr_inf_future(GPR_CLOCK_REALTIME),
                                        channel_polling_cq, op);
}
Beispiel #3
0
GPR_EXPORT void GPR_CALLTYPE grpcsharp_channel_watch_connectivity_state(
    grpc_channel* channel, grpc_connectivity_state last_observed_state,
    gpr_timespec deadline, grpc_completion_queue* cq,
    grpcsharp_batch_context* ctx) {
  grpc_channel_watch_connectivity_state(channel, last_observed_state, deadline,
                                        cq, ctx);
}
Beispiel #4
0
static void* wait_for_watch_state_op_complete_without_gvl(void* arg) {
  watch_state_stack* stack = (watch_state_stack*)arg;
  watch_state_op* op = NULL;
  void* success = (void*)0;

  gpr_mu_lock(&global_connection_polling_mu);
  // its unsafe to do a "watch" after "channel polling abort" because the cq has
  // been shut down.
  if (abort_channel_polling || stack->bg_wrapped->channel_destroyed) {
    gpr_mu_unlock(&global_connection_polling_mu);
    return (void*)0;
  }
  op = gpr_zalloc(sizeof(watch_state_op));
  op->op_type = WATCH_STATE_API;
  grpc_channel_watch_connectivity_state(stack->bg_wrapped->channel,
                                        stack->last_state, stack->deadline,
                                        channel_polling_cq, op);

  while (!op->op.api_callback_args.called_back) {
    gpr_cv_wait(&global_connection_polling_cv, &global_connection_polling_mu,
                gpr_inf_future(GPR_CLOCK_REALTIME));
  }
  if (op->op.api_callback_args.success) {
    success = (void*)1;
  }
  gpr_free(op);
  gpr_mu_unlock(&global_connection_polling_mu);

  return success;
}
Beispiel #5
0
// Either start polling channel connection state or signal that it's free to
// destroy.
// Not safe to call while a channel's connection state is polled.
static void grpc_rb_channel_try_register_connection_polling(
  grpc_rb_channel *wrapper) {
  grpc_connectivity_state conn_state;
  gpr_timespec sleep_time = gpr_time_add(
      gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_millis(20, GPR_TIMESPAN));

  GPR_ASSERT(wrapper);
  GPR_ASSERT(wrapper->wrapped);
  gpr_mu_lock(&wrapper->channel_mu);
  if (wrapper->request_safe_destroy) {
    wrapper->safe_to_destroy = 1;
    gpr_cv_broadcast(&wrapper->channel_cv);
    gpr_mu_unlock(&wrapper->channel_mu);
    return;
  }
  gpr_mu_lock(&global_connection_polling_mu);

  conn_state = grpc_channel_check_connectivity_state(wrapper->wrapped, 0);
  if (conn_state != wrapper->current_connectivity_state) {
    wrapper->current_connectivity_state = conn_state;
    gpr_cv_broadcast(&wrapper->channel_cv);
  }
  // avoid posting work to the channel polling cq if it's been shutdown
  if (!abort_channel_polling && conn_state != GRPC_CHANNEL_SHUTDOWN) {
    grpc_channel_watch_connectivity_state(
        wrapper->wrapped, conn_state, sleep_time, channel_polling_cq, wrapper);
  } else {
    wrapper->safe_to_destroy = 1;
    gpr_cv_broadcast(&wrapper->channel_cv);
  }
  gpr_mu_unlock(&global_connection_polling_mu);
  gpr_mu_unlock(&wrapper->channel_mu);
}
Beispiel #6
0
/**
 * Watch the connectivity state of the channel until it changed
 * @param long The previous connectivity state of the channel
 * @param Timeval The deadline this function should wait until
 * @return bool If the connectivity state changes from last_state
 *              before deadline
 */
PHP_METHOD(Channel, watchConnectivityState) {
  wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
  zend_long last_state;
  zval *deadline_obj;

  /* "lO" == 1 long 1 object */
#ifndef FAST_ZPP
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "lO",
          &last_state, &deadline_obj, grpc_ce_timeval) == FAILURE) {
    zend_throw_exception(spl_ce_InvalidArgumentException,
        "watchConnectivityState expects 1 long 1 timeval", 1);
    return;
  }
#else
  ZEND_PARSE_PARAMETERS_START(2, 2)
    Z_PARAM_LONG(last_state)
    Z_PARAM_OBJECT_OF_CLASS(deadline_obj, grpc_ce_timeval)
  ZEND_PARSE_PARAMETERS_END();
#endif

  wrapped_grpc_timeval *deadline = Z_WRAPPED_GRPC_TIMEVAL_P(deadline_obj);
  grpc_channel_watch_connectivity_state(
      channel->wrapped, (grpc_connectivity_state)last_state,
      deadline->wrapped, completion_queue, NULL);
  grpc_event event = grpc_completion_queue_pluck(
      completion_queue, NULL,
      gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
  RETURN_BOOL(event.success);
}
Beispiel #7
0
static void test_ping(grpc_end2end_test_config config) {
  grpc_end2end_test_fixture f = config.create_fixture(NULL, NULL);
  cq_verifier *cqv = cq_verifier_create(f.cq);
  grpc_connectivity_state state = GRPC_CHANNEL_IDLE;
  int i;

  config.init_client(&f, NULL, NULL);
  config.init_server(&f, NULL);

  grpc_channel_ping(f.client, f.cq, tag(0), NULL);
  CQ_EXPECT_COMPLETION(cqv, tag(0), 0);

  /* check that we're still in idle, and start connecting */
  GPR_ASSERT(grpc_channel_check_connectivity_state(f.client, 1) ==
             GRPC_CHANNEL_IDLE);
  /* we'll go through some set of transitions (some might be missed), until
     READY is reached */
  while (state != GRPC_CHANNEL_READY) {
    grpc_channel_watch_connectivity_state(
        f.client, state, GRPC_TIMEOUT_SECONDS_TO_DEADLINE(3), f.cq, tag(99));
    CQ_EXPECT_COMPLETION(cqv, tag(99), 1);
    cq_verify(cqv);
    state = grpc_channel_check_connectivity_state(f.client, 0);
    GPR_ASSERT(state == GRPC_CHANNEL_READY ||
               state == GRPC_CHANNEL_CONNECTING ||
               state == GRPC_CHANNEL_TRANSIENT_FAILURE);
  }

  for (i = 1; i <= 5; i++) {
    grpc_channel_ping(f.client, f.cq, tag(i), NULL);
    CQ_EXPECT_COMPLETION(cqv, tag(i), 1);
    cq_verify(cqv);
  }

  grpc_server_shutdown_and_notify(f.server, f.cq, tag(0xdead));
  CQ_EXPECT_COMPLETION(cqv, tag(0xdead), 1);
  cq_verify(cqv);

  /* cleanup server */
  grpc_server_destroy(f.server);

  grpc_channel_destroy(f.client);
  grpc_completion_queue_shutdown(f.cq);
  grpc_completion_queue_destroy(f.cq);
  config.tear_down_data(&f);

  cq_verifier_destroy(cqv);
}
Beispiel #8
0
static void test_ping() {
  grpc_channel *client;
  request_data rdata;
  servers_fixture *f;
  cq_verifier *cqv;
  grpc_connectivity_state state = GRPC_CHANNEL_IDLE;
  const size_t num_servers = 1;
  int i;

  rdata.call_details = gpr_malloc(sizeof(grpc_call_details) * num_servers);
  f = setup_servers("127.0.0.1", &rdata, num_servers);
  cqv = cq_verifier_create(f->cq);

  client = create_client(f);

  grpc_channel_ping(client, f->cq, tag(0), NULL);
  cq_expect_completion(cqv, tag(0), 0);

  /* check that we're still in idle, and start connecting */
  GPR_ASSERT(grpc_channel_check_connectivity_state(client, 1) ==
             GRPC_CHANNEL_IDLE);
  /* we'll go through some set of transitions (some might be missed), until
     READY is reached */
  while (state != GRPC_CHANNEL_READY) {
    grpc_channel_watch_connectivity_state(
        client, state, GRPC_TIMEOUT_SECONDS_TO_DEADLINE(3), f->cq, tag(99));
    cq_expect_completion(cqv, tag(99), 1);
    cq_verify(cqv);
    state = grpc_channel_check_connectivity_state(client, 0);
    GPR_ASSERT(state == GRPC_CHANNEL_READY ||
               state == GRPC_CHANNEL_CONNECTING ||
               state == GRPC_CHANNEL_TRANSIENT_FAILURE);
  }

  for (i = 1; i <= 5; i++) {
    grpc_channel_ping(client, f->cq, tag(i), NULL);
    cq_expect_completion(cqv, tag(i), 1);
    cq_verify(cqv);
  }
  gpr_free(rdata.call_details);

  grpc_channel_destroy(client);
  teardown_servers(f);

  cq_verifier_destroy(cqv);
}
void create_loop_destroy(void *addr) {
  for (int i = 0; i < NUM_OUTER_LOOPS; ++i) {
    grpc_completion_queue *cq = grpc_completion_queue_create(NULL);
    grpc_channel *chan = grpc_insecure_channel_create((char *)addr, NULL, NULL);

    for (int j = 0; j < NUM_INNER_LOOPS; ++j) {
      gpr_timespec later_time = GRPC_TIMEOUT_MILLIS_TO_DEADLINE(DELAY_MILLIS);
      grpc_connectivity_state state =
          grpc_channel_check_connectivity_state(chan, 1);
      grpc_channel_watch_connectivity_state(chan, state, later_time, cq, NULL);
      gpr_timespec poll_time = GRPC_TIMEOUT_MILLIS_TO_DEADLINE(POLL_MILLIS);
      GPR_ASSERT(grpc_completion_queue_next(cq, poll_time, NULL).type ==
                 GRPC_OP_COMPLETE);
    }
    grpc_channel_destroy(chan);
    grpc_completion_queue_destroy(cq);
  }
}
void create_loop_destroy(void *addr) {
  for (int i = 0; i < NUM_OUTER_LOOPS; ++i) {
    grpc_completion_queue *cq = grpc_completion_queue_create_for_next(NULL);
    grpc_channel *chan = grpc_insecure_channel_create((char *)addr, NULL, NULL);

    for (int j = 0; j < NUM_INNER_LOOPS; ++j) {
      gpr_timespec later_time =
          grpc_timeout_milliseconds_to_deadline(DELAY_MILLIS);
      grpc_connectivity_state state =
          grpc_channel_check_connectivity_state(chan, 1);
      grpc_channel_watch_connectivity_state(chan, state, later_time, cq, NULL);
      gpr_timespec poll_time =
          grpc_timeout_milliseconds_to_deadline(POLL_MILLIS);
      GPR_ASSERT(grpc_completion_queue_next(cq, poll_time, NULL).type ==
                 GRPC_OP_COMPLETE);
      /* check that the watcher from "watch state" was free'd */
      GPR_ASSERT(grpc_channel_num_external_connectivity_watchers(chan) == 0);
    }
    grpc_channel_destroy(chan);
    grpc_completion_queue_destroy(cq);
  }
}
void watches_with_short_timeouts(void *addr) {
  for (int i = 0; i < NUM_OUTER_LOOPS_SHORT_TIMEOUTS; ++i) {
    grpc_completion_queue *cq = grpc_completion_queue_create_for_next(NULL);
    grpc_channel *chan = grpc_insecure_channel_create((char *)addr, NULL, NULL);

    for (int j = 0; j < NUM_INNER_LOOPS_SHORT_TIMEOUTS; ++j) {
      gpr_timespec later_time =
          grpc_timeout_milliseconds_to_deadline(DELAY_MILLIS_SHORT_TIMEOUTS);
      grpc_connectivity_state state =
          grpc_channel_check_connectivity_state(chan, 0);
      GPR_ASSERT(state == GRPC_CHANNEL_IDLE);
      grpc_channel_watch_connectivity_state(chan, state, later_time, cq, NULL);
      gpr_timespec poll_time =
          grpc_timeout_milliseconds_to_deadline(POLL_MILLIS_SHORT_TIMEOUTS);
      grpc_event ev = grpc_completion_queue_next(cq, poll_time, NULL);
      GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
      GPR_ASSERT(ev.success == false);
      /* check that the watcher from "watch state" was free'd */
      GPR_ASSERT(grpc_channel_num_external_connectivity_watchers(chan) == 0);
    }
    grpc_channel_destroy(chan);
    grpc_completion_queue_destroy(cq);
  }
}
static void test_connectivity(grpc_end2end_test_config config) {
  grpc_end2end_test_fixture f = config.create_fixture(NULL, NULL);
  grpc_connectivity_state state;
  cq_verifier *cqv = cq_verifier_create(f.cq);
  child_events ce;
  gpr_thd_options thdopt = gpr_thd_options_default();
  gpr_thd_id thdid;

  config.init_client(&f, NULL);

  ce.channel = f.client;
  ce.cq = f.cq;
  gpr_event_init(&ce.started);
  gpr_thd_options_set_joinable(&thdopt);
  GPR_ASSERT(gpr_thd_new(&thdid, child_thread, &ce, &thdopt));

  gpr_event_wait(&ce.started, gpr_inf_future(GPR_CLOCK_MONOTONIC));

  /* channels should start life in IDLE, and stay there */
  GPR_ASSERT(grpc_channel_check_connectivity_state(f.client, 0) ==
             GRPC_CHANNEL_IDLE);
  gpr_sleep_until(GRPC_TIMEOUT_MILLIS_TO_DEADLINE(100));
  GPR_ASSERT(grpc_channel_check_connectivity_state(f.client, 0) ==
             GRPC_CHANNEL_IDLE);

  /* start watching for a change */
  gpr_log(GPR_DEBUG, "watching");
  grpc_channel_watch_connectivity_state(
      f.client, GRPC_CHANNEL_IDLE, gpr_now(GPR_CLOCK_MONOTONIC), f.cq, tag(1));

  /* eventually the child thread completion should trigger */
  gpr_thd_join(thdid);

  /* check that we're still in idle, and start connecting */
  GPR_ASSERT(grpc_channel_check_connectivity_state(f.client, 1) ==
             GRPC_CHANNEL_IDLE);
  /* start watching for a change */
  grpc_channel_watch_connectivity_state(f.client, GRPC_CHANNEL_IDLE,
                                        GRPC_TIMEOUT_SECONDS_TO_DEADLINE(3),
                                        f.cq, tag(2));

  /* and now the watch should trigger */
  cq_expect_completion(cqv, tag(2), 1);
  cq_verify(cqv);
  state = grpc_channel_check_connectivity_state(f.client, 0);
  GPR_ASSERT(state == GRPC_CHANNEL_TRANSIENT_FAILURE ||
             state == GRPC_CHANNEL_CONNECTING);

  /* quickly followed by a transition to TRANSIENT_FAILURE */
  grpc_channel_watch_connectivity_state(f.client, GRPC_CHANNEL_CONNECTING,
                                        GRPC_TIMEOUT_SECONDS_TO_DEADLINE(3),
                                        f.cq, tag(3));
  cq_expect_completion(cqv, tag(3), 1);
  cq_verify(cqv);
  state = grpc_channel_check_connectivity_state(f.client, 0);
  GPR_ASSERT(state == GRPC_CHANNEL_TRANSIENT_FAILURE ||
             state == GRPC_CHANNEL_CONNECTING);

  gpr_log(GPR_DEBUG, "*** STARTING SERVER ***");

  /* now let's bring up a server to connect to */
  config.init_server(&f, NULL);

  gpr_log(GPR_DEBUG, "*** STARTED SERVER ***");

  /* we'll go through some set of transitions (some might be missed), until
     READY is reached */
  while (state != GRPC_CHANNEL_READY) {
    grpc_channel_watch_connectivity_state(
        f.client, state, GRPC_TIMEOUT_SECONDS_TO_DEADLINE(3), f.cq, tag(4));
    cq_expect_completion(cqv, tag(4), 1);
    cq_verify(cqv);
    state = grpc_channel_check_connectivity_state(f.client, 0);
    GPR_ASSERT(state == GRPC_CHANNEL_READY ||
               state == GRPC_CHANNEL_CONNECTING ||
               state == GRPC_CHANNEL_TRANSIENT_FAILURE);
  }

  /* bring down the server again */
  /* we should go immediately to TRANSIENT_FAILURE */
  gpr_log(GPR_DEBUG, "*** SHUTTING DOWN SERVER ***");

  grpc_channel_watch_connectivity_state(f.client, GRPC_CHANNEL_READY,
                                        GRPC_TIMEOUT_SECONDS_TO_DEADLINE(3),
                                        f.cq, tag(5));

  grpc_server_shutdown_and_notify(f.server, f.cq, tag(0xdead));

  cq_expect_completion(cqv, tag(5), 1);
  cq_expect_completion(cqv, tag(0xdead), 1);
  cq_verify(cqv);
  state = grpc_channel_check_connectivity_state(f.client, 0);
  GPR_ASSERT(state == GRPC_CHANNEL_TRANSIENT_FAILURE ||
             state == GRPC_CHANNEL_CONNECTING || state == GRPC_CHANNEL_IDLE);

  /* cleanup server */
  grpc_server_destroy(f.server);

  gpr_log(GPR_DEBUG, "*** SHUTDOWN SERVER ***");

  grpc_channel_destroy(f.client);
  grpc_completion_queue_shutdown(f.cq);
  grpc_completion_queue_destroy(f.cq);
  config.tear_down_data(&f);

  cq_verifier_destroy(cqv);
}
Beispiel #13
0
// This test launches a minimal TLS server on a separate thread and then
// establishes a TLS handshake via the core library to the server. The TLS
// server validates ALPN aspects of the handshake and supplies the protocol
// specified in the server_alpn_preferred argument to the client.
static bool client_ssl_test(char *server_alpn_preferred) {
  bool success = true;

  grpc_init();

  // Find a port we can bind to. Retries added to handle flakes in port server
  // and port picking.
  int port = -1;
  int server_socket = -1;
  int socket_retries = 10;
  while (server_socket == -1 && socket_retries-- > 0) {
    port = grpc_pick_unused_port_or_die();
    server_socket = create_socket(port);
    if (server_socket == -1) {
      sleep(1);
    }
  }
  GPR_ASSERT(server_socket > 0);

  // Launch the TLS server thread.
  gpr_thd_options thdopt = gpr_thd_options_default();
  gpr_thd_id thdid;
  gpr_thd_options_set_joinable(&thdopt);
  server_args args = {.socket = server_socket,
                      .alpn_preferred = server_alpn_preferred};
  GPR_ASSERT(gpr_thd_new(&thdid, server_thread, &args, &thdopt));

  // Load key pair and establish client SSL credentials.
  grpc_ssl_pem_key_cert_pair pem_key_cert_pair;
  gpr_slice ca_slice, cert_slice, key_slice;
  GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
                               grpc_load_file(SSL_CA_PATH, 1, &ca_slice)));
  GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
                               grpc_load_file(SSL_CERT_PATH, 1, &cert_slice)));
  GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
                               grpc_load_file(SSL_KEY_PATH, 1, &key_slice)));
  const char *ca_cert = (const char *)GPR_SLICE_START_PTR(ca_slice);
  pem_key_cert_pair.private_key = (const char *)GPR_SLICE_START_PTR(key_slice);
  pem_key_cert_pair.cert_chain = (const char *)GPR_SLICE_START_PTR(cert_slice);
  grpc_channel_credentials *ssl_creds =
      grpc_ssl_credentials_create(ca_cert, &pem_key_cert_pair, NULL);

  // Establish a channel pointing at the TLS server. Since the gRPC runtime is
  // lazy, this won't necessarily establish a connection yet.
  char *target;
  gpr_asprintf(&target, "127.0.0.1:%d", port);
  grpc_arg ssl_name_override = {GRPC_ARG_STRING,
                                GRPC_SSL_TARGET_NAME_OVERRIDE_ARG,
                                {"foo.test.google.fr"}};
  grpc_channel_args grpc_args;
  grpc_args.num_args = 1;
  grpc_args.args = &ssl_name_override;
  grpc_channel *channel =
      grpc_secure_channel_create(ssl_creds, target, &grpc_args, NULL);
  GPR_ASSERT(channel);
  gpr_free(target);

  // Initially the channel will be idle, the
  // grpc_channel_check_connectivity_state triggers an attempt to connect.
  GPR_ASSERT(grpc_channel_check_connectivity_state(
                 channel, 1 /* try_to_connect */) == GRPC_CHANNEL_IDLE);

  // Wait a bounded number of times for the channel to be ready. When the
  // channel is ready, the initial TLS handshake will have successfully
  // completed and we know that the client's ALPN list satisfied the server.
  int retries = 10;
  grpc_connectivity_state state = GRPC_CHANNEL_IDLE;
  grpc_completion_queue *cq = grpc_completion_queue_create(NULL);
  while (state != GRPC_CHANNEL_READY && retries-- > 0) {
    grpc_channel_watch_connectivity_state(
        channel, state, GRPC_TIMEOUT_SECONDS_TO_DEADLINE(3), cq, NULL);
    gpr_timespec cq_deadline = GRPC_TIMEOUT_SECONDS_TO_DEADLINE(5);
    grpc_event ev = grpc_completion_queue_next(cq, cq_deadline, NULL);
    GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
    state =
        grpc_channel_check_connectivity_state(channel, 0 /* try_to_connect */);
  }
  grpc_completion_queue_destroy(cq);
  if (retries < 0) {
    success = false;
  }

  grpc_channel_destroy(channel);
  grpc_channel_credentials_release(ssl_creds);
  gpr_slice_unref(cert_slice);
  gpr_slice_unref(key_slice);
  gpr_slice_unref(ca_slice);

  gpr_thd_join(thdid);

  grpc_shutdown();

  return success;
}
Beispiel #14
0
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  grpc_test_only_set_metadata_hash_seed(0);
  if (squelch) gpr_set_log_function(dont_log);
  input_stream inp = {data, data + size};
  grpc_resolve_address = my_resolve_address;
  grpc_tcp_client_connect_impl = my_tcp_client_connect;
  gpr_now_impl = now_impl;
  grpc_init();

  GPR_ASSERT(g_channel == NULL);
  GPR_ASSERT(g_server == NULL);

  bool server_shutdown = false;
  int pending_server_shutdowns = 0;
  int pending_channel_watches = 0;
  int pending_pings = 0;

  g_active_call = new_call(NULL, ROOT);

  grpc_completion_queue *cq = grpc_completion_queue_create(NULL);

  while (!is_eof(&inp) || g_channel != NULL || g_server != NULL ||
         pending_channel_watches > 0 || pending_pings > 0 ||
         g_active_call->type != ROOT || g_active_call->next != g_active_call) {
    if (is_eof(&inp)) {
      if (g_channel != NULL) {
        grpc_channel_destroy(g_channel);
        g_channel = NULL;
      }
      if (g_server != NULL) {
        if (!server_shutdown) {
          grpc_server_shutdown_and_notify(
              g_server, cq, create_validator(assert_success_and_decrement,
                                             &pending_server_shutdowns));
          server_shutdown = true;
          pending_server_shutdowns++;
        } else if (pending_server_shutdowns == 0) {
          grpc_server_destroy(g_server);
          g_server = NULL;
        }
      }
      call_state *s = g_active_call;
      do {
        if (s->type != PENDING_SERVER && s->call != NULL) {
          s = destroy_call(s);
        } else {
          s = s->next;
        }
      } while (s != g_active_call);

      g_now = gpr_time_add(g_now, gpr_time_from_seconds(1, GPR_TIMESPAN));
    }

    switch (next_byte(&inp)) {
      // terminate on bad bytes
      default:
        end(&inp);
        break;
      // tickle completion queue
      case 0: {
        grpc_event ev = grpc_completion_queue_next(
            cq, gpr_inf_past(GPR_CLOCK_REALTIME), NULL);
        switch (ev.type) {
          case GRPC_OP_COMPLETE: {
            validator *v = ev.tag;
            v->validate(v->arg, ev.success);
            gpr_free(v);
            break;
          }
          case GRPC_QUEUE_TIMEOUT:
            break;
          case GRPC_QUEUE_SHUTDOWN:
            abort();
            break;
        }
        break;
      }
      // increment global time
      case 1: {
        g_now = gpr_time_add(
            g_now, gpr_time_from_micros(read_uint32(&inp), GPR_TIMESPAN));
        break;
      }
      // create an insecure channel
      case 2: {
        if (g_channel == NULL) {
          char *target = read_string(&inp);
          char *target_uri;
          gpr_asprintf(&target_uri, "dns:%s", target);
          grpc_channel_args *args = read_args(&inp);
          g_channel = grpc_insecure_channel_create(target_uri, args, NULL);
          GPR_ASSERT(g_channel != NULL);
          grpc_channel_args_destroy(args);
          gpr_free(target_uri);
          gpr_free(target);
        } else {
          end(&inp);
        }
        break;
      }
      // destroy a channel
      case 3: {
        if (g_channel != NULL) {
          grpc_channel_destroy(g_channel);
          g_channel = NULL;
        } else {
          end(&inp);
        }
        break;
      }
      // bring up a server
      case 4: {
        if (g_server == NULL) {
          grpc_channel_args *args = read_args(&inp);
          g_server = grpc_server_create(args, NULL);
          GPR_ASSERT(g_server != NULL);
          grpc_channel_args_destroy(args);
          grpc_server_register_completion_queue(g_server, cq, NULL);
          grpc_server_start(g_server);
          server_shutdown = false;
          GPR_ASSERT(pending_server_shutdowns == 0);
        } else {
          end(&inp);
        }
      }
      // begin server shutdown
      case 5: {
        if (g_server != NULL) {
          grpc_server_shutdown_and_notify(
              g_server, cq, create_validator(assert_success_and_decrement,
                                             &pending_server_shutdowns));
          pending_server_shutdowns++;
          server_shutdown = true;
        } else {
          end(&inp);
        }
        break;
      }
      // cancel all calls if shutdown
      case 6: {
        if (g_server != NULL && server_shutdown) {
          grpc_server_cancel_all_calls(g_server);
        } else {
          end(&inp);
        }
        break;
      }
      // destroy server
      case 7: {
        if (g_server != NULL && server_shutdown &&
            pending_server_shutdowns == 0) {
          grpc_server_destroy(g_server);
          g_server = NULL;
        } else {
          end(&inp);
        }
        break;
      }
      // check connectivity
      case 8: {
        if (g_channel != NULL) {
          uint8_t try_to_connect = next_byte(&inp);
          if (try_to_connect == 0 || try_to_connect == 1) {
            grpc_channel_check_connectivity_state(g_channel, try_to_connect);
          } else {
            end(&inp);
          }
        } else {
          end(&inp);
        }
        break;
      }
      // watch connectivity
      case 9: {
        if (g_channel != NULL) {
          grpc_connectivity_state st =
              grpc_channel_check_connectivity_state(g_channel, 0);
          if (st != GRPC_CHANNEL_FATAL_FAILURE) {
            gpr_timespec deadline = gpr_time_add(
                gpr_now(GPR_CLOCK_REALTIME),
                gpr_time_from_micros(read_uint32(&inp), GPR_TIMESPAN));
            grpc_channel_watch_connectivity_state(
                g_channel, st, deadline, cq,
                create_validator(validate_connectivity_watch,
                                 make_connectivity_watch(
                                     deadline, &pending_channel_watches)));
            pending_channel_watches++;
          }
        } else {
          end(&inp);
        }
        break;
      }
      // create a call
      case 10: {
        bool ok = true;
        if (g_channel == NULL) ok = false;
        grpc_call *parent_call = NULL;
        if (g_active_call->type != ROOT) {
          if (g_active_call->call == NULL || g_active_call->type == CLIENT) {
            end(&inp);
            break;
          }
          parent_call = g_active_call->call;
        }
        uint32_t propagation_mask = read_uint32(&inp);
        char *method = read_string(&inp);
        char *host = read_string(&inp);
        gpr_timespec deadline =
            gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),
                         gpr_time_from_micros(read_uint32(&inp), GPR_TIMESPAN));

        if (ok) {
          call_state *cs = new_call(g_active_call, CLIENT);
          cs->call =
              grpc_channel_create_call(g_channel, parent_call, propagation_mask,
                                       cq, method, host, deadline, NULL);
        } else {
          end(&inp);
        }
        gpr_free(method);
        gpr_free(host);
        break;
      }
      // switch the 'current' call
      case 11: {
        g_active_call = g_active_call->next;
        break;
      }
      // queue some ops on a call
      case 12: {
        if (g_active_call->type == PENDING_SERVER ||
            g_active_call->type == ROOT || g_active_call->call == NULL) {
          end(&inp);
          break;
        }
        size_t num_ops = next_byte(&inp);
        if (num_ops > 6) {
          end(&inp);
          break;
        }
        grpc_op *ops = gpr_malloc(sizeof(grpc_op) * num_ops);
        bool ok = true;
        size_t i;
        grpc_op *op;
        for (i = 0; i < num_ops; i++) {
          op = &ops[i];
          switch (next_byte(&inp)) {
            default:
              /* invalid value */
              op->op = (grpc_op_type)-1;
              ok = false;
              break;
            case GRPC_OP_SEND_INITIAL_METADATA:
              op->op = GRPC_OP_SEND_INITIAL_METADATA;
              read_metadata(&inp, &op->data.send_initial_metadata.count,
                            &op->data.send_initial_metadata.metadata,
                            g_active_call);
              break;
            case GRPC_OP_SEND_MESSAGE:
              op->op = GRPC_OP_SEND_MESSAGE;
              op->data.send_message = read_message(&inp);
              break;
            case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
              op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
              break;
            case GRPC_OP_SEND_STATUS_FROM_SERVER:
              op->op = GRPC_OP_SEND_STATUS_FROM_SERVER;
              read_metadata(
                  &inp,
                  &op->data.send_status_from_server.trailing_metadata_count,
                  &op->data.send_status_from_server.trailing_metadata,
                  g_active_call);
              op->data.send_status_from_server.status = next_byte(&inp);
              op->data.send_status_from_server.status_details =
                  read_string(&inp);
              break;
            case GRPC_OP_RECV_INITIAL_METADATA:
              op->op = GRPC_OP_RECV_INITIAL_METADATA;
              op->data.recv_initial_metadata =
                  &g_active_call->recv_initial_metadata;
              break;
            case GRPC_OP_RECV_MESSAGE:
              op->op = GRPC_OP_RECV_MESSAGE;
              op->data.recv_message = &g_active_call->recv_message;
              break;
            case GRPC_OP_RECV_STATUS_ON_CLIENT:
              op->op = GRPC_OP_RECV_STATUS_ON_CLIENT;
              op->data.recv_status_on_client.status = &g_active_call->status;
              op->data.recv_status_on_client.trailing_metadata =
                  &g_active_call->recv_trailing_metadata;
              op->data.recv_status_on_client.status_details =
                  &g_active_call->recv_status_details;
              op->data.recv_status_on_client.status_details_capacity =
                  &g_active_call->recv_status_details_capacity;
              break;
            case GRPC_OP_RECV_CLOSE_ON_SERVER:
              op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
              op->data.recv_close_on_server.cancelled =
                  &g_active_call->cancelled;
              break;
          }
          op->reserved = NULL;
          op->flags = read_uint32(&inp);
        }
        if (ok) {
          validator *v = create_validator(finished_batch, g_active_call);
          g_active_call->pending_ops++;
          grpc_call_error error =
              grpc_call_start_batch(g_active_call->call, ops, num_ops, v, NULL);
          if (error != GRPC_CALL_OK) {
            v->validate(v->arg, false);
            gpr_free(v);
          }
        } else {
          end(&inp);
        }
        for (i = 0; i < num_ops; i++) {
          op = &ops[i];
          switch (op->op) {
            case GRPC_OP_SEND_INITIAL_METADATA:
              break;
            case GRPC_OP_SEND_MESSAGE:
              grpc_byte_buffer_destroy(op->data.send_message);
              break;
            case GRPC_OP_SEND_STATUS_FROM_SERVER:
              gpr_free((void *)op->data.send_status_from_server.status_details);
              break;
            case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
            case GRPC_OP_RECV_INITIAL_METADATA:
            case GRPC_OP_RECV_MESSAGE:
            case GRPC_OP_RECV_STATUS_ON_CLIENT:
            case GRPC_OP_RECV_CLOSE_ON_SERVER:
              break;
          }
        }
        gpr_free(ops);

        break;
      }
      // cancel current call
      case 13: {
        if (g_active_call->type != ROOT && g_active_call->call != NULL) {
          grpc_call_cancel(g_active_call->call, NULL);
        } else {
          end(&inp);
        }
        break;
      }
      // get a calls peer
      case 14: {
        if (g_active_call->type != ROOT && g_active_call->call != NULL) {
          free_non_null(grpc_call_get_peer(g_active_call->call));
        } else {
          end(&inp);
        }
        break;
      }
      // get a channels target
      case 15: {
        if (g_channel != NULL) {
          free_non_null(grpc_channel_get_target(g_channel));
        } else {
          end(&inp);
        }
        break;
      }
      // send a ping on a channel
      case 16: {
        if (g_channel != NULL) {
          pending_pings++;
          grpc_channel_ping(g_channel, cq,
                            create_validator(decrement, &pending_pings), NULL);
        } else {
          end(&inp);
        }
        break;
      }
      // enable a tracer
      case 17: {
        char *tracer = read_string(&inp);
        grpc_tracer_set_enabled(tracer, 1);
        gpr_free(tracer);
        break;
      }
      // disable a tracer
      case 18: {
        char *tracer = read_string(&inp);
        grpc_tracer_set_enabled(tracer, 0);
        gpr_free(tracer);
        break;
      }
      // request a server call
      case 19: {
        if (g_server == NULL) {
          end(&inp);
          break;
        }
        call_state *cs = new_call(g_active_call, PENDING_SERVER);
        cs->pending_ops++;
        validator *v = create_validator(finished_request_call, cs);
        grpc_call_error error =
            grpc_server_request_call(g_server, &cs->call, &cs->call_details,
                                     &cs->recv_initial_metadata, cq, cq, v);
        if (error != GRPC_CALL_OK) {
          v->validate(v->arg, false);
          gpr_free(v);
        }
        break;
      }
      // destroy a call
      case 20: {
        if (g_active_call->type != ROOT &&
            g_active_call->type != PENDING_SERVER &&
            g_active_call->call != NULL) {
          destroy_call(g_active_call);
        } else {
          end(&inp);
        }
        break;
      }
    }
  }

  GPR_ASSERT(g_channel == NULL);
  GPR_ASSERT(g_server == NULL);
  GPR_ASSERT(g_active_call->type == ROOT);
  GPR_ASSERT(g_active_call->next == g_active_call);
  gpr_free(g_active_call);

  grpc_completion_queue_shutdown(cq);
  GPR_ASSERT(
      grpc_completion_queue_next(cq, gpr_inf_past(GPR_CLOCK_REALTIME), NULL)
          .type == GRPC_QUEUE_SHUTDOWN);
  grpc_completion_queue_destroy(cq);

  grpc_shutdown();
  return 0;
}