+static int
+timespec_cmp(const struct timespec *a, const struct timespec *b)
+{
+ if (a->tv_sec < b->tv_sec)
+ return -1;
+ if (a->tv_sec > b->tv_sec)
+ return 1;
+ if (a->tv_nsec < b->tv_nsec)
+ return -1;
+ if (a->tv_nsec > b->tv_nsec)
+ return 1;
+ return 0;
+}
+
+enum async_action {
+ ACTION_NONE, /**< don't do anything */
+ ACTION_FREE, /**< free the action entry, but don't trigger callback */
+ ACTION_TRIGGER /**< trigger callback, then free action entry */
+};
+
+static enum async_action
+process_async_request(struct pending_request *sr, const struct timespec *now)
+{
+ struct async_request_param *param;
+ struct rte_mp_reply *reply;
+ bool timeout, received, last_msg;
+
+ param = sr->async.param;
+ reply = ¶m->user_reply;
+
+ /* did we timeout? */
+ timeout = timespec_cmp(¶m->end, now) <= 0;
+
+ /* did we receive a response? */
+ received = sr->reply_received != 0;
+
+ /* if we didn't time out, and we didn't receive a response, ignore */
+ if (!timeout && !received)
+ return ACTION_NONE;
+
+ /* if we received a response, adjust relevant data and copy mesasge. */
+ if (sr->reply_received == 1 && sr->reply) {
+ struct rte_mp_msg *msg, *user_msgs, *tmp;
+
+ msg = sr->reply;
+ user_msgs = reply->msgs;
+
+ tmp = realloc(user_msgs, sizeof(*msg) *
+ (reply->nb_received + 1));
+ if (!tmp) {
+ RTE_LOG(ERR, EAL, "Fail to alloc reply for request %s:%s\n",
+ sr->dst, sr->request->name);
+ /* this entry is going to be removed and its message
+ * dropped, but we don't want to leak memory, so
+ * continue.
+ */
+ } else {
+ user_msgs = tmp;
+ reply->msgs = user_msgs;
+ memcpy(&user_msgs[reply->nb_received],
+ msg, sizeof(*msg));
+ reply->nb_received++;
+ }
+
+ /* mark this request as processed */
+ param->n_responses_processed++;
+ } else if (sr->reply_received == -1) {
+ /* we were asked to ignore this process */
+ reply->nb_sent--;
+ } else if (timeout) {
+ /* count it as processed response, but don't increment
+ * nb_received.
+ */
+ param->n_responses_processed++;
+ }
+
+ free(sr->reply);
+
+ last_msg = param->n_responses_processed == reply->nb_sent;
+
+ return last_msg ? ACTION_TRIGGER : ACTION_FREE;
+}
+
+static void
+trigger_async_action(struct pending_request *sr)
+{
+ struct async_request_param *param;
+ struct rte_mp_reply *reply;
+
+ param = sr->async.param;
+ reply = ¶m->user_reply;
+
+ param->clb(sr->request, reply);
+
+ /* clean up */
+ free(sr->async.param->user_reply.msgs);
+ free(sr->async.param);
+ free(sr->request);
+}
+
+static struct pending_request *
+check_trigger(struct timespec *ts)
+{
+ struct pending_request *next, *cur, *trigger = NULL;
+
+ TAILQ_FOREACH_SAFE(cur, &pending_requests.requests, next, next) {
+ enum async_action action;
+ if (cur->type != REQUEST_TYPE_ASYNC)
+ continue;
+
+ action = process_async_request(cur, ts);
+ if (action == ACTION_FREE) {
+ TAILQ_REMOVE(&pending_requests.requests, cur, next);
+ free(cur);
+ } else if (action == ACTION_TRIGGER) {
+ TAILQ_REMOVE(&pending_requests.requests, cur, next);
+ trigger = cur;
+ break;
+ }
+ }
+ return trigger;
+}
+
+static void
+wait_for_async_messages(void)
+{
+ struct pending_request *sr;
+ struct timespec timeout;
+ bool timedwait = false;
+ bool nowait = false;
+ int ret;
+
+ /* scan through the list and see if there are any timeouts that
+ * are earlier than our current timeout.
+ */
+ TAILQ_FOREACH(sr, &pending_requests.requests, next) {
+ if (sr->type != REQUEST_TYPE_ASYNC)
+ continue;
+ if (!timedwait || timespec_cmp(&sr->async.param->end,
+ &timeout) < 0) {
+ memcpy(&timeout, &sr->async.param->end,
+ sizeof(timeout));
+ timedwait = true;
+ }
+
+ /* sometimes, we don't even wait */
+ if (sr->reply_received) {
+ nowait = true;
+ break;
+ }
+ }
+
+ if (nowait)
+ return;
+
+ do {
+ ret = timedwait ?
+ pthread_cond_timedwait(
+ &pending_requests.async_cond,
+ &pending_requests.lock,
+ &timeout) :
+ pthread_cond_wait(
+ &pending_requests.async_cond,
+ &pending_requests.lock);
+ } while (ret != 0 && ret != ETIMEDOUT);
+
+ /* we've been woken up or timed out */
+}
+
+static void *
+async_reply_handle(void *arg __rte_unused)
+{
+ struct timeval now;
+ struct timespec ts_now;
+ while (1) {
+ struct pending_request *trigger = NULL;
+
+ pthread_mutex_lock(&pending_requests.lock);
+
+ /* we exit this function holding the lock */
+ wait_for_async_messages();
+
+ if (gettimeofday(&now, NULL) < 0) {
+ pthread_mutex_unlock(&pending_requests.lock);
+ RTE_LOG(ERR, EAL, "Cannot get current time\n");
+ break;
+ }
+ ts_now.tv_nsec = now.tv_usec * 1000;
+ ts_now.tv_sec = now.tv_sec;
+
+ do {
+ trigger = check_trigger(&ts_now);
+ /* unlock request list */
+ pthread_mutex_unlock(&pending_requests.lock);
+
+ if (trigger) {
+ trigger_async_action(trigger);
+ free(trigger);
+
+ /* we've triggered a callback, but there may be
+ * more, so lock the list and check again.
+ */
+ pthread_mutex_lock(&pending_requests.lock);
+ }
+ } while (trigger);
+ }
+
+ RTE_LOG(ERR, EAL, "ERROR: asynchronous requests disabled\n");
+
+ return NULL;
+}
+