/* * * Copyright 2014, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "src/core/surface/server.h" #include #include #include "src/core/channel/census_filter.h" #include "src/core/channel/channel_args.h" #include "src/core/channel/connected_channel.h" #include "src/core/iomgr/iomgr.h" #include "src/core/support/string.h" #include "src/core/surface/call.h" #include "src/core/surface/channel.h" #include "src/core/surface/completion_queue.h" #include "src/core/transport/metadata.h" #include #include #include typedef enum { PENDING_START, ALL_CALLS, CALL_LIST_COUNT } call_list; typedef struct listener { void *arg; void (*start)(grpc_server *server, void *arg, grpc_pollset *pollset); void (*destroy)(grpc_server *server, void *arg); struct listener *next; } listener; typedef struct call_data call_data; typedef struct channel_data channel_data; struct channel_data { grpc_server *server; grpc_channel *channel; grpc_mdstr *path_key; grpc_mdstr *authority_key; /* linked list of all channels on a server */ channel_data *next; channel_data *prev; }; typedef void (*new_call_cb)(grpc_server *server, grpc_completion_queue *cq, grpc_call **call, grpc_call_details *details, grpc_metadata_array *initial_metadata, call_data *calld, void *user_data); typedef struct { void *user_data; grpc_completion_queue *cq; grpc_call **call; grpc_call_details *details; grpc_metadata_array *initial_metadata; new_call_cb cb; } requested_call; struct grpc_server { size_t channel_filter_count; const grpc_channel_filter **channel_filters; grpc_channel_args *channel_args; grpc_completion_queue *cq; gpr_mu mu; requested_call *requested_calls; size_t requested_call_count; size_t requested_call_capacity; gpr_uint8 shutdown; gpr_uint8 have_shutdown_tag; void *shutdown_tag; call_data *lists[CALL_LIST_COUNT]; channel_data root_channel_data; listener *listeners; gpr_refcount internal_refcount; }; typedef struct { call_data *next; call_data *prev; } call_link; typedef enum { /* waiting for metadata */ NOT_STARTED, /* inital metadata read, not flow controlled in yet */ PENDING, /* flow controlled in, on completion queue */ ACTIVATED, /* cancelled before being queued */ ZOMBIED } call_state; typedef struct legacy_data { grpc_metadata_array *initial_metadata; } legacy_data; struct call_data { grpc_call *call; call_state state; gpr_timespec deadline; grpc_mdstr *path; grpc_mdstr *host; legacy_data *legacy; grpc_call_details *details; gpr_uint8 included[CALL_LIST_COUNT]; call_link links[CALL_LIST_COUNT]; }; #define SERVER_FROM_CALL_ELEM(elem) \ (((channel_data *)(elem)->channel_data)->server) static void do_nothing(void *unused, grpc_op_error ignored) {} static int call_list_join(grpc_server *server, call_data *call, call_list list) { if (call->included[list]) return 0; call->included[list] = 1; if (!server->lists[list]) { server->lists[list] = call; call->links[list].next = call->links[list].prev = call; } else { call->links[list].next = server->lists[list]; call->links[list].prev = server->lists[list]->links[list].prev; call->links[list].next->links[list].prev = call->links[list].prev->links[list].next = call; } return 1; } static call_data *call_list_remove_head(grpc_server *server, call_list list) { call_data *out = server->lists[list]; if (out) { out->included[list] = 0; if (out->links[list].next == out) { server->lists[list] = NULL; } else { server->lists[list] = out->links[list].next; out->links[list].next->links[list].prev = out->links[list].prev; out->links[list].prev->links[list].next = out->links[list].next; } } return out; } static int call_list_remove(grpc_server *server, call_data *call, call_list list) { if (!call->included[list]) return 0; call->included[list] = 0; if (server->lists[list] == call) { server->lists[list] = call->links[list].next; if (server->lists[list] == call) { server->lists[list] = NULL; return 1; } } GPR_ASSERT(server->lists[list] != call); call->links[list].next->links[list].prev = call->links[list].prev; call->links[list].prev->links[list].next = call->links[list].next; return 1; } static void server_ref(grpc_server *server) { gpr_ref(&server->internal_refcount); } static void server_unref(grpc_server *server) { if (gpr_unref(&server->internal_refcount)) { grpc_channel_args_destroy(server->channel_args); gpr_mu_destroy(&server->mu); gpr_free(server->channel_filters); gpr_free(server->requested_calls); gpr_free(server); } } static int is_channel_orphaned(channel_data *chand) { return chand->next == chand; } static void orphan_channel(channel_data *chand) { chand->next->prev = chand->prev; chand->prev->next = chand->next; chand->next = chand->prev = chand; } static void finish_destroy_channel(void *cd, int success) { channel_data *chand = cd; grpc_server *server = chand->server; /*gpr_log(GPR_INFO, "destroy channel %p", chand->channel);*/ grpc_channel_destroy(chand->channel); server_unref(server); } static void destroy_channel(channel_data *chand) { if (is_channel_orphaned(chand)) return; GPR_ASSERT(chand->server != NULL); orphan_channel(chand); server_ref(chand->server); grpc_iomgr_add_callback(finish_destroy_channel, chand); } static void start_new_rpc(grpc_call_element *elem) { channel_data *chand = elem->channel_data; call_data *calld = elem->call_data; grpc_server *server = chand->server; gpr_mu_lock(&server->mu); if (server->requested_call_count > 0) { requested_call rc = server->requested_calls[--server->requested_call_count]; calld->state = ACTIVATED; gpr_mu_unlock(&server->mu); rc.cb(server, rc.cq, rc.call, rc.details, rc.initial_metadata, calld, rc.user_data); } else { calld->state = PENDING; call_list_join(server, calld, PENDING_START); gpr_mu_unlock(&server->mu); } } static void kill_zombie(void *elem, int success) { grpc_call_destroy(grpc_call_from_top_element(elem)); } static void stream_closed(grpc_call_element *elem) { call_data *calld = elem->call_data; channel_data *chand = elem->channel_data; gpr_mu_lock(&chand->server->mu); switch (calld->state) { case ACTIVATED: break; case PENDING: call_list_remove(chand->server, calld, PENDING_START); /* fallthrough intended */ case NOT_STARTED: calld->state = ZOMBIED; grpc_iomgr_add_callback(kill_zombie, elem); break; case ZOMBIED: break; } gpr_mu_unlock(&chand->server->mu); grpc_call_stream_closed(elem); } static void read_closed(grpc_call_element *elem) { call_data *calld = elem->call_data; channel_data *chand = elem->channel_data; gpr_mu_lock(&chand->server->mu); switch (calld->state) { case ACTIVATED: case PENDING: grpc_call_read_closed(elem); break; case NOT_STARTED: calld->state = ZOMBIED; grpc_iomgr_add_callback(kill_zombie, elem); break; case ZOMBIED: break; } gpr_mu_unlock(&chand->server->mu); } static void call_op(grpc_call_element *elem, grpc_call_element *from_elemn, grpc_call_op *op) { channel_data *chand = elem->channel_data; call_data *calld = elem->call_data; grpc_mdelem *md; GRPC_CALL_LOG_OP(GPR_INFO, elem, op); switch (op->type) { case GRPC_RECV_METADATA: md = op->data.metadata; if (md->key == chand->path_key) { calld->path = grpc_mdstr_ref(md->value); grpc_mdelem_unref(md); } else if (md->key == chand->authority_key) { calld->host = grpc_mdstr_ref(md->value); grpc_mdelem_unref(md); } else { grpc_call_recv_metadata(elem, md); } break; case GRPC_RECV_END_OF_INITIAL_METADATA: start_new_rpc(elem); grpc_call_initial_metadata_complete(elem); break; case GRPC_RECV_MESSAGE: grpc_call_recv_message(elem, op->data.message); op->done_cb(op->user_data, GRPC_OP_OK); break; case GRPC_RECV_HALF_CLOSE: read_closed(elem); break; case GRPC_RECV_FINISH: stream_closed(elem); break; case GRPC_RECV_DEADLINE: grpc_call_set_deadline(elem, op->data.deadline); ((call_data *)elem->call_data)->deadline = op->data.deadline; break; default: GPR_ASSERT(op->dir == GRPC_CALL_DOWN); grpc_call_next_op(elem, op); break; } } static void channel_op(grpc_channel_element *elem, grpc_channel_element *from_elem, grpc_channel_op *op) { channel_data *chand = elem->channel_data; grpc_server *server = chand->server; switch (op->type) { case GRPC_ACCEPT_CALL: /* create a call */ grpc_call_create(chand->channel, NULL, op->data.accept_call.transport_server_data); break; case GRPC_TRANSPORT_CLOSED: /* if the transport is closed for a server channel, we destroy the channel */ gpr_mu_lock(&server->mu); server_ref(server); destroy_channel(chand); gpr_mu_unlock(&server->mu); server_unref(server); break; case GRPC_TRANSPORT_GOAWAY: gpr_slice_unref(op->data.goaway.message); break; default: GPR_ASSERT(op->dir == GRPC_CALL_DOWN); grpc_channel_next_op(elem, op); break; } } static void finish_shutdown_channel(void *cd, int success) { channel_data *chand = cd; grpc_channel_op op; op.type = GRPC_CHANNEL_DISCONNECT; op.dir = GRPC_CALL_DOWN; channel_op(grpc_channel_stack_element( grpc_channel_get_channel_stack(chand->channel), 0), NULL, &op); grpc_channel_internal_unref(chand->channel); } static void shutdown_channel(channel_data *chand) { grpc_channel_internal_ref(chand->channel); grpc_iomgr_add_callback(finish_shutdown_channel, chand); } static void init_call_elem(grpc_call_element *elem, const void *server_transport_data) { call_data *calld = elem->call_data; channel_data *chand = elem->channel_data; memset(calld, 0, sizeof(call_data)); calld->deadline = gpr_inf_future; calld->call = grpc_call_from_top_element(elem); gpr_mu_lock(&chand->server->mu); call_list_join(chand->server, calld, ALL_CALLS); gpr_mu_unlock(&chand->server->mu); server_ref(chand->server); } static void destroy_call_elem(grpc_call_element *elem) { channel_data *chand = elem->channel_data; call_data *calld = elem->call_data; int i; gpr_mu_lock(&chand->server->mu); for (i = 0; i < CALL_LIST_COUNT; i++) { call_list_remove(chand->server, elem->call_data, i); } if (chand->server->shutdown && chand->server->have_shutdown_tag && chand->server->lists[ALL_CALLS] == NULL) { grpc_cq_end_server_shutdown(chand->server->cq, chand->server->shutdown_tag); } gpr_mu_unlock(&chand->server->mu); if (calld->host) { grpc_mdstr_unref(calld->host); } if (calld->path) { grpc_mdstr_unref(calld->path); } if (calld->legacy) { gpr_free(calld->legacy->initial_metadata->metadata); gpr_free(calld->legacy->initial_metadata); gpr_free(calld->legacy); } server_unref(chand->server); } static void init_channel_elem(grpc_channel_element *elem, const grpc_channel_args *args, grpc_mdctx *metadata_context, int is_first, int is_last) { channel_data *chand = elem->channel_data; GPR_ASSERT(is_first); GPR_ASSERT(!is_last); chand->server = NULL; chand->channel = NULL; chand->path_key = grpc_mdstr_from_string(metadata_context, ":path"); chand->authority_key = grpc_mdstr_from_string(metadata_context, ":authority"); chand->next = chand->prev = chand; } static void destroy_channel_elem(grpc_channel_element *elem) { channel_data *chand = elem->channel_data; if (chand->server) { gpr_mu_lock(&chand->server->mu); chand->next->prev = chand->prev; chand->prev->next = chand->next; chand->next = chand->prev = chand; gpr_mu_unlock(&chand->server->mu); grpc_mdstr_unref(chand->path_key); grpc_mdstr_unref(chand->authority_key); server_unref(chand->server); } } static const grpc_channel_filter server_surface_filter = { call_op, channel_op, sizeof(call_data), init_call_elem, destroy_call_elem, sizeof(channel_data), init_channel_elem, destroy_channel_elem, "server", }; grpc_server *grpc_server_create_from_filters(grpc_completion_queue *cq, grpc_channel_filter **filters, size_t filter_count, const grpc_channel_args *args) { size_t i; int census_enabled = grpc_channel_args_is_census_enabled(args); grpc_server *server = gpr_malloc(sizeof(grpc_server)); memset(server, 0, sizeof(grpc_server)); gpr_mu_init(&server->mu); server->cq = cq; /* decremented by grpc_server_destroy */ gpr_ref_init(&server->internal_refcount, 1); server->root_channel_data.next = server->root_channel_data.prev = &server->root_channel_data; /* Server filter stack is: server_surface_filter - for making surface API calls grpc_server_census_filter (optional) - for stats collection and tracing {passed in filter stack} grpc_connected_channel_filter - for interfacing with transports */ server->channel_filter_count = filter_count + 1 + census_enabled; server->channel_filters = gpr_malloc(server->channel_filter_count * sizeof(grpc_channel_filter *)); server->channel_filters[0] = &server_surface_filter; if (census_enabled) { server->channel_filters[1] = &grpc_server_census_filter; } for (i = 0; i < filter_count; i++) { server->channel_filters[i + 1 + census_enabled] = filters[i]; } server->channel_args = grpc_channel_args_copy(args); return server; } void grpc_server_start(grpc_server *server) { listener *l; for (l = server->listeners; l; l = l->next) { l->start(server, l->arg, grpc_cq_pollset(server->cq)); } } grpc_transport_setup_result grpc_server_setup_transport( grpc_server *s, grpc_transport *transport, grpc_channel_filter const **extra_filters, size_t num_extra_filters, grpc_mdctx *mdctx) { size_t num_filters = s->channel_filter_count + num_extra_filters + 1; grpc_channel_filter const **filters = gpr_malloc(sizeof(grpc_channel_filter *) * num_filters); size_t i; grpc_channel *channel; channel_data *chand; for (i = 0; i < s->channel_filter_count; i++) { filters[i] = s->channel_filters[i]; } for (; i < s->channel_filter_count + num_extra_filters; i++) { filters[i] = extra_filters[i - s->channel_filter_count]; } filters[i] = &grpc_connected_channel_filter; grpc_transport_add_to_pollset(transport, grpc_cq_pollset(s->cq)); channel = grpc_channel_create_from_filters(filters, num_filters, s->channel_args, mdctx, 0); chand = (channel_data *)grpc_channel_stack_element( grpc_channel_get_channel_stack(channel), 0)->channel_data; chand->server = s; server_ref(s); chand->channel = channel; gpr_mu_lock(&s->mu); chand->next = &s->root_channel_data; chand->prev = chand->next->prev; chand->next->prev = chand->prev->next = chand; gpr_mu_unlock(&s->mu); gpr_free(filters); return grpc_connected_channel_bind_transport( grpc_channel_get_channel_stack(channel), transport); } void shutdown_internal(grpc_server *server, gpr_uint8 have_shutdown_tag, void *shutdown_tag) { listener *l; requested_call *requested_calls; size_t requested_call_count; channel_data **channels; channel_data *c; size_t nchannels; size_t i; grpc_channel_op op; grpc_channel_element *elem; /* lock, and gather up some stuff to do */ gpr_mu_lock(&server->mu); if (server->shutdown) { gpr_mu_unlock(&server->mu); return; } nchannels = 0; for (c = server->root_channel_data.next; c != &server->root_channel_data; c = c->next) { nchannels++; } channels = gpr_malloc(sizeof(channel_data *) * nchannels); i = 0; for (c = server->root_channel_data.next; c != &server->root_channel_data; c = c->next) { grpc_channel_internal_ref(c->channel); channels[i] = c; i++; } requested_calls = server->requested_calls; requested_call_count = server->requested_call_count; server->requested_calls = NULL; server->requested_call_count = 0; server->shutdown = 1; server->have_shutdown_tag = have_shutdown_tag; server->shutdown_tag = shutdown_tag; if (have_shutdown_tag) { grpc_cq_begin_op(server->cq, NULL, GRPC_SERVER_SHUTDOWN); if (server->lists[ALL_CALLS] == NULL) { grpc_cq_end_server_shutdown(server->cq, shutdown_tag); } } gpr_mu_unlock(&server->mu); for (i = 0; i < nchannels; i++) { c = channels[i]; elem = grpc_channel_stack_element( grpc_channel_get_channel_stack(c->channel), 0); op.type = GRPC_CHANNEL_GOAWAY; op.dir = GRPC_CALL_DOWN; op.data.goaway.status = GRPC_STATUS_OK; op.data.goaway.message = gpr_slice_from_copied_string("Server shutdown"); elem->filter->channel_op(elem, NULL, &op); grpc_channel_internal_unref(c->channel); } gpr_free(channels); /* terminate all the requested calls */ for (i = 0; i < requested_call_count; i++) { requested_calls[i].cb(server, requested_calls[i].cq, requested_calls[i].call, requested_calls[i].details, requested_calls[i].initial_metadata, NULL, requested_calls[i].user_data); } gpr_free(requested_calls); /* Shutdown listeners */ for (l = server->listeners; l; l = l->next) { l->destroy(server, l->arg); } while (server->listeners) { l = server->listeners; server->listeners = l->next; gpr_free(l); } } void grpc_server_shutdown(grpc_server *server) { shutdown_internal(server, 0, NULL); } void grpc_server_shutdown_and_notify(grpc_server *server, void *tag) { shutdown_internal(server, 1, tag); } void grpc_server_destroy(grpc_server *server) { channel_data *c; gpr_mu_lock(&server->mu); for (c = server->root_channel_data.next; c != &server->root_channel_data; c = c->next) { shutdown_channel(c); } gpr_mu_unlock(&server->mu); server_unref(server); } void grpc_server_add_listener(grpc_server *server, void *arg, void (*start)(grpc_server *server, void *arg, grpc_pollset *pollset), void (*destroy)(grpc_server *server, void *arg)) { listener *l = gpr_malloc(sizeof(listener)); l->arg = arg; l->start = start; l->destroy = destroy; l->next = server->listeners; server->listeners = l; } static grpc_call_error queue_call_request(grpc_server *server, grpc_completion_queue *cq, grpc_call **call, grpc_call_details *details, grpc_metadata_array *initial_metadata, new_call_cb cb, void *user_data) { call_data *calld; requested_call *rc; gpr_mu_lock(&server->mu); if (server->shutdown) { gpr_mu_unlock(&server->mu); cb(server, cq, call, details, initial_metadata, NULL, user_data); return GRPC_CALL_OK; } calld = call_list_remove_head(server, PENDING_START); if (calld) { GPR_ASSERT(calld->state == PENDING); calld->state = ACTIVATED; gpr_mu_unlock(&server->mu); cb(server, cq, call, details, initial_metadata, calld, user_data); return GRPC_CALL_OK; } else { if (server->requested_call_count == server->requested_call_capacity) { server->requested_call_capacity = GPR_MAX(server->requested_call_capacity + 8, server->requested_call_capacity * 2); server->requested_calls = gpr_realloc(server->requested_calls, sizeof(requested_call) * server->requested_call_capacity); } rc = &server->requested_calls[server->requested_call_count++]; rc->cb = cb; rc->cq = cq; rc->call = call; rc->details = details; rc->user_data = user_data; rc->initial_metadata = initial_metadata; gpr_mu_unlock(&server->mu); return GRPC_CALL_OK; } } static void cpstr(char **dest, size_t *capacity, grpc_mdstr *value) { gpr_slice slice = value->slice; size_t len = GPR_SLICE_LENGTH(slice); if (len + 1 > *capacity) { *capacity = GPR_MAX(len + 1, *capacity * 2); *dest = gpr_realloc(*dest, *capacity); } memcpy(*dest, grpc_mdstr_as_c_string(value), len + 1); } static void publish_request(grpc_call *call, grpc_op_error status, void *tag) { grpc_call_element *elem = grpc_call_stack_element(grpc_call_get_call_stack(call), 0); call_data *calld = elem->call_data; channel_data *chand = elem->channel_data; grpc_server *server = chand->server; if (status == GRPC_OP_OK) { cpstr(&calld->details->host, &calld->details->host_capacity, calld->host); cpstr(&calld->details->method, &calld->details->method_capacity, calld->path); calld->details->deadline = calld->deadline; grpc_cq_end_op_complete(server->cq, tag, call, do_nothing, NULL, GRPC_OP_OK); } else { abort(); } } static void begin_request(grpc_server *server, grpc_completion_queue *cq, grpc_call **call, grpc_call_details *details, grpc_metadata_array *initial_metadata, call_data *calld, void *tag) { grpc_ioreq req; if (!calld) { *call = NULL; initial_metadata->count = 0; grpc_cq_end_op_complete(cq, tag, NULL, do_nothing, NULL, GRPC_OP_ERROR); return; } calld->details = details; grpc_call_set_completion_queue(calld->call, cq); *call = calld->call; req.op = GRPC_IOREQ_RECV_INITIAL_METADATA; req.data.recv_metadata = initial_metadata; grpc_call_internal_ref(calld->call); grpc_call_start_ioreq_and_call_back(calld->call, &req, 1, publish_request, tag); } grpc_call_error grpc_server_request_call(grpc_server *server, grpc_call **call, grpc_call_details *details, grpc_metadata_array *initial_metadata, grpc_completion_queue *cq, void *tag) { grpc_cq_begin_op(cq, NULL, GRPC_OP_COMPLETE); return queue_call_request(server, cq, call, details, initial_metadata, begin_request, tag); } static void publish_legacy_request(grpc_call *call, grpc_op_error status, void *tag) { grpc_call_element *elem = grpc_call_stack_element(grpc_call_get_call_stack(call), 0); call_data *calld = elem->call_data; channel_data *chand = elem->channel_data; grpc_server *server = chand->server; if (status == GRPC_OP_OK) { grpc_cq_end_new_rpc(server->cq, tag, call, do_nothing, NULL, grpc_mdstr_as_c_string(calld->path), grpc_mdstr_as_c_string(calld->host), calld->deadline, calld->legacy->initial_metadata->count, calld->legacy->initial_metadata->metadata); } else { abort(); } } static void begin_legacy_request(grpc_server *server, grpc_completion_queue *cq, grpc_call **call, grpc_call_details *details, grpc_metadata_array *initial_metadata, call_data *calld, void *tag) { grpc_ioreq req; GPR_ASSERT(call == NULL); GPR_ASSERT(details == NULL); if (!calld) { gpr_free(initial_metadata); grpc_cq_end_new_rpc(cq, tag, NULL, do_nothing, NULL, NULL, NULL, gpr_inf_past, 0, NULL); return; } req.op = GRPC_IOREQ_RECV_INITIAL_METADATA; req.data.recv_metadata = initial_metadata; calld->legacy = gpr_malloc(sizeof(legacy_data)); memset(calld->legacy, 0, sizeof(legacy_data)); calld->legacy->initial_metadata = initial_metadata; grpc_call_internal_ref(calld->call); grpc_call_start_ioreq_and_call_back(calld->call, &req, 1, publish_legacy_request, tag); } grpc_call_error grpc_server_request_call_old(grpc_server *server, void *tag_new) { grpc_metadata_array *client_metadata = gpr_malloc(sizeof(grpc_metadata_array)); memset(client_metadata, 0, sizeof(*client_metadata)); grpc_cq_begin_op(server->cq, NULL, GRPC_SERVER_RPC_NEW); return queue_call_request(server, server->cq, NULL, NULL, client_metadata, begin_legacy_request, tag_new); } const grpc_channel_args *grpc_server_get_channel_args(grpc_server *server) { return server->channel_args; }