mirror of https://github.com/asterisk/asterisk
Asterisk by default will create a single database connection and share it among all threads that attempt to access the database. In previous versions of Asterisk, this was tolerable, because the most used channel driver, chan_sip, mostly accessed the database from a single thread. With PJSIP, however, many threads may be attempting to perform database operations, and there is the potential for many more database accesses, meaning the concurrency is a horrible bottleneck if only one connection is shared. Asterisk has a connection pooling facility built into it, but the implementation has flaws. For one, there is a strict limit on the number of simultaneous connections that could be made to the database. Anything beyond the maximum would result in a failed operation. Attempting to predict what the maximum should be is nearly impossible even for someone intimately familiar with Asterisk's threading model. In addition, use of transactions in the dialplan can cause some severe bugs if connection pooling is enabled. This commit seeks to fix the concurrency problem by removing all connection management code from Asterisk and leaving that to the underlying unixODBC code instead. Now, Asterisk does not share a single connection, nor does it try to maintain a connection pool. Instead, all Asterisk ever does is request a connection from unixODBC and allow unixODBC to either allocate those connections or retrieve them from a pool. Doing this has a bit of a ripple effect. For one, since connections are not long-lived objects, several of the safeguards that previously existed have been removed. We don't have to worry about trying to use a connection that has gone stale. In every case, when we request a connection, it has just been made and we don't need to perform any sanity checks to be sure it's still active. Another major player affected by this change is transactions. Transactions and their respective connections were so tightly coupled that it was almost pornographic. This code change moves transaction-related code to its own file separate from the core ODBC functionality. This way, the core of ODBC does not even have to know that transactions exist. In making this large change, I had to look at a lot of code and understand it. When making this change, I discovered several places where the behavior is definitely not ideal, but it seemed outside the scope of this change to be fixing it. Instead, any place where I saw some sort of room for improvement has had a XXX comment added explaining what could be altered to improve it. Change-Id: I37a84def5ea4ddf93868ce8105f39de078297fbfchanges/66/2066/1
parent
60a15fe436
commit
9714da7aa4
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Asterisk -- An open source telephony toolkit.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016, Digium, Inc.
|
||||||
|
*
|
||||||
|
* Mark Michelson <mmichelson@digium.com>
|
||||||
|
*
|
||||||
|
* See http://www.asterisk.org for more information about
|
||||||
|
* the Asterisk project. Please do not directly contact
|
||||||
|
* any of the maintainers of this project for assistance;
|
||||||
|
* the project provides a web site, mailing lists and IRC
|
||||||
|
* channels for your use.
|
||||||
|
*
|
||||||
|
* This program is free software, distributed under the terms of
|
||||||
|
* the GNU General Public License Version 2. See the LICENSE file
|
||||||
|
* at the top of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RES_ODBC_TRANSACTION_H
|
||||||
|
#define RES_ODBC_TRANSACTION_H
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
*
|
||||||
|
* Retrieve an ODBC transaction connection with the given ODBC class name.
|
||||||
|
*
|
||||||
|
* \note The name passed here is *not* the name of the transaction but the name of the
|
||||||
|
* ODBC class defined in res_odbc.conf.
|
||||||
|
*
|
||||||
|
* \note Do not call ast_odbc_release_obj() on the retrieved connection. Calling this function
|
||||||
|
* does not make you the owner of the connection.
|
||||||
|
*
|
||||||
|
* XXX This function is majorly flawed because it ignores properties of transactions and simply
|
||||||
|
* finds one that corresponds to the given DSN. The problem here is that transactions have names
|
||||||
|
* and they maintain which transaction is "active" for operations like transaction creation,
|
||||||
|
* commit, and rollback. However, when it comes to intermediary operations to be made on the
|
||||||
|
* transactions, all that is ignored. It means that if a channel has created multiple transactions
|
||||||
|
* for the same DSN, it's a crapshoot which of those transactions the operation will be performed
|
||||||
|
* on. This can potentially lead to baffling errors under the right circumstances.
|
||||||
|
*
|
||||||
|
* XXX The semantics of this function make for writing some awkward code. If you use func_odbc as
|
||||||
|
* an example, it has to first try to retrieve a transactional connection, then failing that, create
|
||||||
|
* a non-transactional connection. The result is that it has to remember which type of connection it's
|
||||||
|
* using and know whether to release the connection when completed or not. It would be much better
|
||||||
|
* if callers did not have to jump through such hoops.
|
||||||
|
*
|
||||||
|
* \param chan Channel on which the ODBC transaction was created
|
||||||
|
* \param objname The name of the ODBC class configured in res_odbc.conf
|
||||||
|
* \retval NULL Transaction connection could not be found.
|
||||||
|
* \retval non-NULL A transactional connection
|
||||||
|
*/
|
||||||
|
struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname);
|
||||||
|
|
||||||
|
#endif /* RES_ODBC_TRANSACTION_H */
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,529 @@
|
|||||||
|
/*
|
||||||
|
* Asterisk -- An open source telephony toolkit.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016, Digium, Inc.
|
||||||
|
*
|
||||||
|
* Mark Michelson <mmichelson@digium.com>
|
||||||
|
*
|
||||||
|
* See http://www.asterisk.org for more information about
|
||||||
|
* the Asterisk project. Please do not directly contact
|
||||||
|
* any of the maintainers of this project for assistance;
|
||||||
|
* the project provides a web site, mailing lists and IRC
|
||||||
|
* channels for your use.
|
||||||
|
*
|
||||||
|
* This program is free software, distributed under the terms of
|
||||||
|
* the GNU General Public License Version 2. See the LICENSE file
|
||||||
|
* at the top of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "asterisk.h"
|
||||||
|
|
||||||
|
#include "asterisk/res_odbc.h"
|
||||||
|
#include "asterisk/res_odbc_transaction.h"
|
||||||
|
#include "asterisk/channel.h"
|
||||||
|
#include "asterisk/pbx.h"
|
||||||
|
#include "asterisk/app.h"
|
||||||
|
#include "asterisk/module.h"
|
||||||
|
|
||||||
|
/*** MODULEINFO
|
||||||
|
<depend>res_odbc</depend>
|
||||||
|
<support_level>core</support_level>
|
||||||
|
***/
|
||||||
|
|
||||||
|
/*** DOCUMENTATION
|
||||||
|
<function name="ODBC" language="en_US">
|
||||||
|
<synopsis>
|
||||||
|
Controls ODBC transaction properties.
|
||||||
|
</synopsis>
|
||||||
|
<syntax>
|
||||||
|
<parameter name="property" required="true">
|
||||||
|
<enumlist>
|
||||||
|
<enum name="transaction">
|
||||||
|
<para>Gets or sets the active transaction ID. If set, and the transaction ID does not
|
||||||
|
exist and a <replaceable>database name</replaceable> is specified as an argument, it will be created.</para>
|
||||||
|
</enum>
|
||||||
|
<enum name="forcecommit">
|
||||||
|
<para>Controls whether a transaction will be automatically committed when the channel
|
||||||
|
hangs up. Defaults to false. If a <replaceable>transaction ID</replaceable> is specified in the optional argument,
|
||||||
|
the property will be applied to that ID, otherwise to the current active ID.</para>
|
||||||
|
</enum>
|
||||||
|
<enum name="isolation">
|
||||||
|
<para>Controls the data isolation on uncommitted transactions. May be one of the
|
||||||
|
following: <literal>read_committed</literal>, <literal>read_uncommitted</literal>,
|
||||||
|
<literal>repeatable_read</literal>, or <literal>serializable</literal>. Defaults to the
|
||||||
|
database setting in <filename>res_odbc.conf</filename> or <literal>read_committed</literal>
|
||||||
|
if not specified. If a <replaceable>transaction ID</replaceable> is specified as an optional argument, it will be
|
||||||
|
applied to that ID, otherwise the current active ID.</para>
|
||||||
|
</enum>
|
||||||
|
</enumlist>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="argument" required="false" />
|
||||||
|
</syntax>
|
||||||
|
<description>
|
||||||
|
<para>The ODBC() function allows setting several properties to influence how a connected
|
||||||
|
database processes transactions.</para>
|
||||||
|
</description>
|
||||||
|
</function>
|
||||||
|
<application name="ODBC_Commit" language="en_US">
|
||||||
|
<synopsis>
|
||||||
|
Commits a currently open database transaction.
|
||||||
|
</synopsis>
|
||||||
|
<syntax>
|
||||||
|
<parameter name="transaction ID" required="no" />
|
||||||
|
</syntax>
|
||||||
|
<description>
|
||||||
|
<para>Commits the database transaction specified by <replaceable>transaction ID</replaceable>
|
||||||
|
or the current active transaction, if not specified.</para>
|
||||||
|
</description>
|
||||||
|
</application>
|
||||||
|
<application name="ODBC_Rollback" language="en_US">
|
||||||
|
<synopsis>
|
||||||
|
Rollback a currently open database transaction.
|
||||||
|
</synopsis>
|
||||||
|
<syntax>
|
||||||
|
<parameter name="transaction ID" required="no" />
|
||||||
|
</syntax>
|
||||||
|
<description>
|
||||||
|
<para>Rolls back the database transaction specified by <replaceable>transaction ID</replaceable>
|
||||||
|
or the current active transaction, if not specified.</para>
|
||||||
|
</description>
|
||||||
|
</application>
|
||||||
|
***/
|
||||||
|
|
||||||
|
struct odbc_txn_frame {
|
||||||
|
AST_LIST_ENTRY(odbc_txn_frame) list;
|
||||||
|
struct odbc_obj *obj; /*!< Database handle within which transacted statements are run */
|
||||||
|
/*!\brief Is this record the current active transaction within the channel?
|
||||||
|
* Note that the active flag is really only necessary for statements which
|
||||||
|
* are triggered from the dialplan, as there isn't a direct correlation
|
||||||
|
* between multiple statements. Applications wishing to use transactions
|
||||||
|
* may simply perform each statement on the same odbc_obj, which keeps the
|
||||||
|
* transaction persistent.
|
||||||
|
*/
|
||||||
|
unsigned int active:1;
|
||||||
|
unsigned int forcecommit:1; /*!< Should uncommitted transactions be auto-committed on handle release? */
|
||||||
|
unsigned int isolation; /*!< Flags for how the DB should deal with data in other, uncommitted transactions */
|
||||||
|
char name[0]; /*!< Name of this transaction ID */
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct odbc_txn_frame *release_transaction(struct odbc_txn_frame *tx);
|
||||||
|
|
||||||
|
static void odbc_txn_free(void *vdata)
|
||||||
|
{
|
||||||
|
struct odbc_txn_frame *tx;
|
||||||
|
AST_LIST_HEAD(, odbc_txn_frame) *oldlist = vdata;
|
||||||
|
|
||||||
|
ast_debug(2, "odbc_txn_free(%p) called\n", vdata);
|
||||||
|
|
||||||
|
AST_LIST_LOCK(oldlist);
|
||||||
|
while ((tx = AST_LIST_REMOVE_HEAD(oldlist, list))) {
|
||||||
|
release_transaction(tx);
|
||||||
|
}
|
||||||
|
AST_LIST_UNLOCK(oldlist);
|
||||||
|
AST_LIST_HEAD_DESTROY(oldlist);
|
||||||
|
ast_free(oldlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct ast_datastore_info txn_info = {
|
||||||
|
.type = "ODBC_Transaction",
|
||||||
|
.destroy = odbc_txn_free,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct odbc_txn_frame *create_transaction(struct ast_channel *chan, const char *name, const char *dsn)
|
||||||
|
{
|
||||||
|
struct ast_datastore *txn_store;
|
||||||
|
AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
|
||||||
|
struct odbc_txn_frame *txn = NULL;
|
||||||
|
struct odbc_txn_frame *otxn;
|
||||||
|
|
||||||
|
if (ast_strlen_zero(dsn)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_channel_lock(chan);
|
||||||
|
if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
|
||||||
|
oldlist = txn_store->data;
|
||||||
|
} else {
|
||||||
|
if (!(txn_store = ast_datastore_alloc(&txn_info, NULL))) {
|
||||||
|
ast_log(LOG_ERROR, "Unable to allocate a new datastore. Cannot create a new transaction.\n");
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(oldlist = ast_calloc(1, sizeof(*oldlist)))) {
|
||||||
|
ast_log(LOG_ERROR, "Unable to allocate datastore list head. Cannot create a new transaction.\n");
|
||||||
|
ast_datastore_free(txn_store);
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
txn_store->data = oldlist;
|
||||||
|
AST_LIST_HEAD_INIT(oldlist);
|
||||||
|
ast_channel_datastore_add(chan, txn_store);
|
||||||
|
}
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
|
||||||
|
txn = ast_calloc(1, sizeof(*txn) + strlen(name) + 1);
|
||||||
|
if (!txn) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy(txn->name, name); /* SAFE */
|
||||||
|
txn->obj = ast_odbc_request_obj(dsn, 0);
|
||||||
|
if (!txn->obj) {
|
||||||
|
ast_free(txn);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
txn->isolation = ast_odbc_class_get_isolation(txn->obj->parent);
|
||||||
|
txn->forcecommit = ast_odbc_class_get_isolation(txn->obj->parent);
|
||||||
|
txn->active = 1;
|
||||||
|
|
||||||
|
if (SQLSetConnectAttr(txn->obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
|
||||||
|
ast_odbc_print_errors(SQL_HANDLE_DBC, txn->obj->con, "SetConnectAttr (Autocommit)");
|
||||||
|
ast_odbc_release_obj(txn->obj);
|
||||||
|
ast_free(txn);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the isolation property */
|
||||||
|
if (SQLSetConnectAttr(txn->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)txn->isolation, 0) == SQL_ERROR) {
|
||||||
|
ast_odbc_print_errors(SQL_HANDLE_DBC, txn->obj->con, "SetConnectAttr");
|
||||||
|
ast_odbc_release_obj(txn->obj);
|
||||||
|
ast_free(txn);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* On creation, the txn becomes active, and all others inactive */
|
||||||
|
AST_LIST_LOCK(oldlist);
|
||||||
|
AST_LIST_TRAVERSE(oldlist, otxn, list) {
|
||||||
|
otxn->active = 0;
|
||||||
|
}
|
||||||
|
AST_LIST_INSERT_TAIL(oldlist, txn, list);
|
||||||
|
AST_LIST_UNLOCK(oldlist);
|
||||||
|
|
||||||
|
return txn;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct odbc_txn_frame *find_transaction(struct ast_channel *chan, const char *name, int active)
|
||||||
|
{
|
||||||
|
struct ast_datastore *txn_store;
|
||||||
|
AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
|
||||||
|
struct odbc_txn_frame *txn = NULL;
|
||||||
|
|
||||||
|
if (!chan || (!active && !name)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_channel_lock(chan);
|
||||||
|
txn_store = ast_channel_datastore_find(chan, &txn_info, NULL);
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
|
||||||
|
if (!txn_store) {
|
||||||
|
/* No datastore? Definitely no transaction then */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
oldlist = txn_store->data;
|
||||||
|
AST_LIST_LOCK(oldlist);
|
||||||
|
|
||||||
|
AST_LIST_TRAVERSE(oldlist, txn, list) {
|
||||||
|
if (active) {
|
||||||
|
if (txn->active) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (!strcasecmp(txn->name, name)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AST_LIST_UNLOCK(oldlist);
|
||||||
|
|
||||||
|
return txn;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct odbc_txn_frame *release_transaction(struct odbc_txn_frame *tx)
|
||||||
|
{
|
||||||
|
if (!tx) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_debug(2, "release_transaction(%p) called (tx->obj = %p\n", tx, tx->obj);
|
||||||
|
|
||||||
|
ast_debug(1, "called on a transactional handle with %s\n", tx->forcecommit ? "COMMIT" : "ROLLBACK");
|
||||||
|
if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, tx->forcecommit ? SQL_COMMIT : SQL_ROLLBACK) == SQL_ERROR) {
|
||||||
|
ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLEndTran");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Transaction is done, reset autocommit
|
||||||
|
*
|
||||||
|
* XXX I'm unsure if this is actually necessary, since we're releasing
|
||||||
|
* the connection back to unixODBC. However, if unixODBC pooling is enabled,
|
||||||
|
* it can't hurt to do just in case.
|
||||||
|
*/
|
||||||
|
if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
|
||||||
|
ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLSetAttr");
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_odbc_release_obj(tx->obj);
|
||||||
|
ast_free(tx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int commit_exec(struct ast_channel *chan, const char *data)
|
||||||
|
{
|
||||||
|
struct odbc_txn_frame *tx;
|
||||||
|
|
||||||
|
if (ast_strlen_zero(data)) {
|
||||||
|
tx = find_transaction(chan, NULL, 1);
|
||||||
|
} else {
|
||||||
|
tx = find_transaction(chan, data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* XXX COMMIT_RESULT is set to OK even if no transaction was found. Very misleading */
|
||||||
|
pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", "OK");
|
||||||
|
|
||||||
|
if (tx) {
|
||||||
|
if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_COMMIT) == SQL_ERROR) {
|
||||||
|
struct ast_str *errors = ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLEndTran");
|
||||||
|
pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", ast_str_buffer(errors));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rollback_exec(struct ast_channel *chan, const char *data)
|
||||||
|
{
|
||||||
|
struct odbc_txn_frame *tx;
|
||||||
|
|
||||||
|
if (ast_strlen_zero(data)) {
|
||||||
|
tx = find_transaction(chan, NULL, 1);
|
||||||
|
} else {
|
||||||
|
tx = find_transaction(chan, data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* XXX ROLLBACK_RESULT is set to OK even if no transaction was found. Very misleading */
|
||||||
|
pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", "OK");
|
||||||
|
|
||||||
|
if (tx) {
|
||||||
|
if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_ROLLBACK) == SQL_ERROR) {
|
||||||
|
struct ast_str *errors = ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLEndTran");
|
||||||
|
pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", ast_str_buffer(errors));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int acf_transaction_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
|
||||||
|
{
|
||||||
|
AST_DECLARE_APP_ARGS(args,
|
||||||
|
AST_APP_ARG(property);
|
||||||
|
AST_APP_ARG(opt);
|
||||||
|
);
|
||||||
|
struct odbc_txn_frame *tx;
|
||||||
|
|
||||||
|
AST_STANDARD_APP_ARGS(args, data);
|
||||||
|
if (strcasecmp(args.property, "transaction") == 0) {
|
||||||
|
if ((tx = find_transaction(chan, NULL, 1))) {
|
||||||
|
ast_copy_string(buf, tx->name, len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else if (strcasecmp(args.property, "isolation") == 0) {
|
||||||
|
if (!ast_strlen_zero(args.opt)) {
|
||||||
|
tx = find_transaction(chan, args.opt, 0);
|
||||||
|
} else {
|
||||||
|
tx = find_transaction(chan, NULL, 1);
|
||||||
|
}
|
||||||
|
if (tx) {
|
||||||
|
ast_copy_string(buf, ast_odbc_isolation2text(tx->isolation), len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else if (strcasecmp(args.property, "forcecommit") == 0) {
|
||||||
|
if (!ast_strlen_zero(args.opt)) {
|
||||||
|
tx = find_transaction(chan, args.opt, 0);
|
||||||
|
} else {
|
||||||
|
tx = find_transaction(chan, NULL, 1);
|
||||||
|
}
|
||||||
|
if (tx) {
|
||||||
|
ast_copy_string(buf, tx->forcecommit ? "1" : "0", len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* XXX The idea of "active" transactions is silly and makes things
|
||||||
|
* more prone to error. It would be much better if the transaction
|
||||||
|
* always had to be specified by name so that no implicit behavior
|
||||||
|
* occurred.
|
||||||
|
*/
|
||||||
|
static int mark_transaction_active(struct ast_channel *chan, struct odbc_txn_frame *tx)
|
||||||
|
{
|
||||||
|
struct ast_datastore *txn_store;
|
||||||
|
AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
|
||||||
|
struct odbc_txn_frame *active = NULL, *txn;
|
||||||
|
|
||||||
|
if (!chan) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_channel_lock(chan);
|
||||||
|
if (!(txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
oldlist = txn_store->data;
|
||||||
|
AST_LIST_LOCK(oldlist);
|
||||||
|
AST_LIST_TRAVERSE(oldlist, txn, list) {
|
||||||
|
if (txn == tx) {
|
||||||
|
txn->active = 1;
|
||||||
|
active = txn;
|
||||||
|
} else {
|
||||||
|
txn->active = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AST_LIST_UNLOCK(oldlist);
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
return active ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int acf_transaction_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
|
||||||
|
{
|
||||||
|
AST_DECLARE_APP_ARGS(args,
|
||||||
|
AST_APP_ARG(property);
|
||||||
|
AST_APP_ARG(opt);
|
||||||
|
);
|
||||||
|
struct odbc_txn_frame *tx;
|
||||||
|
|
||||||
|
AST_STANDARD_APP_ARGS(args, s);
|
||||||
|
if (strcasecmp(args.property, "transaction") == 0) {
|
||||||
|
/* Set active transaction */
|
||||||
|
if ((tx = find_transaction(chan, value, 0))) {
|
||||||
|
mark_transaction_active(chan, tx);
|
||||||
|
} else if (!create_transaction(chan, value, args.opt)) {
|
||||||
|
pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
|
||||||
|
return 0;
|
||||||
|
} else if (strcasecmp(args.property, "forcecommit") == 0) {
|
||||||
|
/* Set what happens when an uncommitted transaction ends without explicit Commit or Rollback */
|
||||||
|
if (ast_strlen_zero(args.opt)) {
|
||||||
|
tx = find_transaction(chan, NULL, 1);
|
||||||
|
} else {
|
||||||
|
tx = find_transaction(chan, args.opt, 0);
|
||||||
|
}
|
||||||
|
if (!tx) {
|
||||||
|
pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (ast_true(value)) {
|
||||||
|
tx->forcecommit = 1;
|
||||||
|
} else if (ast_false(value)) {
|
||||||
|
tx->forcecommit = 0;
|
||||||
|
} else {
|
||||||
|
ast_log(LOG_ERROR, "Invalid value for forcecommit: '%s'\n", S_OR(value, ""));
|
||||||
|
pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
|
||||||
|
return 0;
|
||||||
|
} else if (strcasecmp(args.property, "isolation") == 0) {
|
||||||
|
/* How do uncommitted transactions affect reads? */
|
||||||
|
/* XXX This is completely useless. The problem is that setting the isolation here
|
||||||
|
* does not actually alter the connection. The only time the isolation gets set is
|
||||||
|
* when the transaction is created. The only way to set isolation is to set it on
|
||||||
|
* the ODBC class's configuration in res_odbc.conf.
|
||||||
|
*/
|
||||||
|
int isolation = ast_odbc_text2isolation(value);
|
||||||
|
if (ast_strlen_zero(args.opt)) {
|
||||||
|
tx = find_transaction(chan, NULL, 1);
|
||||||
|
} else {
|
||||||
|
tx = find_transaction(chan, args.opt, 0);
|
||||||
|
}
|
||||||
|
if (!tx) {
|
||||||
|
pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (isolation == 0) {
|
||||||
|
pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
|
||||||
|
ast_log(LOG_ERROR, "Invalid isolation specification: '%s'\n", S_OR(value, ""));
|
||||||
|
} else if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)isolation, 0) == SQL_ERROR) {
|
||||||
|
pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "SQL_ERROR");
|
||||||
|
ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SetConnectAttr (Txn isolation)");
|
||||||
|
} else {
|
||||||
|
pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
|
||||||
|
tx->isolation = isolation;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
ast_log(LOG_ERROR, "Unknown property: '%s'\n", args.property);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname)
|
||||||
|
{
|
||||||
|
struct ast_datastore *txn_store;
|
||||||
|
AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
|
||||||
|
struct odbc_txn_frame *txn = NULL;
|
||||||
|
|
||||||
|
if (!chan || !objname) {
|
||||||
|
/* No channel == no transaction */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_channel_lock(chan);
|
||||||
|
if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
|
||||||
|
oldlist = txn_store->data;
|
||||||
|
} else {
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
AST_LIST_LOCK(oldlist);
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
|
||||||
|
AST_LIST_TRAVERSE(oldlist, txn, list) {
|
||||||
|
if (txn->obj && txn->obj->parent && !strcmp(ast_odbc_class_get_name(txn->obj->parent), objname)) {
|
||||||
|
AST_LIST_UNLOCK(oldlist);
|
||||||
|
return txn->obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AST_LIST_UNLOCK(oldlist);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct ast_custom_function odbc_function = {
|
||||||
|
.name = "ODBC",
|
||||||
|
.read = acf_transaction_read,
|
||||||
|
.write = acf_transaction_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * const app_commit = "ODBC_Commit";
|
||||||
|
static const char * const app_rollback = "ODBC_Rollback";
|
||||||
|
|
||||||
|
/* XXX res_odbc takes the path of disallowing unloads from happening.
|
||||||
|
* It's not a great precedent, but since trying to deal with unloading the module
|
||||||
|
* while transactions are active seems like a huge pain to deal with, we'll go
|
||||||
|
* the same way here.
|
||||||
|
*/
|
||||||
|
static int unload_module(void)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int load_module(void)
|
||||||
|
{
|
||||||
|
ast_register_application_xml(app_commit, commit_exec);
|
||||||
|
ast_register_application_xml(app_rollback, rollback_exec);
|
||||||
|
ast_custom_function_register(&odbc_function);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "ODBC transaction resource",
|
||||||
|
.support_level = AST_MODULE_SUPPORT_CORE,
|
||||||
|
.load = load_module,
|
||||||
|
.unload = unload_module,
|
||||||
|
.load_pri = AST_MODPRI_REALTIME_DEPEND,
|
||||||
|
);
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
global:
|
||||||
|
LINKER_SYMBOL_PREFIXast_odbc_retrieve_transaction_obj;
|
||||||
|
local:
|
||||||
|
*;
|
||||||
|
};
|
Loading…
Reference in new issue