// Request the info of a particular sockaddr_in. // Reject info request if response length is greater than max_response_length. // Return 0 on success and -1 on error. int citrusleaf_info_host_limit(struct sockaddr_in *sa_in, char *names, char **values, int timeout_ms, bool send_asis, uint64_t max_response_length) { int rv = -1; int io_rv; *values = 0; // Deal with the incoming 'names' parameter // Translate interior ';' in the passed-in names to \n uint32_t slen = 0; if (names) { if (send_asis) { slen = strlen(names); } else { char *_t = names; while (*_t) { slen++; if ((*_t == ';') || (*_t == ':') || (*_t == ',')) *_t = '\n'; _t++; } } } // Sometimes people forget/cant add the trailing '\n'. Be nice and add it for them. // using a stack allocated variable so we dn't have to clean up, Pain in the ass syntactically // but a nice little thing if (names) { if (names[slen-1] == '\n') { slen = 0; } else { slen++; if (slen > 1024) { return(-1); } } } char names_with_term[slen+1]; if (slen) { strcpy(names_with_term, names); names_with_term[slen-1] = '\n'; names_with_term[slen] = 0; names = names_with_term; } // Actually doing a non-blocking connect int fd = cf_socket_create_and_connect_nb(sa_in); if (fd == -1) { return -1; } cl_proto *req; uint8_t buf[1024]; uint buf_sz; // Un-initialized buf can lead to junk lastshiptimes values. // Initialize buf to 0. bzero(buf, 1024); if (names) { uint sz = strlen(names); buf_sz = sz + sizeof(cl_proto); if (buf_sz < 1024) req = (cl_proto *) buf; else req = (cl_proto *) malloc(buf_sz); if (req == NULL) goto Done; req->sz = sz; memcpy(req->data,names,sz); } else { req = (cl_proto *) buf; req->sz = 0; buf_sz = sizeof(cl_proto); names = ""; } req->version = CL_PROTO_VERSION; req->type = CL_PROTO_TYPE_INFO; cl_proto_swap(req); if (timeout_ms) io_rv = cf_socket_write_timeout(fd, (uint8_t *) req, buf_sz, 0, timeout_ms); else io_rv = cf_socket_write_forever(fd, (uint8_t *) req, buf_sz); if ((uint8_t *)req != buf) free(req); if (io_rv != 0) { #ifdef DEBUG cf_debug("info returned error, rv %d errno %d bufsz %d", io_rv, errno, buf_sz); #endif goto Done; } cl_proto *rsp = (cl_proto *)buf; if (timeout_ms) io_rv = cf_socket_read_timeout(fd, buf, 8, 0, timeout_ms); else io_rv = cf_socket_read_forever(fd, buf, 8); if (0 != io_rv) { #ifdef DEBUG cf_debug("info socket read failed: rv %d errno %d", io_rv, errno); #endif goto Done; } cl_proto_swap(rsp); if (rsp->sz) { size_t read_length = rsp->sz; bool limit_reached = false; if (max_response_length > 0 && rsp->sz > max_response_length) { // Response buffer is too big. Read a few bytes just to see what the buffer contains. read_length = 100; limit_reached = true; } uint8_t *v_buf = malloc(read_length + 1); if (!v_buf) { cf_warn("Info request '%s' failed. Failed to malloc %d bytes", names, read_length); goto Done; } if (timeout_ms) io_rv = cf_socket_read_timeout(fd, v_buf, read_length, 0, timeout_ms); else io_rv = cf_socket_read_forever(fd, v_buf, read_length); if (io_rv != 0) { free(v_buf); if (io_rv != ETIMEDOUT) { cf_warn("Info request '%s' failed. Failed to read %d bytes. Return code %d", names, read_length, io_rv); } goto Done; } v_buf[read_length] = 0; if (limit_reached) { // Response buffer is too big. Log warning and reject. cf_warn("Info request '%s' failed. Response buffer length %lu is excessive. Buffer: %s", names, rsp->sz, v_buf); goto Done; } *values = (char *) v_buf; } else { *values = 0; } rv = 0; Done: shutdown(fd, SHUT_RDWR); close(fd); return(rv); }
static void* async_receiver_fn(void *thdata) { int rv = -1; bool network_error = false; cl_async_work *workitem = NULL; // cl_async_work *tmpworkitem = NULL; as_msg msg; cf_queue *q_to_use = NULL; cl_cluster_node *thisnode = NULL; uint8_t rd_stack_buf[STACK_BUF_SZ]; uint8_t *rd_buf = rd_stack_buf; size_t rd_buf_sz = 0; uint64_t acktrid; // uint64_t starttime, endtime; int progress_timeout_ms; unsigned int thread_id = cf_atomic32_incr(&g_thread_count); if (thdata == NULL) { q_to_use = g_cl_async_q; } else { thisnode = (cl_cluster_node *)thdata; q_to_use = thisnode->asyncwork_q; } //Infinite loop which keeps picking work items from the list and try to find the end result while(1) { network_error = false; #if ONEASYNCFD if(thisnode->dunned == true) { do { rv = cf_queue_pop(thisnode->asyncwork_q, &workitem, CF_QUEUE_NOWAIT); if (rv == CF_QUEUE_OK) { cl_cluster_node_put(thisnode); free(workitem); } } while (rv == CF_QUEUE_OK); //We want to delete all the workitems of this node shash_reduce_delete(g_cl_async_hashtab, cl_del_node_asyncworkitems, thisnode); break; } #endif //This call will block if there is no element in the queue cf_queue_pop(q_to_use, &workitem, CF_QUEUE_FOREVER); //TODO: What if the node gets dunned while this pop call is blocked ? #if ONEASYNCFD //cf_debug("Elements remaining in this node's queue=%d, Hash table size=%d", // cf_queue_sz(thisnode->asyncwork_q), shash_get_size(g_cl_async_hashtab)); #endif // If we have no progress in 50ms, we should move to the next workitem // and revisit this workitem at a later stage progress_timeout_ms = DEFAULT_PROGRESS_TIMEOUT; // Read into this fine cl_msg, which is the short header rv = cf_socket_read_timeout(workitem->fd, (uint8_t *) &msg, sizeof(as_msg), workitem->deadline, progress_timeout_ms); if (rv) { #if DEBUG cf_debug("Citrusleaf: error when reading header from server - rv %d fd %d", rv, workitem->fd); #endif if (rv != ETIMEDOUT) { cf_error("Citrusleaf: error when reading header from server - rv %d fd %d",rv,workitem->fd); network_error = true; goto Error; } else { goto Retry; } } #ifdef DEBUG_VERBOSE dump_buf("read header from cluster", (uint8_t *) &msg, sizeof(cl_msg)); #endif cl_proto_swap(&msg.proto); cl_msg_swap_header(&msg.m); // second read for the remainder of the message rd_buf_sz = msg.proto.sz - msg.m.header_sz; if (rd_buf_sz > 0) { if (rd_buf_sz > sizeof(rd_stack_buf)) { rd_buf = malloc(rd_buf_sz); if (!rd_buf) { cf_error("malloc fail: trying %zu",rd_buf_sz); rv = -1; goto Error; } } rv = cf_socket_read_timeout(workitem->fd, rd_buf, rd_buf_sz, workitem->deadline, progress_timeout_ms); if (rv) { //We already read some part of the message before but failed to read the //remaining data for whatever reason (network error or timeout). We cannot //reread as we already read partial data. Declare this as error. cf_error("Timeout after reading the header but before reading the body"); goto Error; } #ifdef DEBUG_VERBOSE dump_buf("read body from cluster", rd_buf, rd_buf_sz); #endif } rv = CITRUSLEAF_OK; goto Ok; Retry: //We are trying to postpone the reading if (workitem->deadline && workitem->deadline < cf_getms()) { cf_error("async receiver: out of time : deadline %"PRIu64" now %"PRIu64, workitem->deadline, cf_getms()); //cf_error("async receiver: Workitem missed the final deadline"); rv = CITRUSLEAF_FAIL_TIMEOUT; goto Error; } else { //We have time. Push the element back to the queue to be considered later cf_queue_push(q_to_use, &workitem); } //If we allocated memory in this loop, release it. if (rd_buf && (rd_buf != rd_stack_buf)) { free(rd_buf); } cf_atomic_int_incr(&g_async_stats.retries); continue; Error: if (network_error == true) { /* * In case of Async work (for XDS), it may be extreme to * dun a node in case of network error. We just cleanup * things and retry to connect to the remote cluster. * The network error may be a transient one. */ } #if ONEASYNCFD //Do not close FD #else //We do not know the state of FD. It may have pending data to be read. //We cannot reuse the FD. So, close it to be on safe side. cf_error("async receiver: Closing the fd %d because of error", workitem->fd); cf_close(workitem->fd); workitem->fd = -1; #endif cf_atomic_int_incr(&g_async_stats.dropouts); //Continue down with what we do during an Ok //Inform the caller that there is no response from the server for this workitem. //No response does not mean that the work is not done. The work might be //successfully completed on the server side, we just didnt get response for it. if (g_fail_cb_fn) { g_fail_cb_fn(workitem->udata, rv, workitem->starttime); } Ok: //rd_buf may not be there during an error condition. if (rd_buf && (rv == CITRUSLEAF_OK)) { //As of now, async functionality is there only for put call. //In put call, we do not get anything back other than the trid field. //So, just pass variable to get back the trid and ignore others. if (0 != cl_parse(&msg.m, rd_buf, rd_buf_sz, NULL, NULL, NULL, &acktrid, NULL)) { rv = CITRUSLEAF_FAIL_UNKNOWN; } else { rv = msg.m.result_code; if (workitem->trid != acktrid) { #if ONEASYNCFD //It is likely that we may get response for a different trid. //Just delete the correct one from the queue //put back the current workitem back in the queue. shash_get(g_cl_async_hashtab, &acktrid, &tmpworkitem); cf_queue_delete(q_to_use, &tmpworkitem, true); cf_queue_push(q_to_use, &workitem); //From now on workitem will be the one for which we got ack workitem = tmpworkitem; #endif #ifdef DEBUG cf_debug("Got reply for a different trid. Expected=%"PRIu64" Got=%"PRIu64" FD=%d", workitem->trid, acktrid, workitem->fd); #endif } } if (g_success_cb_fn) { g_success_cb_fn(workitem->udata, rv, workitem->starttime); } } //Remember to put back the FD into the pool, if it is re-usable. if (workitem->fd != -1) { cl_cluster_node_fd_put(workitem->node, workitem->fd, true); } //Also decrement the reference count for this node cl_cluster_node_put(workitem->node); #if ONEASYNCFD //Delete the item from the global hashtable if (shash_delete(g_cl_async_hashtab, &workitem->trid) != SHASH_OK) { #if DEBUG cf_debug("Failure while trying to delete trid=%"PRIu64" from hashtable", workitem->trid); #endif } #endif //Push it back into the free pool. If the attempt fails, free it. if (cf_queue_push(g_cl_workitems_freepool_q, &workitem) == -1) { free(workitem); } //If we allocated memory in this loop, release it. if (rd_buf && (rd_buf != rd_stack_buf)) { free(rd_buf); } // Kick this thread out if its ID is greater than total if (thread_id > cf_atomic32_get(g_async_num_threads)) { cf_atomic32_decr(&g_thread_count); return NULL; } }//The infnite loop return NULL; }