aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormmichelson <mmichelson@f38db490-d61c-443f-a65b-d21fe96a405b>2008-10-06 15:29:56 +0000
committermmichelson <mmichelson@f38db490-d61c-443f-a65b-d21fe96a405b>2008-10-06 15:29:56 +0000
commitfe8e13cc848beb2f3e2b79336ba61ef4dd42a5aa (patch)
tree2b66ca6cb623d70b12da9692c60713a3ff85a29f
parentd75f767ab866ea483fc9e8dbb97b541b2729ec43 (diff)
This commit introduces a change to how the "joinempty"
and "leavewhenempty" options are configured in queues.conf. Instead of using vague terms like "yes," "no," "loose," and "strict," we now accept a comma-separated list of values to determine when to consider a member available. Extended details can be found in the queues.conf.sample file. Note also that the above four referenced values are still accepted for backwards-compatibility, but are mapped internally to the new method of representing the option. AST-105 git-svn-id: http://svn.digium.com/svn/asterisk/trunk@146640 f38db490-d61c-443f-a65b-d21fe96a405b
-rw-r--r--CHANGES4
-rw-r--r--apps/app_queue.c184
-rw-r--r--configs/queues.conf.sample57
3 files changed, 144 insertions, 101 deletions
diff --git a/CHANGES b/CHANGES
index 9ea51775a..bf92583ea 100644
--- a/CHANGES
+++ b/CHANGES
@@ -607,6 +607,10 @@ Queue changes
when a realtime queue member is removed. Since there is no calling channel associated
with these events, the string "REALTIME" is placed where the channel's unique id
is typically placed.
+ * The configuration method for the "joinempty" and "leavewhenempty" options has
+ changed to a comma-separated list of methods of determining member availability
+ instead of vague terms such as "yes," "loose," "no," and "strict." These old four
+ values are still accepted for backwards-compatibility, though.
MeetMe Changes
--------------
diff --git a/apps/app_queue.c b/apps/app_queue.c
index a4704bcc9..1ca3244f9 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -392,10 +392,18 @@ struct member {
char rt_uniqueid[80]; /*!< Unique id of realtime member entry */
};
+enum empty_conditions {
+ QUEUE_EMPTY_PENALTY = (1 << 0),
+ QUEUE_EMPTY_PAUSED = (1 << 1),
+ QUEUE_EMPTY_INUSE = (1 << 2),
+ QUEUE_EMPTY_RINGING = (1 << 3),
+ QUEUE_EMPTY_UNAVAILABLE = (1 << 4),
+ QUEUE_EMPTY_INVALID = (1 << 5),
+ QUEUE_EMPTY_UNKNOWN = (1 << 6),
+ QUEUE_EMPTY_WRAPUP = (1 << 7),
+};
+
/* values used in multi-bit flags in call_queue */
-#define QUEUE_EMPTY_NORMAL 1
-#define QUEUE_EMPTY_STRICT 2
-#define QUEUE_EMPTY_LOOSE 3
#define ANNOUNCEHOLDTIME_ALWAYS 1
#define ANNOUNCEHOLDTIME_ONCE 2
#define QUEUE_EVENT_VARIABLES 3
@@ -458,9 +466,7 @@ struct call_queue {
/*! Sound files: Custom announce, no default */
struct ast_str *sound_periodicannounce[MAX_PERIODIC_ANNOUNCEMENTS];
unsigned int dead:1;
- unsigned int joinempty:2;
unsigned int eventwhencalled:2;
- unsigned int leavewhenempty:2;
unsigned int ringinuse:1;
unsigned int setinterfacevar:1;
unsigned int setqueuevar:1;
@@ -474,6 +480,8 @@ struct call_queue {
unsigned int maskmemberstatus:1;
unsigned int realtime:1;
unsigned int found:1;
+ enum empty_conditions joinempty;
+ enum empty_conditions leavewhenempty;
int announcepositionlimit; /*!< How many positions we announce? */
int announcefrequency; /*!< How often to announce their position */
int minannouncefrequency; /*!< The minimum number of seconds between position announcements (def. 15) */
@@ -630,53 +638,67 @@ static inline void insert_entry(struct call_queue *q, struct queue_ent *prev, st
new->opos = *pos;
}
-enum queue_member_status {
- QUEUE_NO_MEMBERS,
- QUEUE_NO_REACHABLE_MEMBERS,
- QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS,
- QUEUE_NORMAL
-};
-
/*! \brief Check if members are available
*
* This function checks to see if members are available to be called. If any member
- * is available, the function immediately returns QUEUE_NORMAL. If no members are available,
- * the appropriate reason why is returned
+ * is available, the function immediately returns 0. If no members are available,
+ * then -1 is returned.
*/
-static enum queue_member_status get_member_status(struct call_queue *q, int max_penalty, int min_penalty)
+static int get_member_status(struct call_queue *q, int max_penalty, int min_penalty, enum empty_conditions conditions)
{
struct member *member;
struct ao2_iterator mem_iter;
- enum queue_member_status result = QUEUE_NO_MEMBERS;
ao2_lock(q);
mem_iter = ao2_iterator_init(q->members, 0);
for (; (member = ao2_iterator_next(&mem_iter)); ao2_ref(member, -1)) {
- if ((max_penalty && (member->penalty > max_penalty)) || (min_penalty && (member->penalty < min_penalty)))
- continue;
+ if ((max_penalty && (member->penalty > max_penalty)) || (min_penalty && (member->penalty < min_penalty))) {
+ if (conditions & QUEUE_EMPTY_PENALTY) {
+ ast_debug(4, "%s is unavailable because his penalty is not between %d and %d\n", member->membername, min_penalty, max_penalty);
+ continue;
+ }
+ }
switch (member->status) {
case AST_DEVICE_INVALID:
- /* nothing to do */
- break;
+ if (conditions & QUEUE_EMPTY_INVALID) {
+ ast_debug(4, "%s is unavailable because his device state is 'invalid'\n", member->membername);
+ break;
+ }
case AST_DEVICE_UNAVAILABLE:
- if (result != QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS)
- result = QUEUE_NO_REACHABLE_MEMBERS;
- break;
+ if (conditions & QUEUE_EMPTY_UNAVAILABLE) {
+ ast_debug(4, "%s is unavailable because his device state is 'unavailable'\n", member->membername);
+ break;
+ }
+ case AST_DEVICE_INUSE:
+ if (conditions & QUEUE_EMPTY_INUSE) {
+ ast_debug(4, "%s is unavailable because his device state is 'inuse'\n", member->membername);
+ break;
+ }
+ case AST_DEVICE_UNKNOWN:
+ if (conditions & QUEUE_EMPTY_UNKNOWN) {
+ ast_debug(4, "%s is unavailable because his device state is 'unknown'\n", member->membername);
+ break;
+ }
default:
- if (member->paused) {
- result = QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS;
+ if (member->paused && (conditions & QUEUE_EMPTY_PAUSED)) {
+ ast_debug(4, "%s is unavailable because he is paused'\n", member->membername);
+ break;
+ } else if ((conditions & QUEUE_EMPTY_WRAPUP) && member->lastcall && q->wrapuptime && (time(NULL) - q->wrapuptime < member->lastcall)) {
+ ast_debug(4, "%s is unavailable because it has only been %d seconds since his last call (wrapup time is %d)\n", member->membername, (int) (time(NULL) - member->lastcall), q->wrapuptime);
+ break;
} else {
ao2_unlock(q);
ao2_ref(member, -1);
- return QUEUE_NORMAL;
+ ast_debug(4, "%s is available.\n", member->membername);
+ return 0;
}
break;
}
}
ao2_unlock(q);
- return result;
+ return -1;
}
struct statechange {
@@ -1000,6 +1022,39 @@ static int insert_penaltychange (const char *list_name, const char *content, con
return 0;
}
+static void parse_empty_options(const char *value, enum empty_conditions *empty)
+{
+ char *value_copy = ast_strdupa(value);
+ char *option = NULL;
+ while ((option = strsep(&value_copy, ","))) {
+ if (!strcasecmp(option, "paused")) {
+ *empty |= QUEUE_EMPTY_PAUSED;
+ } else if (!strcasecmp(option, "penalty")) {
+ *empty |= QUEUE_EMPTY_PENALTY;
+ } else if (!strcasecmp(option, "inuse")) {
+ *empty |= QUEUE_EMPTY_INUSE;
+ } else if (!strcasecmp(option, "ringing")) {
+ *empty |= QUEUE_EMPTY_RINGING;
+ } else if (!strcasecmp(option, "invalid")) {
+ *empty |= QUEUE_EMPTY_INVALID;
+ } else if (!strcasecmp(option, "wrapup")) {
+ *empty |= QUEUE_EMPTY_WRAPUP;
+ } else if (!strcasecmp(option, "unavailable")) {
+ *empty |= QUEUE_EMPTY_UNAVAILABLE;
+ } else if (!strcasecmp(option, "unknown")) {
+ *empty |= QUEUE_EMPTY_UNKNOWN;
+ } else if (!strcasecmp(option, "loose")) {
+ *empty = (QUEUE_EMPTY_PENALTY | QUEUE_EMPTY_INVALID);
+ } else if (!strcasecmp(option, "strict")) {
+ *empty = (QUEUE_EMPTY_PENALTY | QUEUE_EMPTY_INVALID | QUEUE_EMPTY_PAUSED | QUEUE_EMPTY_UNAVAILABLE);
+ } else if (ast_false(option)) {
+ *empty = (QUEUE_EMPTY_PENALTY | QUEUE_EMPTY_INVALID | QUEUE_EMPTY_PAUSED);
+ } else if (ast_true(option)) {
+ *empty = 0;
+ }
+ }
+}
+
/*! \brief Configure a queue parameter.
*
* The failunknown flag is set for config files (and static realtime) to show
@@ -1142,23 +1197,9 @@ static void queue_set_param(struct call_queue *q, const char *param, const char
/* We already have set this, no need to do it again */
return;
} else if (!strcasecmp(param, "joinempty")) {
- if (!strcasecmp(val, "loose"))
- q->joinempty = QUEUE_EMPTY_LOOSE;
- else if (!strcasecmp(val, "strict"))
- q->joinempty = QUEUE_EMPTY_STRICT;
- else if (ast_true(val))
- q->joinempty = QUEUE_EMPTY_NORMAL;
- else
- q->joinempty = 0;
+ parse_empty_options(val, &q->joinempty);
} else if (!strcasecmp(param, "leavewhenempty")) {
- if (!strcasecmp(val, "loose"))
- q->leavewhenempty = QUEUE_EMPTY_LOOSE;
- else if (!strcasecmp(val, "strict"))
- q->leavewhenempty = QUEUE_EMPTY_STRICT;
- else if (ast_true(val))
- q->leavewhenempty = QUEUE_EMPTY_NORMAL;
- else
- q->leavewhenempty = 0;
+ parse_empty_options(val, &q->leavewhenempty);
} else if (!strcasecmp(param, "eventmemberstatus")) {
q->maskmemberstatus = !ast_true(val);
} else if (!strcasecmp(param, "eventwhencalled")) {
@@ -1558,7 +1599,6 @@ static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *
int res = -1;
int pos = 0;
int inserted = 0;
- enum queue_member_status status;
if (!(q = load_realtime_queue(queuename)))
return res;
@@ -1567,14 +1607,14 @@ static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *
ao2_lock(q);
/* This is our one */
- if (q->joinempty != QUEUE_EMPTY_NORMAL) {
- status = get_member_status(q, qe->max_penalty, qe->min_penalty);
- if (!q->joinempty && (status == QUEUE_NO_MEMBERS))
+ if (q->joinempty) {
+ int status = 0;
+ if ((status = get_member_status(q, qe->max_penalty, qe->min_penalty, q->joinempty))) {
*reason = QUEUE_JOINEMPTY;
- else if ((q->joinempty == QUEUE_EMPTY_STRICT) && (status == QUEUE_NO_REACHABLE_MEMBERS || status == QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS || status == QUEUE_NO_MEMBERS))
- *reason = QUEUE_JOINUNAVAIL;
- else if ((q->joinempty == QUEUE_EMPTY_LOOSE) && (status == QUEUE_NO_REACHABLE_MEMBERS || status == QUEUE_NO_MEMBERS))
- *reason = QUEUE_JOINUNAVAIL;
+ ao2_unlock(q);
+ ao2_unlock(queues);
+ return res;
+ }
}
if (*reason == QUEUE_UNKNOWN && q->maxlen && (q->count >= q->maxlen))
*reason = QUEUE_FULL;
@@ -2735,7 +2775,6 @@ static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *r
/* This is the holding pen for callers 2 through maxlen */
for (;;) {
- enum queue_member_status status;
if (is_our_turn(qe))
break;
@@ -2747,29 +2786,14 @@ static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *r
}
if (qe->parent->leavewhenempty) {
- status = get_member_status(qe->parent, qe->max_penalty, qe->min_penalty);
+ int status = 0;
- /* leave the queue if no agents, if enabled */
- if (status == QUEUE_NO_MEMBERS) {
+ if ((status = get_member_status(qe->parent, qe->max_penalty, qe->min_penalty, qe->parent->leavewhenempty))) {
*reason = QUEUE_LEAVEEMPTY;
ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long) time(NULL) - qe->start);
leave_queue(qe);
break;
}
-
- /* leave the queue if no reachable agents, if enabled */
- if ((qe->parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (status == QUEUE_NO_REACHABLE_MEMBERS || status == QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS)) {
- *reason = QUEUE_LEAVEUNAVAIL;
- ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long) time(NULL) - qe->start);
- leave_queue(qe);
- break;
- }
- if ((qe->parent->leavewhenempty == QUEUE_EMPTY_LOOSE) && (status == QUEUE_NO_REACHABLE_MEMBERS)) {
- *reason = QUEUE_LEAVEUNAVAIL;
- ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long) time(NULL) - qe->start);
- leave_queue(qe);
- break;
- }
}
/* Make a position announcement, if enabled */
@@ -4607,8 +4631,6 @@ check_turns:
/* they may dial a digit from the queue context; */
/* or, they may timeout. */
- enum queue_member_status status;
-
/* Leave if we have exceeded our queuetimeout */
if (qe.expire && (time(NULL) >= qe.expire)) {
record_abandoned(&qe);
@@ -4653,30 +4675,14 @@ check_turns:
}
if (qe.parent->leavewhenempty) {
- status = get_member_status(qe.parent, qe.max_penalty, qe.min_penalty);
- /* leave the queue if no agents, if enabled */
- if (status == QUEUE_NO_MEMBERS) {
+ int status = 0;
+ if ((status = get_member_status(qe.parent, qe.max_penalty, qe.min_penalty, qe.parent->leavewhenempty))) {
record_abandoned(&qe);
reason = QUEUE_LEAVEEMPTY;
ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
res = 0;
break;
}
-
- /* leave the queue if no reachable agents, if enabled */
- if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (status == QUEUE_NO_REACHABLE_MEMBERS || status == QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS)) {
- record_abandoned(&qe);
- reason = QUEUE_LEAVEUNAVAIL;
- ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
- res = 0;
- break;
- }
- if ((qe.parent->leavewhenempty == QUEUE_EMPTY_LOOSE) && (status == QUEUE_NO_REACHABLE_MEMBERS)) {
- record_abandoned(&qe);
- reason = QUEUE_LEAVEUNAVAIL;
- res = 0;
- break;
- }
}
/* exit after 'timeout' cycle if 'n' option enabled */
diff --git a/configs/queues.conf.sample b/configs/queues.conf.sample
index 8e3b96d2b..e3e434f89 100644
--- a/configs/queues.conf.sample
+++ b/configs/queues.conf.sample
@@ -365,25 +365,58 @@ shared_lastcall=no
; The contents of MONITOR_FILENAME will also be unescaped from ^{X} to ${X} and
; all variables will be evaluated just prior to recording being started.
;
+; ---------------------- Queue Empty Options ----------------------------------
+;
+; Asterisk has provided the "joinempty" and "leavewhenempty" options for a while
+; with tenuous definitions of what they actually mean. The "joinempty" option controls
+; whether a caller may join a queue depending on several factors of member availability.
+; Similarly, then leavewhenempty option controls whether a caller may remain in a queue
+; he has already joined. Both options take a comma-separated list of factors which
+; contribute towards whether a caller may join/remain in the queue. The list of
+; factors which contribute to these option is as follows:
+;
+; paused: a member is not considered available if he is paused
+; penalty: a member is not considered available if his penalty is less than QUEUE_MAX_PENALTY
+; inuse: a member is not considered available if he is currently on a call
+; ringing: a member is not considered available if his phone is currently ringing
+; unavailable: This applies mainly to Agent channels. If the agent is a member of the queue
+; but has not logged in, then do not consider the member to be available
+; invalid: Do not consider a member to be available if he has an "invalid" device state.
+; This generally is caused by an error condition in the member's channel driver.
+; unknown: Do not consider a member to be available if we are unable to determine the member's
+; current device state.
+; wrapup: A member is not considered available if he is currently in his wrapuptime after
+; taking a call.
+;
+; For the "joinempty" option, when a caller attempts to enter a queue, the members of that
+; queue are examined. If all members are deemed to be unavailable due to any of the conditions
+; listed for the "joinempty" option, then the caller will be unable to enter the queue. For the
+; "leavewhenempty" option, the state of the members of the queue are checked periodically during
+; the caller's stay in the queue. If all of the members are unavailable due to any of the above
+; conditions, then the caller will be removed from the queue.
+;
+; Some examples:
;
-; This setting controls whether callers can join a queue with no members. There
-; are three choices:
-;
-; yes - callers can join a queue with no members or only unavailable members
-; no - callers cannot join a queue with no members
-; strict - callers cannot join a queue with no members or only unavailable
-; members
-; loose - same as strict, but paused queue members do not count as unavailable
+;joinempty = paused,inuse,invalid
;
-; joinempty = yes
+; A caller will not be able to enter a queue if at least one member cannot be found
+; who is not paused, on the phone, or who has an invalid device state.
;
+;leavewhenempty = inuse,ringing
;
-; If you wish to remove callers from the queue when new callers cannot join,
-; set this setting to one of the same choices for 'joinempty'
+; A caller will be removed from the queue if at least one member cannot be found
+; who is not on the phone, or whose phone is not ringing.
;
-; leavewhenempty = yes
+; For the sake of backwards-compatibility, the joinempty and leavewhenempty
+; options also accept the strings "yes" "no" "strict" and "loose". The following
+; serves as a translation for these values:
;
+; yes - (empty) for joinempty; penalty,paused,invalid for leavewhenempty
+; no - penalty,paused,invalid for joinempty; (empty) for leavewhenempty
+; strict - penalty,paused,invalid,unavailable
+; loose - penalty,invalid
;
+
; If this is set to yes, the following manager events will be generated:
; AgentCalled, AgentDump, AgentConnect, AgentComplete; setting this to
; vars also sends all channel variables with the event.