27#define _POSIX_C_SOURCE 200809L
30#include <cjson/cJSON.h>
40#define GRPC_CHANNEL_MAX_ADDRS 64
41#define GRPC_CHANNEL_DEFAULT_BUFFER_LIMIT (256u * 1024u)
42#define GRPC_CHANNEL_MAX_ATTEMPTS_CAP 5
45#define GRPC_CHANNEL_CONN_BACKOFF_INITIAL_MS 1000.0
46#define GRPC_CHANNEL_CONN_BACKOFF_MULTIPLIER 1.6
47#define GRPC_CHANNEL_CONN_BACKOFF_MAX_MS 120000.0
48#define GRPC_CHANNEL_CONN_BACKOFF_JITTER 0.2
106static uint64_t channel_now_ms(
void) {
123static uint64_t channel_rand(cwist_grpc_channel *ch) {
135static double channel_jitter(cwist_grpc_channel *ch) {
137 double u = (
double)(channel_rand(ch) >> 11) * (1.0 / 9007199254740992.0);
139 return 0.8 + 0.4 * u;
150static int channel_throttle_allows(cwist_grpc_channel *ch) {
151 if (!ch->has_throttle)
return 1;
153 int ok = ch->tokens > ch->throttle.max_tokens / 2.0;
159static void channel_throttle_failure(cwist_grpc_channel *ch) {
160 if (!ch->has_throttle)
return;
163 if (ch->tokens < 0) ch->tokens = 0;
170static void channel_throttle_success(cwist_grpc_channel *ch) {
171 if (!ch->has_throttle)
return;
173 ch->tokens += ch->throttle.token_ratio;
174 if (ch->tokens > ch->throttle.max_tokens) ch->tokens = ch->throttle.max_tokens;
182static void channel_account(cwist_grpc_channel *ch,
cwist_grpc_status_t status,
int has_policy,
185 channel_throttle_success(ch);
187 channel_throttle_failure(ch);
197static void channel_recycle_locked(cwist_grpc_channel *ch, cwist_grpc_subchannel *sub) {
216static int sub_ensure_connected(cwist_grpc_channel *ch, cwist_grpc_subchannel *sub) {
217 channel_recycle_locked(ch, sub);
218 if (sub->client)
return 0;
220 if (sub->next_connect_at &&
now < sub->next_connect_at)
return -1;
222 o.authority = ch->authority;
223 o.tls_server_name = ch->tls_name;
228 ((
double)(channel_rand(ch) >> 11) * (1.0 / 9007199254740992.0));
236 sub->client = client;
238 sub->next_connect_at = 0;
252 for (
size_t k = 0;
k < ch->nsubs;
k++) {
253 size_t i = (ch->rr_next +
k) % ch->nsubs;
256 ch->rr_next = (
i + 1) % ch->nsubs;
263 if (ch->pf_index < ch->nsubs) {
287 for (
size_t k = 0;
k < ch->nsubs;
k++) {
288 size_t i = (ch->rr_next +
k) % ch->nsubs;
290 if (
c->inflight)
continue;
291 if (sub_ensure_connected(ch,
c) == 0) {
292 ch->rr_next = (
i + 1) % ch->nsubs;
300 for (
size_t i = 0;
i < ch->nsubs;
i++) {
302 if (
c->inflight)
continue;
303 if (sub_ensure_connected(ch,
c) == 0) {
316static void channel_release(cwist_grpc_channel *ch, cwist_grpc_subchannel *sub) {
318 if (sub->inflight > 0) sub->inflight--;
323static void channel_recycle(cwist_grpc_channel *ch, cwist_grpc_subchannel *sub) {
325 channel_recycle_locked(ch, sub);
336static void channel_split_method(
const char *path,
const char **service,
size_t *
service_len,
337 const char **method) {
338 const char *
start = path;
358static const grpc_method_config *channel_find_mc(cwist_grpc_channel *ch,
const char *path) {
359 const char *service, *method;
361 channel_split_method(path, &service, &
service_len, &method);
363 for (
const grpc_method_config *
mc = ch->mcs;
mc;
mc =
mc->next) {
364 if (
mc->service[0] ==
'\0') {
374 if (method &&
strcmp(
mc->method, method) == 0)
return mc;
380static void channel_free_mcs(cwist_grpc_channel *ch) {
395static char *channel_strdup(
const char *s) {
397 size_t len =
strlen(s) + 1;
399 if (copy)
memcpy(copy, s, len);
419 cc->attempts = attempts;
420 cc->syn_status = status;
421 cc->syn_message = channel_strdup(message);
422 cc->acct_has_policy = has_policy;
433 cwist_grpc_call *call,
uint32_t attempts,
439 if (sub) channel_release(ch, sub);
444 cc->accounted = accounted;
472 int wfr = ch->wait_for_ready;
476 if (
mc->has_policy) policy = &
mc->policy;
477 if (
mc->has_wfr)
wfr =
mc->wait_for_ready;
489 if (max_attempts < 1) max_attempts = 1;
492 int has_policy = policy !=
NULL;
501 if (deadline &&
now >= deadline)
509 channel_sleep_ms(20);
514 if (
coded) channel_throttle_failure(ch);
516 channel_throttle_allows(ch)) {
525 channel_sleep_ms(delay);
531 if (
cc)
cc->accounted = 1;
539 now = channel_now_ms();
547 channel_recycle(ch, sub);
548 channel_release(ch, sub);
573 channel_recycle(ch, sub);
574 channel_release(ch, sub);
591 !channel_throttle_allows(ch)) {
607 channel_release(ch, sub);
612 channel_sleep_ms(delay);
625 if (!
cc || !out)
return -1;
626 if (!
cc->call)
return 0;
638 const char **message) {
640 if (message) *message =
NULL;
647 status =
cc->syn_status;
648 if (message) *message =
cc->syn_message;
650 if (!
cc->accounted) {
651 channel_account(
cc->ch, status,
cc->acct_has_policy,
cc->acct_mask);
670 if (!
cc->accounted) {
672 channel_account(
cc->ch, status,
cc->acct_has_policy,
cc->acct_mask);
677 channel_recycle(
cc->ch,
cc->sub);
678 channel_release(
cc->ch,
cc->sub);
686 return cc ?
cc->attempts : 0;
706 if (status_message) *status_message =
NULL;
722 if (status_message &&
sm) *status_message = channel_strdup(
sm);
750static int channel_split_host_port(
const char *s,
size_t len,
char *host,
size_t host_cap,
753 if (!s || !len)
return -1;
756 if (!
close)
return -1;
763 if (
close[1] !=
':' || used + 1 == len)
return -1;
772 for (
size_t i = 0;
i < len;
i++)
802static int channel_add_addr(cwist_grpc_channel *ch,
const char *host,
uint16_t port) {
803 for (
size_t i = 0;
i < ch->nsubs;
i++)
804 if (ch->subs[
i].port == port &&
strcmp(ch->subs[
i].host, host) == 0)
808 memset(sub, 0,
sizeof(*sub));
809 sub->host = channel_strdup(host);
810 if (!sub->host)
return -1;
825static int channel_resolve_dns(cwist_grpc_channel *ch,
const char *name) {
828 if (channel_split_host_port(name,
strlen(name), host,
sizeof(host), &port) != 0)
return -1;
843 if (channel_add_addr(ch,
numeric, port) == 0)
rc = 0;
846 if (
rc == 0 && !ch->tls_name) ch->tls_name = channel_strdup(host);
847 if (
rc == 0 && !ch->authority) {
849 snprintf(buf,
sizeof(buf),
"%s:%u", host, (
unsigned)port);
850 ch->authority = channel_strdup(buf);
864static int channel_resolve_literal(cwist_grpc_channel *ch,
const char *
list,
int v6) {
865 char *copy = channel_strdup(
list);
866 if (!copy)
return -1;
872 if (channel_split_host_port(
tok,
strlen(
tok), host,
sizeof(host), &port) != 0)
continue;
876 if (channel_add_addr(ch, host, port) == 0)
rc = 0;
879 if (
rc == 0 && !ch->tls_name && ch->nsubs) ch->tls_name = channel_strdup(ch->subs[0].host);
880 if (
rc == 0 && !ch->authority && ch->nsubs) {
882 snprintf(buf,
sizeof(buf),
"%s:%u", ch->subs[0].host, (
unsigned)ch->subs[0].port);
883 ch->authority = channel_strdup(buf);
905 if (!ch)
return NULL;
906 memset(ch, 0,
sizeof(*ch));
921 ch->has_throttle = 1;
922 ch->tokens = ch->throttle.max_tokens;
928 ch->copt.use_tls = options->
use_tls;
934 if (!ch->rng) ch->rng = 0x9e3779b97f4a7c15ULL;
938 const char *name =
target + 4;
939 if (name[0] ==
'/' && name[1] ==
'/') {
947 rc = channel_resolve_dns(ch, name);
949 rc = channel_resolve_literal(ch,
target + 5, 0);
951 rc = channel_resolve_literal(ch,
target + 5, 1);
956 rc = channel_resolve_dns(ch,
target);
958 if (
rc != 0 || ch->nsubs == 0)
goto fail;
959 if (!ch->authority)
goto fail;
965 for (
size_t i = 0;
i < ch->nsubs;
i++) (
void)sub_ensure_connected(ch, &ch->subs[
i]);
982 for (
size_t i = 0;
i < ch->nsubs;
i++) {
987 channel_free_mcs(ch);
1006 for (
size_t i = 0;
i < ch->nsubs;
i++) {
1012 if (!s->next_connect_at ||
now >= s->next_connect_at)
any_dialable = 1;
1024static const char *
const grpc_status_names[] = {
1025 "OK",
"CANCELLED",
"UNKNOWN",
"INVALID_ARGUMENT",
"DEADLINE_EXCEEDED",
1026 "NOT_FOUND",
"ALREADY_EXISTS",
"PERMISSION_DENIED",
"RESOURCE_EXHAUSTED",
"FAILED_PRECONDITION",
1027 "ABORTED",
"OUT_OF_RANGE",
"UNIMPLEMENTED",
"INTERNAL",
"UNAVAILABLE",
1028 "DATA_LOSS",
"UNAUTHENTICATED"};
1044 for (
int i = 0;
i <= 16;
i++) {
1064 const char *s =
item->valuestring;
1066 if (len < 2 || s[len - 1] !=
's')
return -1;
1069 if (
end != s + len - 1 ||
v <= 0)
return -1;
1092 if (!
cJSON_IsNumber(max_attempts) || max_attempts->valuedouble < 2 ||
1093 max_attempts->valuedouble != (
double)(
int)max_attempts->valuedouble)
1097 grpc_json_duration_ms(max, &
max_ms) != 0)
1105 if (grpc_status_from_json(
code, &
bit) != 0)
return -1;
1119static void grpc_json_free_mc_list(grpc_method_config *
list) {
1141 if (!ch || !json)
return -1;
1181 }
else if (
strcasecmp(
lbp->valuestring,
"PICK_FIRST") == 0) {
1192 if (!
cJSON_IsNumber(max_tokens) || max_tokens->valuedouble <= 0 ||
1195 throttle.
max_tokens = max_tokens->valuedouble;
1216 if (grpc_json_retry_policy(
rp, &policy) != 0)
goto invalid;
1222 int has_wfr = 0,
wfr = 0;
1229 int has_timeout = 0;
1233 if (grpc_json_duration_ms(
timeout, &timeout_ms) != 0)
goto invalid;
1249 mc->service = channel_strdup(service->valuestring);
1250 mc->method = method ? channel_strdup(method->valuestring) :
NULL;
1251 if (!
mc->service || (method && !
mc->method)) {
1257 mc->has_policy = has_policy;
1258 mc->policy = policy;
1259 mc->has_wfr = has_wfr;
1260 mc->wait_for_ready =
wfr;
1261 mc->has_timeout = has_timeout;
1262 mc->timeout_ms = timeout_ms;
1272 channel_free_mcs(ch);
1277 for (
size_t i = 0;
i < ch->nsubs;
i++) (
void)sub_ensure_connected(ch, &ch->subs[
i]);
1282 int had = ch->has_throttle;
1283 ch->throttle = throttle;
1284 ch->has_throttle = 1;
1292 grpc_json_free_mc_list(mcs);
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
@ CWIST_GRPC_DEADLINE_EXCEEDED
enum cwist_grpc_status cwist_grpc_status_t
void cwist_grpc_channel_call_destroy(cwist_grpc_channel_call *cc)
Destroy a channel call and release its resources.
void cwist_grpc_channel_close(cwist_grpc_channel *ch)
Close a channel and free all its resources.
cwist_grpc_channel_state cwist_grpc_channel_get_state(cwist_grpc_channel *ch)
Aggregate connectivity state across subchannels (load-balancing.md).
cwist_grpc_channel * cwist_grpc_channel_connect(const char *target, const cwist_grpc_channel_options *options)
Create a channel for target and resolve its backend addresses.
uint32_t cwist_grpc_channel_call_attempts(const cwist_grpc_channel_call *cc)
Number of policy attempts a channel call used (transparent retries excluded).
struct cwist_grpc_subchannel cwist_grpc_subchannel
void cwist_grpc_channel_call_cancel(cwist_grpc_channel_call *cc)
Cancel the underlying attempt call, if it reached the transport.
#define GRPC_CHANNEL_DEFAULT_BUFFER_LIMIT
cwist_grpc_status_t cwist_grpc_channel_unary(cwist_grpc_channel *ch, const char *method, const void *request, size_t request_len, uint64_t timeout_ms, uint8_t **response, size_t *response_len, char **status_message)
Convenience blocking unary RPC: start, read one response, finish.
#define GRPC_CHANNEL_MAX_ATTEMPTS_CAP
#define GRPC_CHANNEL_CONN_BACKOFF_MAX_MS
struct grpc_method_config grpc_method_config
#define GRPC_CHANNEL_CONN_BACKOFF_INITIAL_MS
cwist_grpc_channel_call * cwist_grpc_channel_call_start(cwist_grpc_channel *ch, const char *method, const void *request, size_t request_len, uint64_t timeout_ms)
Start a unary RPC through the channel, running the gRFC A6 retry engine transparently across attempts...
#define GRPC_CHANNEL_CONN_BACKOFF_MULTIPLIER
cwist_grpc_status_t cwist_grpc_channel_call_finish(cwist_grpc_channel_call *cc, const char **message)
Finish a channel call and return its final gRPC status.
#define GRPC_CHANNEL_MAX_ADDRS
int cwist_grpc_channel_call_recv(cwist_grpc_channel_call *cc, cwist_grpc_message *out)
Receive the next response message of a channel call.
#define GRPC_CHANNEL_CONN_BACKOFF_JITTER
int cwist_grpc_channel_apply_service_config_json(cwist_grpc_channel *ch, const char *json)
Apply a JSON service config (doc/service_config.md subset, gRFC A6).
gRPC client channel: name resolution, client-side load balancing, and gRFC A6 retry policy on top of ...
@ CWIST_GRPC_CHANNEL_IDLE
@ CWIST_GRPC_CHANNEL_SHUTDOWN
@ CWIST_GRPC_CHANNEL_READY
@ CWIST_GRPC_CHANNEL_TRANSIENT_FAILURE
#define CWIST_GRPC_STATUS_BIT(code)
struct cwist_grpc_channel_call cwist_grpc_channel_call
struct cwist_grpc_channel cwist_grpc_channel
@ CWIST_GRPC_LB_ROUND_ROBIN
@ CWIST_GRPC_LB_PICK_FIRST
struct cwist_grpc_client cwist_grpc_client
void cwist_grpc_client_close(cwist_grpc_client *client)
Close the client: send GOAWAY, tear down TLS and the socket, free all state.
cwist_grpc_client * cwist_grpc_client_connect(const char *host, uint16_t port, const cwist_grpc_client_options *options)
Connect to a gRPC server and complete the HTTP/2 handshake.
int cwist_grpc_call_recv(cwist_grpc_call *call, cwist_grpc_message *out)
Receive the next response message of a call.
int cwist_grpc_call_await_headers(cwist_grpc_call *call)
Block until response headers arrive or the stream ends.
int cwist_grpc_client_dead(cwist_grpc_client *client)
Whether the client connection is unusable.
int cwist_grpc_call_refused(const cwist_grpc_call *call)
Whether the call failed before commit in a retryable way (gRFC A6 case 3: REFUSED_STREAM before heade...
void cwist_grpc_call_cancel(cwist_grpc_call *call)
Cancel a call: send RST_STREAM(CANCEL), mark it failed with CWIST_GRPC_CANCELLED, and end its stream.
int cwist_grpc_call_retry_pushback_ms(const cwist_grpc_call *call, int32_t *out_ms)
Report the server-provided retry pushback delay, if any.
void cwist_grpc_call_destroy(cwist_grpc_call *call)
Destroy a call: cancel it if still open, release it as the client's active call, and free all queued ...
struct cwist_grpc_call cwist_grpc_call
cwist_grpc_status_t cwist_grpc_call_finish(cwist_grpc_call *call, const char **message)
Drain the call to completion and return its final status.
cwist_grpc_status_t cwist_grpc_call_status(const cwist_grpc_call *call)
Current gRPC status code of a call.
cwist_grpc_call * cwist_grpc_call_start_ex(cwist_grpc_client *client, const char *method, const void *request, size_t request_len, uint64_t timeout_ms, uint32_t previous_attempts)
Start a unary call, with a gRFC A6 retry-attempt marker.
uint64_t connect_timeout_ms
const cwist_grpc_retry_throttle * retry_throttle
uint64_t per_rpc_buffer_limit
cwist_grpc_lb_policy lb_policy
const cwist_grpc_retry_policy * retry_policy
uint32_t retryable_status_mask
uint64_t initial_backoff_ms
double backoff_multiplier