From 2be01ba40bf8d682b05c1df2039d7b0a63cc86fe Mon Sep 17 00:00:00 2001 From: Kfir Itzhak Date: Wed, 9 Feb 2022 12:28:29 +0200 Subject: [PATCH] app_queue: Add QueueWithdrawCaller AMI action This adds a new AMI action called QueueWithdrawCaller. This AMI action makes it possible to withdraw a caller from a queue, in a safe and a generic manner. This can be useful for retrieving a specific call and dispatching it to a specific extension. It works by signaling the caller to exit the queue application whenever it can. Therefore, it is not guaranteed that the call will leave the queue. ASTERISK-29909 #close Change-Id: Ic15aa238e23b2884abdcaadff2fda7679e29b7ec --- apps/app_queue.c | 146 +++++++++++++++++- doc/CHANGES-staging/queue_withdraw_caller.txt | 14 ++ 2 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 doc/CHANGES-staging/queue_withdraw_caller.txt diff --git a/apps/app_queue.c b/apps/app_queue.c index 8ec98e23e0..35054130a4 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -290,6 +290,7 @@ + If the call was not answered by an agent this variable will be TRUE. @@ -298,6 +299,9 @@ Resource of the agent that was dialed set on the outbound channel. + + If the call was successfully withdrawn from the queue, and the withdraw request was provided with optional withdraw info, the withdraw info will be stored in this variable. + @@ -1057,6 +1061,25 @@ + + + Request to withdraw a caller from the queue back to the dialplan. + + + + + The name of the queue to take action on. + + + The caller (channel) to withdraw from the queue. + + + Optional info to store. If the call is successfully withdrawn from the queue, this information will be available in the QUEUE_WITHDRAW_INFO variable. + + + + + @@ -1602,6 +1625,7 @@ enum queue_result { QUEUE_LEAVEUNAVAIL = 5, QUEUE_FULL = 6, QUEUE_CONTINUE = 7, + QUEUE_WITHDRAW = 8, }; static const struct { @@ -1616,6 +1640,7 @@ static const struct { { QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" }, { QUEUE_FULL, "FULL" }, { QUEUE_CONTINUE, "CONTINUE" }, + { QUEUE_WITHDRAW, "WITHDRAW" }, }; enum queue_timeout_priority { @@ -1684,6 +1709,8 @@ struct queue_ent { time_t start; /*!< When we started holding */ time_t expire; /*!< When this entry should expire (time out of queue) */ int cancel_answered_elsewhere; /*!< Whether we should force the CAE flag on this call (C) option*/ + unsigned int withdraw:1; /*!< Should this call exit the queue at its next iteration? Used for QueueWithdrawCaller */ + char *withdraw_info; /*!< Optional info passed by the caller of QueueWithdrawCaller */ struct ast_channel *chan; /*!< Our channel */ AST_LIST_HEAD_NOLOCK(,penalty_rule) qe_rules; /*!< Local copy of the queue's penalty rules */ struct penalty_rule *pr; /*!< Pointer to the next penalty rule to implement */ @@ -5802,6 +5829,13 @@ 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 (;;) { + /* A request to withdraw this call from the queue arrived */ + if (qe->withdraw) { + *reason = QUEUE_WITHDRAW; + res = 1; + break; + } + if (is_our_turn(qe)) { break; } @@ -7622,6 +7656,51 @@ static int change_priority_caller_on_queue(const char *queuename, const char *ca } +/*! \brief Request to withdraw a caller from a queue + * \retval RES_NOSUCHQUEUE queue does not exist + * \retval RES_OKAY withdraw request sent + * \retval RES_NOT_CALLER queue exists but no caller + * \retval RES_EXISTS a withdraw request was already sent for this caller (channel) and queue +*/ +static int request_withdraw_caller_from_queue(const char *queuename, const char *caller, const char *withdraw_info) +{ + struct call_queue *q; + struct queue_ent *qe; + int res = RES_NOSUCHQUEUE; + + /*! \note Ensure the appropriate realtime queue is loaded. Note that this + * short-circuits if the queue is already in memory. */ + if (!(q = find_load_queue_rt_friendly(queuename))) { + return res; + } + + ao2_lock(q); + res = RES_NOT_CALLER; + for (qe = q->head; qe; qe = qe->next) { + if (!strcmp(ast_channel_name(qe->chan), caller)) { + if (qe->withdraw) { + ast_debug(1, "Ignoring duplicate withdraw request of caller %s from queue %s\n", caller, queuename); + res = RES_EXISTS; + } else { + ast_debug(1, "Requested withdraw of caller %s from queue %s\n", caller, queuename); + /* It is not possible to change the withdraw info by further withdraw requests for this caller (channel) + in this queue, so we do not need to worry about a memory leak here. */ + if (withdraw_info) { + qe->withdraw_info = ast_strdup(withdraw_info); + } + qe->withdraw = 1; + res = RES_OKAY; + } + break; + } + } + ao2_unlock(q); + queue_unref(q); + + return res; +} + + static int publish_queue_member_pause(struct call_queue *q, struct member *member) { struct ast_json *json_blob = queue_member_blob_create(q, member); @@ -8569,6 +8648,13 @@ check_turns: /* they may dial a digit from the queue context; */ /* or, they may timeout. */ + /* A request to withdraw this call from the queue arrived */ + if (qe.withdraw) { + reason = QUEUE_WITHDRAW; + res = 1; + break; + } + /* Leave if we have exceeded our queuetimeout */ if (qe.expire && (time(NULL) >= qe.expire)) { record_abandoned(&qe); @@ -8596,6 +8682,13 @@ check_turns: } } + /* A request to withdraw this call from the queue arrived */ + if (qe.withdraw) { + reason = QUEUE_WITHDRAW; + res = 1; + break; + } + /* Leave if we have exceeded our queuetimeout */ if (qe.expire && (time(NULL) >= qe.expire)) { record_abandoned(&qe); @@ -8670,7 +8763,14 @@ check_turns: stop: if (res) { - if (res < 0) { + if (reason == QUEUE_WITHDRAW) { + record_abandoned(&qe); + ast_queue_log(qe.parent->name, ast_channel_uniqueid(qe.chan), "NONE", "WITHDRAW", "%d|%d|%ld|%.40s", qe.pos, qe.opos, (long) (time(NULL) - qe.start), qe.withdraw_info ? qe.withdraw_info : ""); + if (qe.withdraw_info) { + pbx_builtin_setvar_helper(qe.chan, "QUEUE_WITHDRAW_INFO", qe.withdraw_info); + } + res = 0; + } else if (res < 0) { if (!qe.handled) { record_abandoned(&qe); ast_queue_log(args.queuename, ast_channel_uniqueid(chan), "NONE", "ABANDON", @@ -8690,6 +8790,13 @@ stop: } } + /* Free the optional withdraw info if present */ + /* This is done here to catch all cases. e.g. if the call eventually wasn't withdrawn, e.g. answered */ + if (qe.withdraw_info) { + ast_free(qe.withdraw_info); + qe.withdraw_info = NULL; + } + /* Don't allow return code > 0 */ if (res >= 0) { res = 0; @@ -10743,6 +10850,41 @@ static int manager_change_priority_caller_on_queue(struct mansession *s, const s return 0; } +static int manager_request_withdraw_caller_from_queue(struct mansession *s, const struct message *m) +{ + const char *queuename, *caller, *withdraw_info; + + queuename = astman_get_header(m, "Queue"); + caller = astman_get_header(m, "Caller"); + withdraw_info = astman_get_header(m, "WithdrawInfo"); + + if (ast_strlen_zero(queuename)) { + astman_send_error(s, m, "'Queue' not specified."); + return 0; + } + + if (ast_strlen_zero(caller)) { + astman_send_error(s, m, "'Caller' not specified."); + return 0; + } + + switch (request_withdraw_caller_from_queue(queuename, caller, withdraw_info)) { + case RES_OKAY: + astman_send_ack(s, m, "Withdraw requested successfully"); + break; + case RES_NOSUCHQUEUE: + astman_send_error(s, m, "Unable to request withdraw from queue: No such queue"); + break; + case RES_NOT_CALLER: + astman_send_error(s, m, "Unable to request withdraw from queue: No such caller"); + break; + case RES_EXISTS: + astman_send_error(s, m, "Unable to request withdraw from queue: Already requested"); + break; + } + + return 0; +} static char *handle_queue_add_member(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) @@ -11472,6 +11614,7 @@ static int unload_module(void) ast_manager_unregister("QueueReset"); ast_manager_unregister("QueueMemberRingInUse"); ast_manager_unregister("QueueChangePriorityCaller"); + ast_manager_unregister("QueueWithdrawCaller"); ast_unregister_application(app_aqm); ast_unregister_application(app_rqm); ast_unregister_application(app_pqm); @@ -11585,6 +11728,7 @@ static int load_module(void) err |= ast_manager_register_xml("QueueReload", 0, manager_queue_reload); err |= ast_manager_register_xml("QueueReset", 0, manager_queue_reset); err |= ast_manager_register_xml("QueueChangePriorityCaller", 0, manager_change_priority_caller_on_queue); + err |= ast_manager_register_xml("QueueWithdrawCaller", 0, manager_request_withdraw_caller_from_queue); err |= ast_custom_function_register(&queuevar_function); err |= ast_custom_function_register(&queueexists_function); err |= ast_custom_function_register(&queuemembercount_function); diff --git a/doc/CHANGES-staging/queue_withdraw_caller.txt b/doc/CHANGES-staging/queue_withdraw_caller.txt new file mode 100644 index 0000000000..04e43d0770 --- /dev/null +++ b/doc/CHANGES-staging/queue_withdraw_caller.txt @@ -0,0 +1,14 @@ +Subject: app_queue + +Added a new AMI action: QueueWithdrawCaller +This AMI action makes it possible to withdraw a caller from a queue +back to the dialplan. The call will be signaled to leave the queue +whenever it can, hence, it not guaranteed that the call will leave +the queue. + +Optional custom data can be passed in the request, in the WithdrawInfo +parameter. If the call successfully withdrawn the queue, +it can be retrieved using the QUEUE_WITHDRAW_INFO variable. + +This can be useful for certain uses, such as dispatching the call +to a specific extension.