void cproxy_process_a2a_downstream(conn *c, char *line) { assert(c != NULL); assert(c->next == NULL); assert(c->extra != NULL); assert(c->cmd == -1); assert(c->item == NULL); assert(line != NULL); assert(line == c->rcurr); assert(IS_ASCII(c->protocol)); assert(IS_PROXY(c->protocol)); if (settings.verbose > 1) fprintf(stderr, "<%d cproxy_process_a2a_downstream %s\n", c->sfd, line); downstream *d = c->extra; assert(d != NULL); assert(d->ptd != NULL); assert(d->ptd->proxy != NULL); if (strncmp(line, "VALUE ", 6) == 0) { token_t tokens[MAX_TOKENS]; size_t ntokens; unsigned int flags; int clen = 0; int vlen; uint64_t cas = CPROXY_NOT_CAS; ntokens = scan_tokens(line, tokens, MAX_TOKENS, &clen); if (ntokens >= 5 && // Accounts for extra termimation token. ntokens <= 6 && tokens[KEY_TOKEN].length <= KEY_MAX_LENGTH && safe_strtoul(tokens[2].value, (uint32_t *) &flags) && safe_strtoul(tokens[3].value, (uint32_t *) &vlen)) { char *key = tokens[KEY_TOKEN].value; size_t nkey = tokens[KEY_TOKEN].length; item *it = item_alloc(key, nkey, flags, 0, vlen + 2); if (it != NULL) { if (ntokens == 5 || safe_strtoull(tokens[4].value, &cas)) { ITEM_set_cas(it, cas); c->item = it; c->ritem = ITEM_data(it); c->rlbytes = it->nbytes; c->cmd = -1; conn_set_state(c, conn_nread); return; // Success. } else { if (settings.verbose > 1) fprintf(stderr, "cproxy could not parse cas\n"); } } else { if (settings.verbose > 1) fprintf(stderr, "cproxy could not item_alloc size %u\n", vlen + 2); } if (it != NULL) item_remove(it); it = NULL; c->sbytes = vlen + 2; // Number of bytes to swallow. conn_set_state(c, conn_swallow); // Note, eventually, we'll see an END later. } else { // We don't know how much to swallow, so close the downstream. // The conn_closing should release the downstream, // which should write a suffix/error to the upstream. // conn_set_state(c, conn_closing); } } else if (strncmp(line, "END", 3) == 0) { conn_set_state(c, conn_pause); } else if (strncmp(line, "OK", 2) == 0) { conn_set_state(c, conn_pause); // TODO: Handle flush_all's expiration parameter against // the front_cache. // // TODO: We flush the front_cache too often, inefficiently // on every downstream flush_all OK response, rather than // on just the last flush_all OK response. // conn *uc = d->upstream_conn; if (uc != NULL && uc->cmd_curr == PROTOCOL_BINARY_CMD_FLUSH) { mcache_flush_all(&d->ptd->proxy->front_cache, 0); } } else if (strncmp(line, "STAT ", 5) == 0 || strncmp(line, "ITEM ", 5) == 0 || strncmp(line, "PREFIX ", 7) == 0) { assert(d->merger != NULL); conn *uc = d->upstream_conn; if (uc != NULL) { assert(uc->next == NULL); if (protocol_stats_merge_line(d->merger, line) == false) { // Forward the line as-is if we couldn't merge it. // int nline = strlen(line); item *it = item_alloc("s", 1, 0, 0, nline + 2); if (it != NULL) { strncpy(ITEM_data(it), line, nline); strncpy(ITEM_data(it) + nline, "\r\n", 2); if (add_conn_item(uc, it)) { add_iov(uc, ITEM_data(it), nline + 2); it = NULL; } if (it != NULL) item_remove(it); } } } conn_set_state(c, conn_new_cmd); } else { conn_set_state(c, conn_pause); // The upstream conn might be NULL when closed already // or while handling a noreply. // conn *uc = d->upstream_conn; if (uc != NULL) { assert(uc->next == NULL); out_string(uc, line); if (!update_event(uc, EV_WRITE | EV_PERSIST)) { if (settings.verbose > 1) fprintf(stderr, "Can't update upstream write event\n"); d->ptd->stats.stats.err_oom++; cproxy_close_conn(uc); } cproxy_del_front_cache_key_ascii_response(d, line, uc->cmd_start); } } }
/* Used for broadcast commands, like flush_all or stats. */ bool cproxy_broadcast_a2a_downstream(downstream *d, char *command, conn *uc, char *suffix) { assert(d != NULL); assert(d->ptd != NULL); assert(d->ptd->proxy != NULL); assert(d->downstream_conns != NULL); assert(command != NULL); assert(uc != NULL); assert(uc->next == NULL); assert(uc->item == NULL); int nwrite = 0; int nconns = memcached_server_count(&d->mst); for (int i = 0; i < nconns; i++) { conn *c = d->downstream_conns[i]; if (c != NULL) { if (cproxy_prep_conn_for_write(c)) { assert(c->state == conn_pause); out_string(c, command); if (update_event(c, EV_WRITE | EV_PERSIST)) { nwrite++; if (uc->noreply) { c->write_and_go = conn_pause; } } else { if (settings.verbose > 1) fprintf(stderr, "Update cproxy write event failed\n"); d->ptd->stats.stats.err_oom++; cproxy_close_conn(c); } } else { d->ptd->stats.stats.err_downstream_write_prep++; cproxy_close_conn(c); } } } if (settings.verbose > 1) fprintf(stderr, "forward multiget nwrite %d out of %d\n", nwrite, nconns); d->downstream_used_start = nwrite; d->downstream_used = nwrite; if (cproxy_dettach_if_noreply(d, uc) == false) { d->upstream_suffix = suffix; cproxy_start_downstream_timeout(d, NULL); } else { // TODO: Handle flush_all's expiration parameter against // the front_cache. // if (strncmp(command, "flush_all", 9) == 0) { mcache_flush_all(&d->ptd->proxy->front_cache, 0); } } return nwrite > 0; }
/* Used for broadcast commands, like flush_all or stats. */ bool cproxy_broadcast_a2a_downstream(downstream *d, char *command, conn *uc, char *suffix) { assert(d != NULL); assert(d->ptd != NULL); assert(d->ptd->proxy != NULL); assert(d->downstream_conns != NULL); assert(d->downstream_used_start == 0); assert(d->downstream_used == 0); assert(command != NULL); assert(uc != NULL); assert(uc->next == NULL); assert(uc->item == NULL); int nwrite = 0; int nconns = mcs_server_count(&d->mst); for (int i = 0; i < nconns; i++) { conn *c = d->downstream_conns[i]; if (c != NULL && c != NULL_CONN) { if (cproxy_prep_conn_for_write(c)) { assert(c->state == conn_pause); out_string(c, command); if (update_event(c, EV_WRITE | EV_PERSIST)) { nwrite++; if (uc->noreply) { c->write_and_go = conn_pause; } } else { if (settings.verbose > 1) { moxi_log_write("Update cproxy write event failed\n"); } d->ptd->stats.stats.err_oom++; cproxy_close_conn(c); } } else { d->ptd->stats.stats.err_downstream_write_prep++; cproxy_close_conn(c); } } } if (settings.verbose > 1) { moxi_log_write("%d: a2a broadcast nwrite %d out of %d\n", uc->sfd, nwrite, nconns); } if (nwrite > 0) { d->downstream_used_start = nwrite; d->downstream_used = nwrite; if (cproxy_dettach_if_noreply(d, uc) == false) { d->upstream_suffix = suffix; d->upstream_suffix_len = 0; d->upstream_status = PROTOCOL_BINARY_RESPONSE_SUCCESS; d->upstream_retry = 0; d->target_host_ident = NULL; cproxy_start_downstream_timeout(d, NULL); } else { // TODO: Handle flush_all's expiration parameter against // the front_cache. // if (strncmp(command, "flush_all", 9) == 0) { mcache_flush_all(&d->ptd->proxy->front_cache, 0); } } return true; } return false; }