app_queue: queue members can receive multiple calls
It was possible for a queue member that is a member of at least 2 or more queues to receive mulitiple calls at the same time. This happened because of a race between when a member was being rung and when the device state notified the other queue(s) member object of the state change. This patch makes it so when a queue member is being rung it gets added to a global pool of queue members. If that same member is tried again, e.g. from another queue, and it is found to already exist in the pending member container then it will not ring that member. ASTERISK-16115 #close Change-Id: I546dd474776d158c2b6be44205353dee5bac7e48
This commit is contained in:
parent
99fcf2a791
commit
30ab21d5fa
1 changed files with 101 additions and 44 deletions
143
apps/app_queue.c
143
apps/app_queue.c
|
@ -1554,7 +1554,6 @@ struct member {
|
|||
struct call_queue *lastqueue; /*!< Last queue we received a call */
|
||||
unsigned int dead:1; /*!< Used to detect members deleted in realtime */
|
||||
unsigned int delme:1; /*!< Flag to delete entry on reload */
|
||||
unsigned int call_pending:1; /*!< TRUE if the Q is attempting to place a call to the member. */
|
||||
char rt_uniqueid[80]; /*!< Unique id of realtime member entry */
|
||||
unsigned int ringinuse:1; /*!< Flag to ring queue members even if their status is 'inuse' */
|
||||
};
|
||||
|
@ -2289,6 +2288,70 @@ static int get_member_status(struct call_queue *q, int max_penalty, int min_pena
|
|||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* A "pool" of member objects that calls are currently pending on. If an
|
||||
* agent is a member of multiple queues it's possible for that agent to be
|
||||
* called by each of the queues at the same time. This happens because device
|
||||
* state is slow to notify the queue app of one of it's member's being rung.
|
||||
* This "pool" allows us to track which members are currently being rung while
|
||||
* we wait on the device state change.
|
||||
*/
|
||||
static struct ao2_container *pending_members;
|
||||
#define MAX_CALL_ATTEMPT_BUCKETS 353
|
||||
|
||||
static int pending_members_hash(const void *obj, const int flags)
|
||||
{
|
||||
const struct member *object;
|
||||
const char *key;
|
||||
|
||||
switch (flags & OBJ_SEARCH_MASK) {
|
||||
case OBJ_SEARCH_KEY:
|
||||
key = obj;
|
||||
break;
|
||||
case OBJ_SEARCH_OBJECT:
|
||||
object = obj;
|
||||
key = object->interface;
|
||||
break;
|
||||
default:
|
||||
ast_assert(0);
|
||||
return 0;
|
||||
}
|
||||
return ast_str_case_hash(key);
|
||||
}
|
||||
|
||||
static int pending_members_cmp(void *obj, void *arg, int flags)
|
||||
{
|
||||
const struct member *object_left = obj;
|
||||
const struct member *object_right = arg;
|
||||
const char *right_key = arg;
|
||||
int cmp;
|
||||
|
||||
switch (flags & OBJ_SEARCH_MASK) {
|
||||
case OBJ_SEARCH_OBJECT:
|
||||
right_key = object_right->interface;
|
||||
/* Fall through */
|
||||
case OBJ_SEARCH_KEY:
|
||||
cmp = strcasecmp(object_left->interface, right_key);
|
||||
break;
|
||||
case OBJ_SEARCH_PARTIAL_KEY:
|
||||
/* Not supported by container. */
|
||||
ast_assert(0);
|
||||
return 0;
|
||||
default:
|
||||
cmp = 0;
|
||||
break;
|
||||
}
|
||||
if (cmp) {
|
||||
return 0;
|
||||
}
|
||||
return CMP_MATCH;
|
||||
}
|
||||
|
||||
static void pending_members_remove(struct member *mem)
|
||||
{
|
||||
ao2_find(pending_members, mem, OBJ_POINTER | OBJ_NODATA | OBJ_UNLINK);
|
||||
}
|
||||
|
||||
/*! \brief set a member's status based on device state of that member's state_interface.
|
||||
*
|
||||
* Lock interface list find sc, iterate through each queues queue_member list for member to
|
||||
|
@ -2298,6 +2361,9 @@ static void update_status(struct call_queue *q, struct member *m, const int stat
|
|||
{
|
||||
m->status = status;
|
||||
|
||||
/* Whatever the status is clear the member from the pending members pool */
|
||||
pending_members_remove(m);
|
||||
|
||||
queue_publish_member_blob(queue_member_status_type(), queue_member_blob_create(q, m));
|
||||
}
|
||||
|
||||
|
@ -3157,6 +3223,7 @@ static void member_add_to_queue(struct call_queue *queue, struct member *mem)
|
|||
*/
|
||||
static void member_remove_from_queue(struct call_queue *queue, struct member *mem)
|
||||
{
|
||||
pending_members_remove(mem);
|
||||
ao2_lock(queue->members);
|
||||
ast_devstate_changed(QUEUE_UNKNOWN_PAUSED_DEVSTATE, AST_DEVSTATE_CACHABLE, "Queue:%s_pause_%s", queue->name, mem->interface);
|
||||
queue_member_follower_removal(queue, mem);
|
||||
|
@ -4133,41 +4200,6 @@ static int member_status_available(int status)
|
|||
return status == AST_DEVICE_NOT_INUSE || status == AST_DEVICE_UNKNOWN;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Clear the member call pending flag.
|
||||
*
|
||||
* \param mem Queue member.
|
||||
*
|
||||
* \return Nothing
|
||||
*/
|
||||
static void member_call_pending_clear(struct member *mem)
|
||||
{
|
||||
ao2_lock(mem);
|
||||
mem->call_pending = 0;
|
||||
ao2_unlock(mem);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Set the member call pending flag.
|
||||
*
|
||||
* \param mem Queue member.
|
||||
*
|
||||
* \retval non-zero if call pending flag was already set.
|
||||
*/
|
||||
static int member_call_pending_set(struct member *mem)
|
||||
{
|
||||
int old_pending;
|
||||
|
||||
ao2_lock(mem);
|
||||
old_pending = mem->call_pending;
|
||||
mem->call_pending = 1;
|
||||
ao2_unlock(mem);
|
||||
|
||||
return old_pending;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Determine if can ring a queue entry.
|
||||
|
@ -4210,12 +4242,31 @@ static int can_ring_entry(struct queue_ent *qe, struct callattempt *call)
|
|||
}
|
||||
|
||||
if (!call->member->ringinuse) {
|
||||
if (member_call_pending_set(call->member)) {
|
||||
ast_debug(1, "%s has another call pending, can't receive call\n",
|
||||
struct member *mem;
|
||||
|
||||
ao2_lock(pending_members);
|
||||
|
||||
mem = ao2_find(pending_members, call->member,
|
||||
OBJ_SEARCH_OBJECT | OBJ_NOLOCK);
|
||||
if (mem) {
|
||||
/*
|
||||
* If found that means this member is currently being attempted
|
||||
* from another calling thread, so stop trying from this thread
|
||||
*/
|
||||
ast_debug(1, "%s has another call trying, can't receive call\n",
|
||||
call->interface);
|
||||
ao2_ref(mem, -1);
|
||||
ao2_unlock(pending_members);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* If not found add it to the container so another queue
|
||||
* won't attempt to call this member at the same time.
|
||||
*/
|
||||
ao2_link(pending_members, call->member);
|
||||
ao2_unlock(pending_members);
|
||||
|
||||
/*
|
||||
* The queue member is available. Get current status to be sure
|
||||
* because the device state and extension state callbacks may
|
||||
|
@ -4224,7 +4275,7 @@ static int can_ring_entry(struct queue_ent *qe, struct callattempt *call)
|
|||
if (!member_status_available(get_queue_member_status(call->member))) {
|
||||
ast_debug(1, "%s actually not available, can't receive call\n",
|
||||
call->interface);
|
||||
member_call_pending_clear(call->member);
|
||||
pending_members_remove(call->member);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -4261,7 +4312,6 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
|
|||
++*busies;
|
||||
return 0;
|
||||
}
|
||||
ast_assert(tmp->member->ringinuse || tmp->member->call_pending);
|
||||
|
||||
ast_copy_string(tech, tmp->interface, sizeof(tech));
|
||||
if ((location = strchr(tech, '/'))) {
|
||||
|
@ -4278,7 +4328,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
|
|||
qe->linpos++;
|
||||
ao2_unlock(qe->parent);
|
||||
|
||||
member_call_pending_clear(tmp->member);
|
||||
pending_members_remove(tmp->member);
|
||||
|
||||
publish_dial_end_event(qe->chan, tmp, NULL, "BUSY");
|
||||
tmp->stillgoing = 0;
|
||||
|
@ -4349,7 +4399,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
|
|||
/* Again, keep going even if there's an error */
|
||||
ast_verb(3, "Couldn't call %s\n", tmp->interface);
|
||||
do_hang(tmp);
|
||||
member_call_pending_clear(tmp->member);
|
||||
pending_members_remove(tmp->member);
|
||||
++*busies;
|
||||
return 0;
|
||||
}
|
||||
|
@ -4369,7 +4419,6 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
|
|||
|
||||
ast_verb(3, "Called %s\n", tmp->interface);
|
||||
|
||||
member_call_pending_clear(tmp->member);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -10934,6 +10983,7 @@ static int unload_module(void)
|
|||
ast_extension_state_del(0, extension_state_cb);
|
||||
|
||||
ast_unload_realtime("queue_members");
|
||||
ao2_cleanup(pending_members);
|
||||
ao2_cleanup(queues);
|
||||
queues = NULL;
|
||||
return 0;
|
||||
|
@ -10962,6 +11012,13 @@ static int load_module(void)
|
|||
return AST_MODULE_LOAD_DECLINE;
|
||||
}
|
||||
|
||||
pending_members = ao2_container_alloc(
|
||||
MAX_CALL_ATTEMPT_BUCKETS, pending_members_hash, pending_members_cmp);
|
||||
if (!pending_members) {
|
||||
unload_module();
|
||||
return AST_MODULE_LOAD_DECLINE;
|
||||
}
|
||||
|
||||
use_weight = 0;
|
||||
|
||||
if (reload_handler(0, &mask, NULL)) {
|
||||
|
|
Loading…
Reference in a new issue