static void record_metrics(mc_PIPELINE *pipeline, mc_PACKET *req, packet_info *res) { lcb_t instance = pipeline->parent->cqdata; if (instance->histogram) { lcb_record_metrics( instance, gethrtime() - MCREQ_PKT_RDATA(req)->start, PACKET_OPCODE(res)); } }
/* * Drop all packets with sequence number less than specified. * * The packets are considered as stale and the caller will receive * appropriate error code in the operation callback. * * Returns 0 on success */ int lcb_server_purge_implicit_responses(lcb_server_t *c, lcb_uint32_t seqno, hrtime_t end, int all) { protocol_binary_request_header req; /** Instance level allocated buffers */ ringbuffer_t *cmdlog, *cookies; lcb_size_t nr = ringbuffer_peek(&c->cmd_log, req.bytes, sizeof(req)); /* There should at _LEAST_ be _ONE_ message in here if we're not * trying to purge _ALL_ of the messages in the queue */ if (all && nr == 0) { return 0; } /** * Reading the command log is not re-entrant safe, as an additional * command to the same server may result in the command log being modified. * To this end, we must first buffer all the commands in a separate * ringbuffer (or simple buffer) for that matter, and only *then* * invoke the callbacks */ lcb_assert(nr == sizeof(req)); if (req.request.opaque >= seqno) { return 0; } cmdlog = &c->instance->purged_buf; cookies = &c->instance->purged_cookies; ringbuffer_reset(cmdlog); ringbuffer_reset(cookies); /** * Move all the commands we want to purge into the relevant ("local") buffers. * We will later read from these local buffers */ while (req.request.opaque < seqno) { lcb_size_t packetsize = ntohl(req.request.bodylen) + (lcb_uint32_t)sizeof(req); ringbuffer_memcpy(cmdlog, &c->cmd_log, packetsize); ringbuffer_consumed(&c->cmd_log, packetsize); ringbuffer_memcpy(cookies, &c->output_cookies, sizeof(struct lcb_command_data_st)); ringbuffer_consumed(&c->output_cookies, sizeof(struct lcb_command_data_st)); nr = ringbuffer_peek(&c->cmd_log, req.bytes, sizeof(req.bytes)); if (!nr) { break; } lcb_assert(nr == sizeof(req)); } nr = ringbuffer_peek(cmdlog, req.bytes, sizeof(req)); lcb_assert(nr == sizeof(req)); if (!all) { lcb_assert(c->cmd_log.nbytes); } do { struct lcb_command_data_st ct; char *packet = cmdlog->read_head; lcb_size_t packetsize = ntohl(req.request.bodylen) + (lcb_uint32_t)sizeof(req); char *keyptr; union { lcb_get_resp_t get; lcb_store_resp_t store; lcb_remove_resp_t remove; lcb_touch_resp_t touch; lcb_unlock_resp_t unlock; lcb_arithmetic_resp_t arithmetic; lcb_observe_resp_t observe; } resp; nr = ringbuffer_read(cookies, &ct, sizeof(ct)); lcb_assert(nr == sizeof(ct)); if (c->instance->histogram) { lcb_record_metrics(c->instance, end - ct.start, req.request.opcode); } if (!ringbuffer_is_continous(cmdlog, RINGBUFFER_READ, packetsize)) { packet = malloc(packetsize); if (packet == NULL) { lcb_error_handler(c->instance, LCB_CLIENT_ENOMEM, NULL); return -1; } nr = ringbuffer_peek(cmdlog, packet, packetsize); if (nr != packetsize) { lcb_error_handler(c->instance, LCB_EINTERNAL, NULL); free(packet); return -1; } } switch (req.request.opcode) { case PROTOCOL_BINARY_CMD_GATQ: case PROTOCOL_BINARY_CMD_GETQ: keyptr = packet + sizeof(req) + req.request.extlen; setup_lcb_get_resp_t(&resp.get, keyptr, ntohs(req.request.keylen), NULL, 0, 0, 0, 0); TRACE_GET_END(req.request.opaque, ntohs(req.request.vbucket), req.request.opcode, LCB_KEY_ENOENT, &resp.get); c->instance->callbacks.get(c->instance, ct.cookie, LCB_KEY_ENOENT, &resp.get); break; case CMD_OBSERVE: lcb_failout_observe_request(c, &ct, packet, sizeof(req.bytes) + ntohl(req.request.bodylen), LCB_SERVER_BUG); break; case PROTOCOL_BINARY_CMD_NOOP: break; default: { char errinfo[128] = { '\0' }; snprintf(errinfo, 128, "Unknown implicit send message op=%0x", req.request.opcode); lcb_error_handler(c->instance, LCB_EINTERNAL, errinfo); return -1; } } if (packet != cmdlog->read_head) { free(packet); } ringbuffer_consumed(cmdlog, packetsize); nr = ringbuffer_peek(cmdlog, req.bytes, sizeof(req)); if (nr == 0) { return 0; } lcb_assert(nr == sizeof(req)); } while (1); /* CONSTCOND */ return 0; }
static void purge_single_server(lcb_server_t *server, lcb_error_t error, hrtime_t min_nonstale, hrtime_t *tmo_next) { protocol_binary_request_header req; struct lcb_command_data_st ct; lcb_size_t nr; char *packet; lcb_size_t packetsize; char *keyptr; ringbuffer_t rest; ringbuffer_t *stream = &server->cmd_log; ringbuffer_t *cookies; ringbuffer_t *mirror = NULL; /* mirror buffer should be purged with main stream */ lcb_connection_t conn = &server->connection; lcb_size_t send_size = 0; lcb_size_t stream_size = ringbuffer_get_nbytes(stream); hrtime_t now = gethrtime(); if (server->connection_ready) { cookies = &server->output_cookies; } else { cookies = &server->pending_cookies; mirror = &server->pending; } if (conn->output) { /* This will usually be false for v1 */ send_size = ringbuffer_get_nbytes(conn->output); } lcb_assert(ringbuffer_initialize(&rest, 1024)); do { int allocated = 0; lcb_uint32_t headersize; lcb_uint16_t nkey; nr = ringbuffer_peek(cookies, &ct, sizeof(ct)); if (nr != sizeof(ct)) { break; } nr = ringbuffer_peek(stream, req.bytes, sizeof(req)); if (nr != sizeof(req)) { break; } packetsize = (lcb_uint32_t)sizeof(req) + ntohl(req.request.bodylen); if (stream->nbytes < packetsize) { break; } if (min_nonstale && ct.start >= min_nonstale) { lcb_log(LOGARGS(server, INFO), "Still have %d ms remaining for command", (ct.start - min_nonstale) / 1000000); if (tmo_next) { *tmo_next = (ct.start - min_nonstale) + 1; } break; } lcb_log(LOGARGS(server, INFO), "Command with cookie=%p timed out from server %s:%s", ct.cookie, server->curhost.host, server->curhost.port); ringbuffer_consumed(cookies, sizeof(ct)); lcb_assert(nr == sizeof(req)); packet = stream->read_head; if (server->instance->histogram) { lcb_record_metrics(server->instance, now - ct.start, req.request.opcode); } if (server->connection_ready && stream_size > send_size && (stream_size - packetsize) < send_size) { /* Copy the rest of the current packet into the temporary stream */ /* I do believe I have some IOV functions to do that? */ lcb_size_t nbytes = packetsize - (stream_size - send_size); lcb_assert(ringbuffer_memcpy(&rest, conn->output, nbytes) == 0); ringbuffer_consumed(conn->output, nbytes); send_size -= nbytes; } stream_size -= packetsize; headersize = (lcb_uint32_t)sizeof(req) + req.request.extlen + htons(req.request.keylen); if (!ringbuffer_is_continous(stream, RINGBUFFER_READ, headersize)) { packet = malloc(headersize); if (packet == NULL) { lcb_error_handler(server->instance, LCB_CLIENT_ENOMEM, NULL); abort(); } nr = ringbuffer_peek(stream, packet, headersize); if (nr != headersize) { lcb_error_handler(server->instance, LCB_EINTERNAL, NULL); free(packet); abort(); } allocated = 1; } keyptr = packet + sizeof(req) + req.request.extlen; nkey = ntohs(req.request.keylen); failout_single_request(server, &req, &ct, error, keyptr, nkey, packet); if (allocated) { free(packet); } ringbuffer_consumed(stream, packetsize); if (mirror) { ringbuffer_consumed(mirror, packetsize); } } while (1); /* CONSTCOND */ if (server->connection_ready && conn->output) { /* Preserve the rest of the stream */ lcb_size_t nbytes = ringbuffer_get_nbytes(stream); send_size = ringbuffer_get_nbytes(conn->output); if (send_size >= nbytes) { ringbuffer_consumed(conn->output, send_size - nbytes); lcb_assert(ringbuffer_memcpy(&rest, conn->output, nbytes) == 0); } ringbuffer_reset(conn->output); ringbuffer_append(&rest, conn->output); } ringbuffer_destruct(&rest); lcb_maybe_breakout(server->instance); }
int lcb_proto_parse_single(lcb_server_t *c, hrtime_t stop) { int rv; packet_info info; protocol_binary_request_header req; lcb_size_t nr; lcb_connection_t conn = &c->connection; rv = lcb_packet_read_ringbuffer(&info, conn->input); if (rv == -1) { return -1; } else if (rv == 0) { return 0; } /* Is it already timed out? */ nr = ringbuffer_peek(&c->cmd_log, req.bytes, sizeof(req)); if (nr < sizeof(req) || /* the command log doesn't know about it */ (PACKET_OPAQUE(&info) < req.request.opaque && PACKET_OPAQUE(&info) > 0)) { /* sasl comes with zero opaque */ /* already processed. */ lcb_packet_release_ringbuffer(&info, conn->input); return 1; } nr = ringbuffer_peek(&c->output_cookies, &info.ct, sizeof(info.ct)); if (nr != sizeof(info.ct)) { lcb_error_handler(c->instance, LCB_EINTERNAL, NULL); lcb_packet_release_ringbuffer(&info, conn->input); return -1; } info.ct.vbucket = ntohs(req.request.vbucket); switch (info.res.response.magic) { case PROTOCOL_BINARY_REQ: /* * The only way to get request packets is if someone started * to send us TAP requests, and we don't support that anymore */ lcb_error_handler(c->instance, LCB_EINTERNAL, "Protocol error. someone sent us a command!"); return -1; case PROTOCOL_BINARY_RES: { int was_connected = c->connection_ready; rv = lcb_server_purge_implicit_responses(c, PACKET_OPAQUE(&info), stop, 0); if (rv != 0) { lcb_packet_release_ringbuffer(&info, conn->input); return -1; } if (c->instance->histogram) { lcb_record_metrics(c->instance, stop - info.ct.start, PACKET_OPCODE(&info)); } if (PACKET_STATUS(&info) != PROTOCOL_BINARY_RESPONSE_NOT_MY_VBUCKET || PACKET_OPCODE(&info) == CMD_GET_REPLICA || PACKET_OPCODE(&info) == CMD_OBSERVE) { rv = lcb_dispatch_response(c, &info); if (rv == -1) { lcb_error_handler(c->instance, LCB_EINTERNAL, "Received unknown command response"); abort(); return -1; } /* keep command and cookie until we get complete STAT response */ swallow_command(c, &info.res, was_connected); } else { rv = handle_not_my_vbucket(c, &info, &req, &info.ct); if (rv == -1) { return -1; } else if (rv == 0) { lcb_dispatch_response(c, &info); swallow_command(c, &info.res, was_connected); } } break; } default: lcb_error_handler(c->instance, LCB_PROTOCOL_ERROR, NULL); lcb_packet_release_ringbuffer(&info, conn->input); return -1; } lcb_packet_release_ringbuffer(&info, conn->input); return 1; }
static int lcb_parse_single_with_packet_forward(lcb_server_t *c, hrtime_t stop) { protocol_binary_request_header req; protocol_binary_response_header header; lcb_size_t nr; lcb_size_t avail; int was_connected = c->connection_ready; uint16_t status; lcb_size_t packetsize; struct lcb_command_data_st ct; lcb_connection_t conn = &c->connection; lcb_t instance = c->instance; /* Check if we have the entire packet */ nr = lcb_ringbuffer_peek(&conn->input.buffer->ringbuffer, header.bytes, sizeof(header)); if (nr < sizeof(header)) { return 0; } packetsize = ntohl(header.response.bodylen) + (lcb_uint32_t)sizeof(header); avail = lcb_ringbuffer_get_nbytes(&conn->input.buffer->ringbuffer); if (avail < packetsize) { /* We need more data! */ return 0; } /* Is it already timed out? */ nr = lcb_ringbuffer_peek(&c->cmd_log, req.bytes, sizeof(req)); if (nr < sizeof(req) || /* the command log doesn't know about it */ (header.response.opaque < req.request.opaque && header.response.opaque > 0)) { /* sasl comes with zero opaque */ /* already processed. */ lcb_ringbuffer_consumed(&conn->input.buffer->ringbuffer, packetsize); return 1; } /* We've got the entire packet!! */ assert(header.response.magic == PROTOCOL_BINARY_RES); nr = lcb_ringbuffer_peek(&c->output_cookies, &ct, sizeof(ct)); if (nr != sizeof(ct)) { lcb_error_handler(c->instance, LCB_EINTERNAL, NULL); return -1; } ct.vbucket = ntohs(req.request.vbucket); if (lcb_server_purge_implicit_responses(c, header.response.opaque, stop, 0)) { return -1; } if (c->instance->histogram) { lcb_record_metrics(c->instance, stop - ct.start, header.response.opcode); } status = ntohs(header.response.status); if (status != PROTOCOL_BINARY_RESPONSE_NOT_MY_VBUCKET || header.response.opcode == CMD_GET_REPLICA || header.response.opcode == CMD_OBSERVE) { /* Prepare the callback data */ lcb_packet_fwd_resp_t resp; lcb_error_t err = LCB_SUCCESS; resp.version = 0; resp.v.v0.buffer = conn->input.buffer; lcb_ringbuffer_get_iov(&resp.v.v0.buffer->ringbuffer, LCB_RINGBUFFER_READ, resp.v.v0.iov); /* The IOV should only contain the exact bits for the packet */ if (resp.v.v0.iov[0].iov_len > packetsize) { resp.v.v0.iov[0].iov_len = packetsize; resp.v.v0.iov[1].iov_len = 0; } else { resp.v.v0.iov[1].iov_len = packetsize - resp.v.v0.iov[0].iov_len; } /* fire the callback */ conn->input.locked |= instance->callbacks.packet_fwd(instance, ct.cookie, err, &resp); lcb_ringbuffer_consumed(&conn->input.buffer->ringbuffer, packetsize); /* keep command and cookie until we get complete STAT response */ swallow_command(c, &header, was_connected); } else { int rv = handle_not_my_vbucket(c, &req, &ct); if (rv == -1) { return -1; } else if (rv == 0) { /* Prepare the callback data */ lcb_packet_fwd_resp_t resp; lcb_error_t err = LCB_SUCCESS; resp.version = 0; resp.v.v0.buffer = conn->input.buffer; lcb_ringbuffer_get_iov(&resp.v.v0.buffer->ringbuffer, LCB_RINGBUFFER_READ, resp.v.v0.iov); /* The IOV should only contain the exact bits for the packet */ if (resp.v.v0.iov[0].iov_len > packetsize) { resp.v.v0.iov[0].iov_len = packetsize; resp.v.v0.iov[1].iov_len = 0; } else { resp.v.v0.iov[1].iov_len = packetsize - resp.v.v0.iov[0].iov_len; } /* fire the callback */ conn->input.locked |= instance->callbacks.packet_fwd(instance, ct.cookie, err, &resp); lcb_ringbuffer_consumed(&conn->input.buffer->ringbuffer, packetsize); /* keep command and cookie until we get complete STAT response */ swallow_command(c, &header, was_connected); } } return 1; }
static int lcb_parse_single_with_specialized_callbacks(lcb_server_t *c, hrtime_t stop) { protocol_binary_request_header req; protocol_binary_response_header header; lcb_size_t nr; char *packet; lcb_size_t packetsize; struct lcb_command_data_st ct; lcb_connection_t conn = &c->connection; if (lcb_ringbuffer_ensure_alignment(&conn->input.buffer->ringbuffer) != 0) { lcb_error_handler(c->instance, LCB_EINTERNAL, NULL); return -1; } nr = lcb_ringbuffer_peek(&conn->input.buffer->ringbuffer, header.bytes, sizeof(header)); if (nr < sizeof(header)) { return 0; } packetsize = ntohl(header.response.bodylen) + (lcb_uint32_t)sizeof(header); if (conn->input.buffer->ringbuffer.nbytes < packetsize) { return 0; } /* Is it already timed out? */ nr = lcb_ringbuffer_peek(&c->cmd_log, req.bytes, sizeof(req)); if (nr < sizeof(req) || /* the command log doesn't know about it */ (header.response.opaque < req.request.opaque && header.response.opaque > 0)) { /* sasl comes with zero opaque */ /* already processed. */ lcb_ringbuffer_consumed(&conn->input.buffer->ringbuffer, packetsize); return 1; } packet = conn->input.buffer->ringbuffer.read_head; /* we have everything! */ if (!lcb_ringbuffer_is_continous(&conn->input.buffer->ringbuffer, LCB_RINGBUFFER_READ, packetsize)) { /* The buffer isn't continous.. for now just copy it out and ** operate on the copy ;) */ if ((packet = malloc(packetsize)) == NULL) { lcb_error_handler(c->instance, LCB_CLIENT_ENOMEM, NULL); return -1; } nr = lcb_ringbuffer_read(&conn->input.buffer->ringbuffer, packet, packetsize); if (nr != packetsize) { lcb_error_handler(c->instance, LCB_EINTERNAL, NULL); free(packet); return -1; } } nr = lcb_ringbuffer_peek(&c->output_cookies, &ct, sizeof(ct)); if (nr != sizeof(ct)) { lcb_error_handler(c->instance, LCB_EINTERNAL, NULL); if (packet != conn->input.buffer->ringbuffer.read_head) { free(packet); } return -1; } ct.vbucket = ntohs(req.request.vbucket); switch (header.response.magic) { case PROTOCOL_BINARY_REQ: /* * The only way to get request packets is if someone started * to send us TAP requests, and we don't support that anymore */ lcb_error_handler(c->instance, LCB_EINTERNAL, "Protocol error. someone sent us a command!"); return -1; case PROTOCOL_BINARY_RES: { int was_connected = c->connection_ready; if (lcb_server_purge_implicit_responses(c, header.response.opaque, stop, 0) != 0) { if (packet != conn->input.buffer->ringbuffer.read_head) { free(packet); } return -1; } if (c->instance->histogram) { lcb_record_metrics(c->instance, stop - ct.start, header.response.opcode); } if (ntohs(header.response.status) != PROTOCOL_BINARY_RESPONSE_NOT_MY_VBUCKET || header.response.opcode == CMD_GET_REPLICA || header.response.opcode == CMD_OBSERVE) { if (lcb_dispatch_response(c, &ct, (void *)packet) == -1) { /* * Internal error.. we received an unsupported response * id. This should _ONLY_ happen at development time because * we won't receive response packets with other opcodes * than we send. Let's abort here to make it easy for * the developer to know what happened.. */ lcb_error_handler(c->instance, LCB_EINTERNAL, "Received unknown command response"); abort(); return -1; } /* keep command and cookie until we get complete STAT response */ swallow_command(c, &header, was_connected); } else { int rv = handle_not_my_vbucket(c, &req, &ct); if (rv == -1) { return -1; } else if (rv == 0) { lcb_dispatch_response(c, &ct, (void *)packet); swallow_command(c, &header, was_connected); } } break; } default: lcb_error_handler(c->instance, LCB_PROTOCOL_ERROR, NULL); if (packet != conn->input.buffer->ringbuffer.read_head) { free(packet); } return -1; } if (packet != conn->input.buffer->ringbuffer.read_head) { free(packet); } else { lcb_ringbuffer_consumed(&conn->input.buffer->ringbuffer, packetsize); } return 1; }