]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
app_queue: Add force_longest_waiting_caller option.
authorNathan Bruning <nathan.bruning@talksome.com>
Tue, 24 Jan 2023 13:50:39 +0000 (14:50 +0100)
committerasterisk-org-access-app[bot] <120671045+asterisk-org-access-app[bot]@users.noreply.github.com>
Mon, 12 Jun 2023 18:20:33 +0000 (18:20 +0000)
This adds an option 'force_longest_waiting_caller' which changes the
global behavior of the queue engine to prevent queue callers from
'jumping ahead' when an agent is in multiple queues.

Resolves: #108

Also closes old asterisk issues:
- ASTERISK-17732
- ASTERISK-17570

Change-Id: I0f84e27903fefbe2018d0afa2d67b23aa0b321ce

apps/app_queue.c
configs/samples/queues.conf.sample

index f138cd590cab0bc87512f8dca9d16ec88979ddda..97143d7ae9a45641f81fc3e45ecae2e2f7cf68ab 100644 (file)
@@ -1603,6 +1603,9 @@ static int negative_penalty_invalid;
 /*! \brief queues.conf [general] option */
 static int log_membername_as_agent;
 
+/*! \brief queues.conf [general] option */
+static int force_longest_waiting_caller;
+
 /*! \brief name of the ringinuse field in the realtime database */
 static char *realtime_ringinuse_field;
 
@@ -4549,6 +4552,56 @@ static int compare_weight(struct call_queue *rq, struct member *member)
        return found;
 }
 
+static int is_longest_waiting_caller(struct queue_ent *caller, struct member *member)
+{
+       struct call_queue *q;
+       struct member *mem;
+       int is_longest_waiting = 1;
+       struct ao2_iterator queue_iter;
+       struct queue_ent *ch;
+
+       queue_iter = ao2_iterator_init(queues, 0);
+       while ((q = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) {
+               if (q == caller->parent) { /* don't check myself, could deadlock */
+                       queue_t_unref(q, "Done with iterator");
+                       continue;
+               }
+               ao2_lock(q);
+               /*
+                * If the other queue has equal weight, see if we should let that handle
+                * their call first. If weights are not equal, compare_weights will step in.
+                */
+               if (q->weight == caller->parent->weight && q->count && q->members) {
+                       if ((mem = ao2_find(q->members, member, OBJ_POINTER))) {
+                               ast_debug(2, "Found matching member %s in queue '%s'\n", mem->interface, q->name);
+
+                               /* Does this queue have a caller that's been waiting longer? */
+                               ch = q->head;
+                               while (ch) {
+                                       /* If ch->pending, the other call (which may be waiting for a longer period of time),
+                                        * is already ringing at another agent. Ignore such callers; otherwise, all agents
+                                        * will be unused until the first caller is picked up.
+                                        */
+                                       if (ch->start < caller->start && !ch->pending) {
+                                               ast_debug(1, "Queue %s has a call at position %i that's been waiting longer (%li vs %li)\n",
+                                                                 q->name, ch->pos, ch->start, caller->start);
+                                               is_longest_waiting = 0;
+                                               break;
+                                       }
+                                       ch = ch->next;
+                               }
+                       }
+               }
+               ao2_unlock(q);
+               queue_t_unref(q, "Done with iterator");
+               if (!is_longest_waiting) {
+                       break;
+               }
+       }
+       ao2_iterator_destroy(&queue_iter);
+       return is_longest_waiting;
+}
+
 /*! \brief common hangup actions */
 static void do_hang(struct callattempt *o)
 {
@@ -4613,6 +4666,12 @@ static int can_ring_entry(struct queue_ent *qe, struct callattempt *call)
                return 0;
        }
 
+       if (force_longest_waiting_caller && !is_longest_waiting_caller(qe, memberp)) {
+               ast_debug(1, "Another caller was waiting longer; delaying call to %s:%s\n",
+                                 qe->parent->name, call->interface);
+               return 0;
+       }
+
        if (!memberp->ringinuse) {
                struct member *mem;
 
@@ -9429,6 +9488,7 @@ static void queue_reset_global_params(void)
        shared_lastcall = 0;
        negative_penalty_invalid = 0;
        log_membername_as_agent = 0;
+       force_longest_waiting_caller = 0;
 }
 
 /*! Set the global queue parameters as defined in the "general" section of queues.conf */
@@ -9454,6 +9514,9 @@ static void queue_set_global_params(struct ast_config *cfg)
        if ((general_val = ast_variable_retrieve(cfg, "general", "log_membername_as_agent"))) {
                log_membername_as_agent = ast_true(general_val);
        }
+       if ((general_val = ast_variable_retrieve(cfg, "general", "force_longest_waiting_caller"))) {
+               force_longest_waiting_caller = ast_true(general_val);
+       }
 }
 
 /*! \brief reload information pertaining to a single member
index 0987236cd695d815b3227e1cc879a7877549e9aa..ee49b7ce6c2e92fde0ba7180a03578c376706f58 100644 (file)
@@ -51,6 +51,14 @@ monitor-type = MixMonitor
 ;
 ;log_membername_as_agent = no
 ;
+; force_longest_waiting_caller will cause app_queue to make sure callers are offered
+; in order (longest waiting first), even for callers across multiple queues.
+; Before a call is offered to an agent, an additional check is made to see if the agent
+; is a member of another queue with a call that's been waiting longer. If so, the current
+; call is not offered to the agent. The default value is 'no'.
+;
+;force_longest_waiting_caller = no
+;
 ;[markq]
 ;
 ; A sample call queue