static int fqd_http_message_url(http_parser *p, const char *at, size_t len) { struct http_req *req = p->data; req->url = malloc(len+1); strlcpy(req->url, at, len+1); req->qs = strchr(req->url, '?'); if(req->qs) *(req->qs++) = '\0'; if (req->qs != NULL) { char *trailing = req->qs; for (uint32_t i = 0; i < strlen(req->qs); i++) { if (req->qs[i] == '&') { req->qs[i] = '\0'; store_kv(&req->query_params, trailing); req->qs[i] = '&'; trailing = &req->qs[i+1]; } } store_kv(&req->query_params, trailing); } fq_debug(FQ_DEBUG_HTTP, ".on_url -> '%s'\n", req->url); fq_debug(FQ_DEBUG_HTTP, ".on_url query_string -> '%s'\n", req->qs); return 0; }
static int fq_client_connect_internal(fq_conn_s *conn_s) { int rv = -1; uint32_t cmd = htonl(FQ_PROTO_CMD_MODE); fq_client_disconnect_internal(conn_s); conn_s->cmd_fd = fq_socket_connect(conn_s); if(conn_s->cmd_fd < 0) goto shutdown; fq_debug(FQ_DEBUG_CONN, "connect(cmd_fd) -> %d\n", conn_s->cmd_fd); if(write(conn_s->cmd_fd, &cmd, sizeof(cmd)) != sizeof(cmd)) goto shutdown; if((rv = fq_client_do_auth(conn_s)) < 0) { fq_debug(FQ_DEBUG_CONN, "fq_client_do_auth -> %d\n", rv); goto shutdown; } if(conn_s->auth_hook) { if(conn_s->sync_hooks) enqueue_auth_hook_req(conn_s, 0); else conn_s->auth_hook((fq_client)conn_s, 0); } return 0; shutdown: if(conn_s->cmd_fd >= 0) { int toclose = conn_s->cmd_fd; conn_s->cmd_fd = -1; fq_debug(FQ_DEBUG_CONN, "close(cmd_fd) (in auth)\n"); close(toclose); if(conn_s->disconnect_hook) conn_s->disconnect_hook(conn_s); } if(conn_s->auth_hook) { if(conn_s->sync_hooks) enqueue_auth_hook_req(conn_s, rv); else conn_s->auth_hook((fq_client)conn_s, rv); } return -1; }
static void fqd_peer_auth_hook(fq_client conn, int authed) { int i; fqd_peer_connection *peer; peer = fq_client_get_userdata(conn); fq_debug(FQ_DEBUG_PEER, "authed(%s:%d) -> %d\n", peer->host, peer->port, authed); if(authed || !peer) return; pthread_mutex_lock(&lock); peer->online_and_bound = true; for(i=0;i<peer->n_bindings;i++) { peer_binding_info *bi = peer->bindings[i]; fq_bind_req *breq; breq = calloc(1, sizeof(*breq)); memcpy(&breq->exchange, &bi->exchange, sizeof(bi->exchange)); breq->flags = FQ_BIND_PEER | (bi->perm ? FQ_BIND_PERM : 0); breq->program = strdup(bi->prog); fq_client_bind(conn, breq); fq_debug(FQ_DEBUG_PEER, "bindreq(%s:%d) %.*s/%s\n", peer->host, peer->port, bi->exchange.len, bi->exchange.name, bi->prog); } pthread_mutex_unlock(&lock); }
static void * fq_data_worker(void *u) { int backoff = 0; bool zero; fq_conn_s *conn_s = (fq_conn_s *)u; ck_pr_inc_uint(&conn_s->thrcnt); while(conn_s->stop == 0) { if(conn_s->data_ready) { if(fq_client_data_connect_internal(conn_s) == 0) { backoff = 0; /* we're good, restart our backoff */ } fq_debug(FQ_DEBUG_IO, "[data] connected\n"); fq_data_worker_loop(conn_s); fq_debug(FQ_DEBUG_IO, "[data] connection failed: %s\n", conn_s->error); } if(backoff) usleep(backoff + (4096 - (lrand48()%8192))); /* +/- 4ms */ else backoff = 16384; if(backoff < 1000000) backoff += (backoff >> 4); } if(conn_s->data_fd >= 0) { int toclose = conn_s->data_fd; conn_s->data_fd = -1; fq_debug(FQ_DEBUG_CONN, "close(data_fd)\n"); close(toclose); } ck_pr_dec_uint_zero(&conn_s->thrcnt, &zero); if(zero) fq_conn_free(conn_s); return (void *)NULL; }
static int fq_client_data_connect_internal(fq_conn_s *conn_s) { int flags; uint32_t cmd = htonl(conn_s->peermode ? FQ_PROTO_PEER_MODE : FQ_PROTO_DATA_MODE); /* We don't support data connections when the cmd connection is down */ if(conn_s->cmd_fd < 0) return -1; if(conn_s->data_fd >= 0) { int toclose = conn_s->data_fd; conn_s->data_fd = -1; fq_debug(FQ_DEBUG_CONN, "close(data_fd)\n"); close(toclose); } conn_s->data_fd = fq_socket_connect(conn_s); if(conn_s->data_fd < 0) goto shutdown; fq_debug(FQ_DEBUG_CONN, "connect(data_fd) -> %d\n", conn_s->data_fd); if(write(conn_s->data_fd, &cmd, sizeof(cmd)) != sizeof(cmd)) goto shutdown; if(conn_s->peermode) { if(write(conn_s->data_fd, &conn_s->peermode, sizeof(conn_s->peermode)) != sizeof(conn_s->peermode)) goto shutdown; } #ifdef DEBUG { char hex[260]; if(fq_rk_to_hex(hex, sizeof(hex), &conn_s->key) >= 0) fq_debug(FQ_DEBUG_CONN, "client keying:\n%s\n", hex); } #endif if(fq_write_short_cmd(conn_s->data_fd, conn_s->key.len, conn_s->key.name) < 0) { goto shutdown; } conn_s->tosend_offset = 0; if(((flags = fcntl(conn_s->data_fd, F_GETFL, 0)) == -1) || (fcntl(conn_s->data_fd, F_SETFL, flags | O_NONBLOCK) == -1)) goto shutdown; return 0; shutdown: if(conn_s->data_fd >= 0) { int toclose = conn_s->data_fd; conn_s->data_fd = -1; fq_debug(FQ_DEBUG_CONN, "close(data_fd)\n"); close(toclose); } return -1; }
static fqd_queue_impl_data queue_jlog_setup(fq_rk *qname, uint32_t *count) { char qpath[PATH_MAX]; jlog_id chkpt; struct queue_jlog *d; d = calloc(1, sizeof(*d)); d->auto_chkpt = true; fqd_config_construct_queue_path(qpath, sizeof(qpath), qname); d->qpath = strdup(qpath); d->writer = jlog_new(d->qpath); if(jlog_ctx_open_writer(d->writer) != 0) { jlog_ctx_close(d->writer); d->writer = jlog_new(d->qpath); if(jlog_ctx_init(d->writer) != 0) { fq_debug(FQ_DEBUG_IO, "jlog init: %s\n", jlog_ctx_err_string(d->writer)); goto bail; } jlog_ctx_close(d->writer); d->writer = jlog_new(d->qpath); if(jlog_ctx_open_writer(d->writer) != 0) { fq_debug(FQ_DEBUG_IO, "jlog writer: %s\n", jlog_ctx_err_string(d->writer)); goto bail; } } d->reader = jlog_new(d->qpath); if(jlog_get_checkpoint(d->reader, "fq", &chkpt) != 0) { if(jlog_ctx_add_subscriber(d->reader, "fq", JLOG_BEGIN) != 0) { fq_debug(FQ_DEBUG_IO, "jlog add sub: %s\n", jlog_ctx_err_string(d->reader)); goto bail; } } if(jlog_ctx_open_reader(d->reader, "fq") != 0) { fq_debug(FQ_DEBUG_IO, "jlog: %s\n", jlog_ctx_err_string(d->reader)); goto bail; } uuid_generate(d->uuid); write_sig(d); *count = 0; (void)qname; return d; bail: if(d->writer) jlog_ctx_close(d->writer); if(d->reader) jlog_ctx_close(d->reader); free(d->qpath); free(d); return NULL; }
static int fqd_http_message_header_value(http_parser *p, const char *at, size_t len) { struct http_req *req = p->data; ck_ht_entry_t entry; ck_ht_hash_t hv; char *val; if(!req->fldname) return -1; val = malloc(len+1); strlcpy(val, at, len+1); fq_debug(FQ_DEBUG_HTTP, ".on_header_value -> '%s'\n", val); ck_ht_hash(&hv, &req->headers, req->fldname, strlen(req->fldname)); ck_ht_entry_set(&entry, hv, req->fldname, strlen(req->fldname), val); if(ck_ht_set_spmc(&req->headers, hv, &entry) && !ck_ht_entry_empty(&entry)) { char *key = ck_ht_entry_key(&entry); char *value = ck_ht_entry_value(&entry); if(key && key != req->fldname) free(key); if(value && value != val) free(value); } if(!strcmp(req->fldname, "expect") && !strcasecmp(val,"100-continue")) req->expect_continue = HTTP_EXPECT_CONTINUE; req->fldname = NULL; return 0; }
static void fqd_peer_disconnect_hook(fq_client conn) { fqd_peer_connection *peer; peer = fq_client_get_userdata(conn); fq_debug(FQ_DEBUG_PEER, "disconnect from peer(%s:%d)\n", peer->host, peer->port); if(peer) peer->online_and_bound = false; }
static int fqd_http_message_status(http_parser *p, const char *at, size_t len) { struct http_req *req = p->data; req->status = malloc(len+1); strlcpy(req->status, at, len+1); fq_debug(FQ_DEBUG_HTTP, ".on_status -> '%s'\n", req->status); return 0; }
static int fq_client_do_auth(fq_conn_s *conn_s) { int len, qlen, qtlen; uint16_t cmd; char error[1024]; char queue_composed[MAX_RK_LEN*2 + 1]; if(fq_write_uint16(conn_s->cmd_fd, FQ_PROTO_AUTH_CMD)) return -1; if(fq_write_uint16(conn_s->cmd_fd, FQ_PROTO_AUTH_PLAIN)) return -2; if(fq_write_short_cmd(conn_s->cmd_fd, strlen(conn_s->user), conn_s->user) < 0) return -3; qlen = strlen(conn_s->queue); if(qlen > MAX_RK_LEN) qlen = MAX_RK_LEN; qtlen = strlen(conn_s->queue_type); if(qtlen > MAX_RK_LEN) qtlen = MAX_RK_LEN; len = qlen + qtlen + 1; memcpy(queue_composed, conn_s->queue, qlen); queue_composed[qlen] = '\0'; memcpy(queue_composed + qlen + 1, conn_s->queue_type, qtlen); if(fq_write_short_cmd(conn_s->cmd_fd, len, queue_composed) < 0) return -4; if(fq_write_short_cmd(conn_s->cmd_fd, strlen(conn_s->pass), conn_s->pass) < 0) return -5; if(fq_read_uint16(conn_s->cmd_fd, &cmd)) return -6; switch(cmd) { case FQ_PROTO_ERROR: len = fq_read_short_cmd(conn_s->cmd_fd, sizeof(error)-1, error); if(conn_s->errorlog) { if(len > (int)sizeof(error)-1) len = sizeof(error)-1; if(len < 0) conn_s->errorlog(conn_s, "error reading error"); else conn_s->errorlog(conn_s,error); } return -7; case FQ_PROTO_AUTH_RESP: len = fq_read_short_cmd(conn_s->cmd_fd, sizeof(conn_s->key.name), conn_s->key.name); if(len < 0 || len > (int)sizeof(conn_s->key.name)) return -8; conn_s->key.len = len; #ifdef DEBUG { char hex[260]; if(fq_rk_to_hex(hex, sizeof(hex), &conn_s->key) >= 0) fq_debug(FQ_DEBUG_CONN, "client keyed:\n%s\n", hex); } #endif conn_s->data_ready = 1; break; default: if(conn_s->errorlog) { snprintf(error, sizeof(error), "server auth response 0x%04x unknown\n", cmd); conn_s->errorlog(conn_s, error); } return -9; } return 0; }
static void fqd_peer_start(fqd_peer_connection *peer) { fq_debug(FQ_DEBUG_PEER, "starting peer(%s:%d)\n", peer->host, peer->port); fq_client_init(&peer->client, fqd_config_get_nodeid(), peerlog); fq_client_set_userdata(peer->client, peer); fq_client_creds(peer->client, peer->host, peer->port, peer->user, peer->pass); fq_client_hooks(peer->client, &fqd_peer_hooks); fq_client_connect(peer->client); }
static int fqd_http_message_header_field(http_parser *p, const char *at, size_t len) { char *cp; struct http_req *req = p->data; req->fldname = malloc(len+1); strlcpy(req->fldname, at, len+1); for(cp=req->fldname;*cp;cp++) *cp = tolower(*cp); fq_debug(FQ_DEBUG_HTTP, ".on_header_field -> '%s'\n", req->fldname); return 0; }
void fqd_remote_client_deref(remote_client *r) { bool zero; ck_pr_dec_uint_zero(&r->refcnt, &zero); fq_debug(FQ_DEBUG_CONN, "deref client -> %u%s\n", r->refcnt, zero ? " dropping" : ""); if(zero) { close(r->fd); free(r); } }
fq_msg * fq_msg_alloc_BLANK(size_t s) { fq_msg *m; m = calloc(offsetof(fq_msg, payload) + ((s | (MSG_ALIGN-1))+1), 1); if(!m) return NULL; m->payload_len = s; #ifdef DEBUG fq_debug(FQ_DEBUG_MSG, "msg(%p) -> alloc\n", (void *)m); #endif m->refcnt = 1; return m; }
void fq_msg_deref(fq_msg *msg) { bool zero; ck_pr_dec_uint_zero(&msg->refcnt, &zero); if(zero) { #ifdef DEBUG fq_debug(FQ_DEBUG_MSG, "msg(%p) -> free\n", (void *)msg); #endif free(msg); } }
void fq_debug_stacktrace(fq_debug_bits_t b, const char *tag, int start, int end) { #define STACK_DEPTH 16 int i, cnt; void *bti[STACK_DEPTH + 1], **bt = bti+1; char **btname; cnt = backtrace(bti, STACK_DEPTH + 1); if(cnt < 1) { fq_debug(b, "track trace failed\n"); return; } btname = backtrace_symbols(bt, cnt); if(start > cnt) start = cnt; if(end > cnt) end = cnt; for(i=start;i!=end;i += (start > end) ? -1 : 1) { if(btname && btname[i]) fq_debug(b, "[%2d] %s %s\n", i, tag, btname[i]); else fq_debug(b, "[%2d] %s %p\n", i, tag, bt[i]); } if(btname) free(btname); }
static void fqd_peer_online_unbind(fqd_peer_connection *peer, peer_binding_info *bi) { fq_unbind_req *ureq; if(!bi->disabled || bi->disable_requested || !peer->online_and_bound || bi->route_id == FQ_BIND_ILLEGAL) return; fq_debug(FQ_DEBUG_PEER, "unbinding peer(%s:%d) exchange:\"%.*s\"\n", peer->host, peer->port, bi->exchange.len, bi->exchange.name); ureq = calloc(1, sizeof(*ureq)); memcpy(&ureq->exchange, &bi->exchange, sizeof(bi->exchange)); ureq->route_id = bi->route_id; fq_client_unbind(peer->client, ureq); }
static void fqd_peer_online_bind(fqd_peer_connection *peer, peer_binding_info *bi) { if(peer->online_and_bound == true) { fq_bind_req *breq; fq_debug(FQ_DEBUG_PEER, "binding peer(%s:%d) exchange:\"%.*s\"\n", peer->host, peer->port, bi->exchange.len, bi->exchange.name); breq = calloc(1, sizeof(*breq)); memcpy(&breq->exchange, &bi->exchange, sizeof(bi->exchange)); breq->flags = FQ_BIND_PEER | FQ_BIND_PERM; breq->program = strdup(bi->prog); fq_client_bind(peer->client, breq); } }
extern void fqd_data_subscription_server(remote_data_client *client) { int len; char buf[260]; fqd_config *config; remote_client *parent; fq_rk key; fq_debug(FQ_DEBUG_CONN, "--> dss thread [%s]\n", client->mode == FQ_PROTO_DATA_MODE ? "client" : "peer"); if((len = fq_read_short_cmd(client->fd, sizeof(key.name), key.name)) < 0) return; if(len > (int)sizeof(key.name)) return; key.len = len; fq_rk_to_hex(buf, sizeof(buf), &key); fq_debug(FQ_DEBUG_CONN, "data conn w/ key:\n%s\n", buf); config = fqd_config_get(); parent = fqd_config_get_registered_client(config, &key); fqd_config_release(config); if(!parent) return; if(parent->data) return; ck_pr_cas_ptr(&parent->data, NULL, client); if(parent->data != client) { fq_debug(FQ_DEBUG_CONN, "%s dss double gang rejected\n", parent->pretty); return; } if(FQ_CLIENT_AUTH_DATA_ENABLED()) { fq_dtrace_remote_data_client_t dclient; DTRACE_PACK_DATA_CLIENT(&dclient, client); FQ_CLIENT_AUTH_DATA(&dclient); } fqd_remote_client_ref(parent); fqd_data_driver(parent); fq_clear_message_cleanup_stack(); fqd_remote_client_deref(parent); fq_debug(FQ_DEBUG_CONN, "<-- dss thread\n"); }
static int fqd_http_message_body(http_parser *p, const char *at, size_t len) { struct http_req *req = p->data; fq_debug(FQ_DEBUG_HTTP, ".on_data -> %zu\n", len); if(req->msg) { if(req->body_read + len > req->body_len) { req->error = strdup("excessive data received"); return 1; } memcpy(req->msg->payload + req->body_read, at, len); req->body_read += len; } return 0; }
fq_msg * fq_msg_alloc(const void *data, size_t s) { fq_msg *m; m = malloc(offsetof(fq_msg, payload) + ((s | (MSG_ALIGN-1))+1)); if(!m) return NULL; m->payload_len = s; memset(m, 0, offsetof(fq_msg, payload)); if(s) memcpy(m->payload, data, s); #ifdef DEBUG fq_debug(FQ_DEBUG_MSG, "msg(%p) -> alloc\n", (void *)m); #endif m->refcnt = 1; return m; }
static void fq_client_disconnect_internal(fq_conn_s *conn_s) { if(conn_s->cmd_fd >= 0) { #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "close(cmd_fd)\n"); #endif close(conn_s->data_fd); } if(conn_s->data_fd >= 0) { #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "close(data_fd)\n"); #endif close(conn_s->data_fd); } conn_s->data_ready = 0; if(conn_s->cmd_hb_ms) { unsigned short hb; hb = conn_s->cmd_hb_ms; conn_s->cmd_hb_ms = 0; fq_client_heartbeat(conn_s, hb); conn_s->cmd_hb_last = 0; } }
static void queue_jlog_dispose(fq_rk *qname, fqd_queue_impl_data f) { struct queue_jlog *d = (struct queue_jlog *)f; uuid_t exist; (void)qname; uuid_clear(exist); read_sig(d, exist); if(uuid_compare(d->uuid, exist) == 0) { /* This is my jlog queue ... I can delete it */ fq_debug(FQ_DEBUG_IO, "jlog: removing %s\n", d->qpath); nftw(d->qpath, multi_unlink, 2, FTW_DEPTH); rmdir(d->qpath); } free(d); }
static int fqd_http_reset_to_checkpoint(struct http_req *req) { fqd_config *config = fqd_config_get(); const char *cpname = get_ht_value(&req->query_params, "cpname"); const char *qname = get_ht_value(&req->query_params, "qname"); fq_rk qn; fq_rk_from_str(&qn, qname); fqd_queue *queue = fqd_config_get_registered_queue(config, &qn); if (strcmp(cpname, "fq") == 0) { fqd_http_error_json(req->client, "'fq' is a reserved name, cannot be used for a checkpoint name"); req->close = 1; return 0; } if (queue == NULL) { fqd_http_error_json_f(req->client, "Cannot find registered queue '%s'", qname); req->close = 1; return 0; } int rv = queue->impl->reset_checkpoint(queue->impl_data, cpname); if (rv == -1) { fqd_http_error_json(req->client, "Checkpoint does not exist"); req->close = 1; return 0; } if (rv < 0) { fqd_http_error_json(req->client, "Unknown error"); req->close = 1; return 0; } fqd_http_success_json_f(req->client, "'%s' reset to checkpoint '%s'", qname, cpname); req->close = 1; fq_debug(FQ_DEBUG_HTTP, ".on_complete -> reset_to_checkpoint on [%s] for queue [%s]\n", cpname, qname); return 0; }
static bool fqd_peer_message_hook(fq_client conn, fq_msg *m) { int i; uint32_t me = fqd_config_get_nodeid(); fqd_peer_connection *peer; peer = fq_client_get_userdata(conn); for(i=MAX_HOPS-1;i>0;i--) { if(m->hops[i] == me) { fq_debug(FQ_DEBUG_PEER, "recieved looped message"); return true; } m->hops[i] = m->hops[i-1]; } m->hops[0] = fqd_config_get_nodeid(); /* route this */ fq_msg_ref(m); fqd_inject_message(peer ? peer->stats_holder : NULL, m); return true; }
/* split incoming string by '=' and store the left as key and right as value in the table */ static void store_kv(ck_ht_t *table, char *kv_string) { ck_ht_entry_t entry; ck_ht_hash_t hv; const char *key = kv_string; char *eq = strchr(kv_string, '='); if(eq) eq[0] = '\0'; const char *val = eq ? eq + 1 : ""; ck_ht_hash(&hv, table, key, strlen(key)); ck_ht_entry_set(&entry, hv, strdup(key), strlen(key), strdup(val)); if(ck_ht_set_spmc(table, hv, &entry)) { fq_debug(FQ_DEBUG_HTTP, ".store_kv -> added (%s, %s)\n", key, val); } /* be non-destructive */ if(eq) eq[0] = '='; }
static void queue_jlog_dispose(fq_rk *qname, fqd_queue_impl_data f) { struct queue_jlog *d = (struct queue_jlog *)f; uuid_t exist; (void)qname; if (d == NULL) { /* there was likely a total failure to init this queue type due to file system permissions, bail */ return; } uuid_clear(exist); read_sig(d, exist); if(uuid_compare(d->uuid, exist) == 0) { /* This is my jlog queue ... I can delete it */ fq_debug(FQ_DEBUG_IO, "jlog: removing %s\n", d->qpath); nftw(d->qpath, multi_unlink, 2, FTW_DEPTH); rmdir(d->qpath); } free(d); }
static void fqd_peer_bind_hook(fq_client conn, fq_bind_req *breq) { int i; fqd_peer_connection *peer; peer = fq_client_get_userdata(conn); if(!peer) return; fq_debug(FQ_DEBUG_PEER, "bindresp(%u) %.*s/%s\n", breq->out__route_id, breq->exchange.len, breq->exchange.name, breq->program); if(breq->out__route_id == FQ_BIND_ILLEGAL) { return; } pthread_mutex_lock(&lock); for(i=0;i<peer->n_bindings;i++) { peer_binding_info *bi = peer->bindings[i]; if(!fq_rk_cmp(&bi->exchange, &breq->exchange) && !strcmp(bi->prog, breq->program)) { bi->route_id = breq->out__route_id; break; } } pthread_mutex_unlock(&lock); }
static void * fq_data_worker(void *u) { int backoff = 0; fq_conn_s *conn_s = (fq_conn_s *)u; while(conn_s->stop == 0) { if(conn_s->data_ready) { if(fq_client_data_connect_internal(conn_s) == 0) { backoff = 0; /* we're good, restart our backoff */ } fq_data_worker_loop(conn_s); #ifdef DEBUG fq_debug(FQ_DEBUG_IO, "[data] connection failed: %s\n", conn_s->error); #endif } if(backoff < 1000000) backoff += 10000; if(backoff) usleep(backoff); } if(conn_s->data_fd >= 0) { close(conn_s->data_fd); conn_s->data_fd = -1; } return (void *)NULL; }
static void fq_client_disconnect_internal(fq_conn_s *conn_s) { if(conn_s->cmd_fd >= 0) { int toclose = conn_s->cmd_fd; conn_s->cmd_fd = -1; fq_stacktrace(FQ_DEBUG_CONN, "close(cmd_fd)\n",1,2); close(toclose); if(conn_s->disconnect_hook) conn_s->disconnect_hook(conn_s); } if(conn_s->data_fd >= 0) { int toclose = conn_s->data_fd; conn_s->data_fd = -1; fq_debug(FQ_DEBUG_CONN, "close(data_fd)\n"); close(toclose); } conn_s->data_ready = 0; if(conn_s->cmd_hb_ms) { unsigned short hb; hb = conn_s->cmd_hb_ms; conn_s->cmd_hb_ms = 0; fq_client_heartbeat(conn_s, hb); conn_s->cmd_hb_last = 0; } }