File transaction_sr.c¶
File List > cubrid > src > transaction > transaction_sr.c
Go to the documentation of this file
/*
* Copyright 2008 Search Solution Corporation
* Copyright 2016 CUBRID Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/*
* transaction_sr.c -
*/
#ident "$Id$"
#include "config.h"
#include <assert.h>
#include "transaction_sr.h"
#include "locator_sr.h"
#include "log_2pc.h"
#include "log_lsa.hpp"
#include "log_manager.h"
#if defined(SERVER_MODE)
#endif
#include "xserver_interface.h"
#if defined(ENABLE_SYSTEMTAP)
#include "probes.h"
#endif /* ENABLE_SYSTEMTAP */
#include "server_support.h"
#include "dbtype.h"
#include "thread_manager.hpp" // for thread_get_thread_entry_info and thread_sleep
// XXX: SHOULD BE THE LAST INCLUDE HEADER
#include "memory_wrapper.hpp"
/*
* xtran_server_commit - Commit the current transaction
*
* return: state of operation
*
* thrd(in): this thread handle
* retain_lock(in): false = release locks (default)
* true = retain locks
*
* NOTE: Commit the current transaction. All transient class name
* entries are removed, and the commit is forwarded to the log
* and recovery manager. The log manager declares all changes of
* the transaction as permanent and releases all locks acquired
* by the transaction. The return value may indicate that the
* transaction has not been committed completely when there are
* some loose_end postpone actions to be done in the client
* machine. In this case the client transaction manager must
* obtain and execute these actions.
*
* This function should be called after all objects that have
* been updated by the transaction are flushed from the workspace
* (client) to the page buffer pool (server).
*/
TRAN_STATE
xtran_server_commit (THREAD_ENTRY * thread_p, bool retain_lock)
{
TRAN_STATE state;
int tran_index;
/*
* Execute some few remaining actions before the log manager is notified of
* the commit
*/
tran_index = LOG_FIND_THREAD_TRAN_INDEX (thread_p);
#ifndef CCI_XA
/* dblink transaction commit first */
QMGR_TRAN_STATUS dblink_tran = qmgr_check_dblink_trans (thread_p, false);
if (dblink_tran == QMGR_TRAN_DBLINK_ABORTED)
{
/* transaction is aborted for dblink commit fail */
state = log_abort (thread_p, tran_index);
#if defined(ENABLE_SYSTEMTAP)
CUBRID_TRAN_ABORT (tran_index, TRAN_UNACTIVE_ABORTED_INFORMING_PARTICIPANTS);
#endif /* ENABLE_SYSTEMTAP */
return TRAN_UNACTIVE_ABORTED_INFORMING_PARTICIPANTS;
}
#endif
state = log_commit (thread_p, tran_index, retain_lock);
#if defined(ENABLE_SYSTEMTAP)
if (state == TRAN_UNACTIVE_COMMITTED || state == TRAN_UNACTIVE_COMMITTED_INFORMING_PARTICIPANTS)
{
CUBRID_TRAN_COMMIT (tran_index);
}
#endif /* ENABLE_SYSTEMTAP */
return state;
}
/*
* xtran_server_abort - Abort the current transaction
*
* return: state of operation
*
* thrd(in): this thread handle
*
* NOTE: Abort the current transaction. All transient class name
* entries are removed, and the abort operation is forwarded to
* to the log/recovery manager. The log manager undoes any
* changes made by the transaction and releases all lock acquired
* by the transaction. The return value may indicate that the
* transaction has not been aborted completely when there are
* some loose_end undo actions to be executed in the client
* machine. In this case the client transaction manager must
* obtain and execute these actions.
* This function should be called after all updated objects in
* the workspace are removed.
*/
TRAN_STATE
xtran_server_abort (THREAD_ENTRY * thread_p)
{
TRAN_STATE state;
int tran_index;
/* Execute some few remaining actions before the log manager is notified of the commit */
tran_index = LOG_FIND_THREAD_TRAN_INDEX (thread_p);
/* dblink transaction abort first */
(void) qmgr_check_dblink_trans (thread_p, true);
state = log_abort (thread_p, tran_index);
#if defined(ENABLE_SYSTEMTAP)
CUBRID_TRAN_ABORT (tran_index, state);
#endif /* ENABLE_SYSTEMTAP */
return state;
}
#if defined (ENABLE_UNUSED_FUNCTION)
/*
* tran_server_unilaterally_abort - Unilaterally abort the current transaction
*
* return: states of transactions
*
* tran_index(in): Transaction index
*
* NOTE: The given transaction is unilaterally aborted by the server
* Execute tran_server_abort & set an error message
*/
TRAN_STATE
tran_server_unilaterally_abort (THREAD_ENTRY * thread_p, int tran_index)
{
TRAN_STATE state;
int save_tran_index;
const char *client_prog_name; /* Client user name for transaction */
const char *client_user_name; /* Client user name for transaction */
const char *client_host_name; /* Client host for transaction */
int client_pid; /* Client process identifier for transaction */
if (thread_p == NULL)
{
thread_p = thread_get_thread_entry_info ();
}
save_tran_index = LOG_FIND_THREAD_TRAN_INDEX (thread_p);
LOG_SET_CURRENT_TRAN_INDEX (thread_p, tran_index);
(void) logtb_find_client_name_host_pid (tran_index, &client_prog_name, &client_user_name, &client_host_name,
&client_pid);
state = xtran_server_abort (thread_p);
/* free any drivers used by this transaction */
LOG_SET_CURRENT_TRAN_INDEX (thread_p, save_tran_index);
er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_LK_UNILATERALLY_ABORTED, 4, tran_index, client_user_name,
client_host_name, client_pid);
return state;
}
#endif
#if defined(SERVER_MODE)
/*
* tran_server_unilaterally_abort_tran -
*
* return: state of operation
*
* NOTE:this function is used when pgbuf_fix() results in deadlock.
* It is used by request handler functions to rollback gracefully,
*/
TRAN_STATE
tran_server_unilaterally_abort_tran (THREAD_ENTRY * thread_p)
{
if (thread_p == NULL)
{
thread_p = thread_get_thread_entry_info ();
}
return xtran_server_abort (thread_p);
}
#endif /* SERVER_MODE */
/*
* xtran_server_start_topop - Start a server macro nested top operation
*
* return: NO_ERROR if all OK, ER_ status otherwise
*
* topop_lsa(in/out): Address of top operation for rollback purposes
*
*/
int
xtran_server_start_topop (THREAD_ENTRY * thread_p, LOG_LSA * topop_lsa)
{
int error_code = NO_ERROR;
/*
* Execute some few remaining actions before the start top nested action is
* started by the log manager.
*/
log_sysop_start (thread_p);
if (log_get_parent_lsa_system_op (thread_p, topop_lsa) == NULL)
{
assert_release (false);
return ER_FAILED;
}
error_code = locator_savepoint_transient_class_name_entries (thread_p, topop_lsa);
if (error_code != NO_ERROR)
{
ASSERT_ERROR ();
LSA_SET_NULL (topop_lsa);
return error_code;
}
return NO_ERROR;
}
/*
* xtran_server_end_topop - End a macro nested top operation
*
* return: states of transactions
*
* result(in): Result of the nested top action
* topop_lsa(in): Address where the top operation for rollback purposes
* started.
*
* NOTE:Finish the latest nested top macro operation by either
* aborting or attaching to outer parent.
*
* Note that a top operation is not associated with the current
* transaction, thus, it can be aborted
* independently of the transaction.
*/
TRAN_STATE
xtran_server_end_topop (THREAD_ENTRY * thread_p, LOG_RESULT_TOPOP result, LOG_LSA * topop_lsa)
{
TRAN_STATE state;
bool drop_transient_class = false;
LOG_TDES *tdes = LOG_FIND_CURRENT_TDES (thread_p);
assert (result == LOG_RESULT_TOPOP_ABORT || result == LOG_RESULT_TOPOP_ATTACH_TO_OUTER);
/*
* Execute some few remaining actions before the start top nested action is
* started by the log manager.
*/
if (tdes == NULL)
{
int tran_index = LOG_FIND_THREAD_TRAN_INDEX (thread_p);
er_set (ER_FATAL_ERROR_SEVERITY, ARG_FILE_LINE, ER_LOG_UNKNOWN_TRANINDEX, 1, tran_index);
return TRAN_UNACTIVE_UNKNOWN;
}
er_stack_push ();
switch (result)
{
case LOG_RESULT_TOPOP_COMMIT:
case LOG_RESULT_TOPOP_ABORT:
if (log_get_parent_lsa_system_op (thread_p, topop_lsa) == topop_lsa)
{
drop_transient_class = true;
}
if (result == LOG_RESULT_TOPOP_COMMIT)
{
log_sysop_commit (thread_p);
state = TRAN_UNACTIVE_COMMITTED;
}
else
{
log_sysop_abort (thread_p);
state = TRAN_UNACTIVE_ABORTED;
}
if (drop_transient_class)
{
(void) locator_drop_transient_class_name_entries (thread_p, topop_lsa);
}
if (result == LOG_RESULT_TOPOP_ABORT)
{
tx_lob_locator_clear (thread_p, tdes, false, topop_lsa);
}
break;
case LOG_RESULT_TOPOP_ATTACH_TO_OUTER:
default:
log_sysop_attach_to_outer (thread_p);
state = tdes->state;
break;
}
er_stack_pop ();
return state;
}
/*
* xtran_server_savepoint - Declare a user savepoint
*
* return: NO_ERROR if all OK, ER_ status otherwise
*
* savept_name(in): Name of the savepoint
* savept_lsa(in): Address of save point operation.
*
* NOTE: A savepoint is established for the current transaction so
* that future transaction actions can be rolled back to this
* established savepoint. We call this operation a partial abort
* (rollback). That is, all database actions affected by the
* transaction after the savepoint are "undone", and all effects
* of the transaction preceding the savepoint remain. The
* transaction can then continue executing other database
* statements. It is permissible to abort to the same savepoint
* repeatedly within the same transaction.
* If the same savepoint name is used in multiple savepoint
* declarations within the same transaction, then only the latest
* savepoint with that name is available for aborts and the
* others are forgotten.
* There are no limits on the number of savepoints that a
* transaction can have.
*/
int
xtran_server_savepoint (THREAD_ENTRY * thread_p, const char *savept_name, LOG_LSA * savept_lsa)
{
LOG_LSA *lsa;
int error_code = NO_ERROR;
/*
* Execute some few remaining actions before the start top nested action is
* started by the log manager.
*/
lsa = log_append_savepoint (thread_p, savept_name);
if (lsa == NULL)
{
LSA_SET_NULL (savept_lsa);
error_code = ER_FAILED;
}
else
{
LSA_COPY (savept_lsa, lsa);
error_code = locator_savepoint_transient_class_name_entries (thread_p, lsa);
if (error_code != NO_ERROR)
{
LSA_SET_NULL (savept_lsa);
}
}
return error_code;
}
/*
* xtran_server_partial_abort -Abort operations of a transaction up to a savepoint
*
* return: state of partial aborted operation (i.e., notify if
* there are client actions that need to be undone).
*
* savept_name(in): Name of the savepoint
* savept_lsa(in/out): Address of save point operation.
*
* Note: All the effects of the current transaction after the
* given savepoint are undone and all the effects of the transaction
* preceding the given savepoint remain. After the partial abort
* the transaction can continue its normal execution as if
* the statements that were undone, were never executed.
* The return value may indicate that there are some client
* loose_end undo actions to be performed at the client machine.
* In this case the transaction manager must obtain and execute
* these actions at the client.
*/
TRAN_STATE
xtran_server_partial_abort (THREAD_ENTRY * thread_p, const char *savept_name, LOG_LSA * savept_lsa)
{
TRAN_STATE state;
state = log_abort_partial (thread_p, savept_name, savept_lsa);
return state;
}
/*
* xtran_server_set_global_tran_info - Set global transaction information
*
* return: NO_ERROR if all OK, ER_ status otherwise
*
* gtrid(in): global transaction identifier
* info(in): pointer to the user information to be set
* size(in): size of the user information to be set
*
* NOTE: Set the user information related with the global transaction.
* The global transaction identified by the 'gtrid' should exist
* and should be the value returned by 'db_2pc_start_transaction'
* You can use this function to set the longer format of global
* transaction identifier such as XID of XA interface.
*/
int
xtran_server_set_global_tran_info (THREAD_ENTRY * thread_p, int gtrid, void *info, int size)
{
return log_2pc_set_global_tran_info (thread_p, gtrid, info, size);
}
/*
* xtran_server_get_global_tran_info - Get global transaction information
*
* return: NO_ERROR if all OK, ER_ status otherwise
*
* gtrid(in): global transaction identifier
* buffer(in):pointer to the buffer into which the user information is stored
* size(in): size of the buffer
*
* NOTE: Get the user information of the global transaction identified
* by the 'gtrid'.
* You can use this function to get the longer format of global
* transaction identifier such as XID of XA interface. This
* function is designed to use if you want to get XID after
* calling 'db_2pc_prepared_transactions' to support xa_recover()
*/
int
xtran_server_get_global_tran_info (THREAD_ENTRY * thread_p, int gtrid, void *buffer, int size)
{
return log_2pc_get_global_tran_info (thread_p, gtrid, buffer, size);
}
/*
* xtran_server_2pc_start - Start transaction as a part of global transaction
*
* return: return global transaction identifier
*
* Note: Make current transaction as a part of a global transaction by
* assigning a global transaction identifier(gtrid).
* It is recommended to call this function just after the end of
* a transaction(commit or abort) before executing other works.
* This function is one way of getting gtrid of the transaction.
* The other way is to use 'db_2pc_prepare_to_commit_transaction'
* The function 'db_2pc_prepare_transaction' should be used if
* this function is called.
*/
int
xtran_server_2pc_start (THREAD_ENTRY * thread_p)
{
return log_2pc_start (thread_p);
}
/*
* xtran_server_2pc_prepare - Prepare transaction to commit
*
* return: TRAN_STATE
*
* Note: Prepare the current transaction for commitment in 2PC. The
* transaction should be made as a part of a global transaction
* before by 'db_2pc_start_transaction', a pair one of this
* function.
* The system promises not to unilaterally abort the transaction.
* After this function call, the only API functions that should
* be executed are 'db_commit_transaction' &
* 'db_abort_transaction'.
*/
TRAN_STATE
xtran_server_2pc_prepare (THREAD_ENTRY * thread_p)
{
/* Make the transient classname entries permanent; If transaction aborted after this point, these operations will
* also be undone, just like previous operations of the transaction. */
(void) locator_drop_transient_class_name_entries (thread_p, NULL);
return log_2pc_prepare (thread_p);
}
/*
* xtran_server_2pc_recovery_prepared - Obtain list of prepared transactions
*
* return: the number of ids copied into 'gtrids[]'
*
* gtrids(in): array into which global transaction identifiers are copied
* size(in): size of 'gtrids[]' array
*
* Note: For restart recovery of global transactions, this function
* returns gtrids of transactions in prepared state, which was
* a part of a global transaction.
* If the return value is less than the 'size', there's no more
* transactions to recover.
*/
int
xtran_server_2pc_recovery_prepared (THREAD_ENTRY * thread_p, int gtrids[], int size)
{
return log_2pc_recovery_prepared (thread_p, gtrids, size);
}
/*
* xtran_server_2pc_attach_global_tran - Attach to a loose end 2pc transaction
*
* return: New transaction index
*
* gtrid(in): Global transaction identifier
*
* Note:The current client index is attached to the given 2PC loose
* end (i.e., transaction wiating for decision) transaction with
* the given global transaction identifier.
* The old client transaction is aborted before the attachement,
* the old client transaction must not be in the middle of a 2PC.
* It is recommended to attach a client to a 2PC loose end
* transaction just after the client restart or after a commit
* or abort.
*/
int
xtran_server_2pc_attach_global_tran (THREAD_ENTRY * thread_p, int gtrid)
{
return log_2pc_attach_global_tran (thread_p, gtrid);
}
/*
* xtran_server_2pc_prepare_global_tran - Prepare the transaction to commit
*
* return: TRAN_STATE
*
* global_tranid(in): Identifier of the global transaction
*
* Note:This function prepares the transaction identified by "gtrid"
* for commitment. Any objects and data that the transaction held
* or modified are placed in a state that can be guarantee the
* the commintment of the transaction by coordinator request
* regardless of failures. The shared type locks (IS, S) acquired
* by the transaction are released (SIX is demoted to IX lock)
* and the exclusive type locks (IX, X) acquired by the
* transaction are saved in the log as part of the prepare to
* commit log record. This is needed since, we must guarantee the
* consistency of the updated data until the transaction is
* either committed or aborted by the coordinator regardless of
* failures. If the transaction cannot be committed, it was
* previously aborted, and the coordinator is notified of such
* state.
*/
TRAN_STATE
xtran_server_2pc_prepare_global_tran (THREAD_ENTRY * thread_p, int global_tranid)
{
/* Make the transient classname entries permanent; If transaction aborted after this point, these operations will
* also be undone, just like previous operations of the transaction. */
(void) locator_drop_transient_class_name_entries (thread_p, NULL);
return log_2pc_prepare_global_tran (thread_p, global_tranid);
}
/*
* xtran_is_blocked - Is transaction suspended ?
*
* return:
*
* tran_index(in): Transaction index
*
* NOTE: Find if current transaction is suspended.
*/
bool
xtran_is_blocked (THREAD_ENTRY * thread_p, int tran_index)
{
return lock_is_waiting_transaction (tran_index);
}
/*
* xtran_server_has_updated - Has transaction updated the database ?
*
* return:
*
* NOTE: Find if the transaction has dirtied the database. We say that
* a transaction has updated the database, if it has log
* something and it has a write lock on an object, or if there
* has been an update to a remote database.
*/
bool
xtran_server_has_updated (THREAD_ENTRY * thread_p)
{
return ((logtb_has_updated (thread_p) && lock_has_xlock (thread_p)));
}
/*
* xtran_wait_server_active_trans -
*
* return:
*
* NOTE: wait for server threads with current tran index to finish
*/
int
xtran_wait_server_active_trans (THREAD_ENTRY * thread_p)
{
#if defined(SERVER_MODE)
size_t prev_thrd_cnt, thrd_cnt;
CSS_CONN_ENTRY *p;
int tran_index, client_id;
bool continue_check;
if (thread_p == NULL)
{
thread_p = thread_get_thread_entry_info ();
}
p = thread_p->conn_entry;
if (p == NULL)
{
return 0;
}
tran_index = thread_p->tran_index;
client_id = p->client_id;
loop:
prev_thrd_cnt = css_count_transaction_worker_threads (thread_p, tran_index, client_id);
if (prev_thrd_cnt > 0)
{
if (!logtb_is_interrupted_tran (thread_p, false, &continue_check, tran_index))
{
logtb_set_tran_index_interrupt (thread_p, tran_index, true);
}
}
while ((thrd_cnt = css_count_transaction_worker_threads (thread_p, tran_index, client_id)) >= prev_thrd_cnt
&& thrd_cnt > 0)
{
/* Some threads may wait for data from the m-driver. It's possible from the fact that css_server_thread() is
* responsible for receiving every data from which is sent by a client and all m-drivers. We must have chance to
* receive data from them. */
thread_sleep (10); /* 10 msec */
}
if (thrd_cnt > 0)
{
goto loop;
}
logtb_set_tran_index_interrupt (thread_p, tran_index, false);
#endif /* SERVER_MODE */
return 0;
}
/*
* xtran_server_is_active_and_has_updated - Find if transaction is active and
* has updated the database ?
* return:
*
* NOTE: Find if the transaction is active and has dirtied the
* database. We say that a transaction has updated the database,
* if it has log something and it has a write lock on an object.
*/
int
xtran_server_is_active_and_has_updated (THREAD_ENTRY * thread_p)
{
return (logtb_is_current_active (thread_p) && xtran_server_has_updated (thread_p));
}
/*
* xtran_get_local_transaction_id -
*
* return: NO_ERROR if all OK, ER_ status otherwise
*
* trid(in):
*
* NOTE:
*/
int
xtran_get_local_transaction_id (THREAD_ENTRY * thread_p, DB_VALUE * trid)
{
int error_code = NO_ERROR;
assert (trid != (DB_VALUE *) NULL);
error_code = db_value_domain_init (trid, DB_TYPE_INTEGER, 0, 0);
if (error_code == NO_ERROR)
{
db_make_int (trid, logtb_find_current_tranid (thread_p));
}
return error_code;
}
/*
* xtran_lock_rep_read - lock RR transaction with specified lock
*
* return: NO_ERROR if all OK, ER_ status otherwise
*
* thread_p(in):
* lock_rr_tran(in):
*
* NOTE:
*/
int
xtran_lock_rep_read (THREAD_ENTRY * thread_p, LOCK lock_rr_tran)
{
return lock_rep_read_tran (thread_p, lock_rr_tran, LK_UNCOND_LOCK);
}
#if defined(SERVER_MODE)
/*
* xtran_should_connection_reset - Reset on commit
*
* return: true if reset is required
*
* thread_p(in): this thread handle
* has_updated(in): true, if has updated
*/
bool
xtran_should_connection_reset (THREAD_ENTRY * thread_p, bool has_updated)
{
int client_type;
const char *hostname;
HA_SERVER_STATE ha_state;
bool should_conn_reset = false;
client_type = logtb_find_current_client_type (thread_p);
hostname = logtb_find_current_client_hostname (thread_p);
ha_state = css_ha_server_state ();
if (has_updated && ha_state == HA_SERVER_STATE_TO_BE_STANDBY && BOOT_NORMAL_CLIENT_TYPE (client_type))
{
should_conn_reset = true;
er_log_debug (ARG_FILE_LINE,
"xtran_should_connection_reset: (has_updated && to-be-standby && normal client)"
" DB_CONNECTION_STATUS_RESET\n");
}
else if (ha_state == HA_SERVER_STATE_STANDBY)
{
/* be aware that the order of if conditions is important */
if (BOOT_CSQL_CLIENT_TYPE (client_type))
{
thread_p->conn_entry->reset_on_commit = false;
}
else if (client_type == DB_CLIENT_TYPE_BROKER)
{
should_conn_reset = true;
er_log_debug (ARG_FILE_LINE,
"xtran_should_connection_reset: (standby && read-write broker) DB_CONNECTION_STATUS_RESET\n");
}
else if (BOOT_NORMAL_CLIENT_TYPE (client_type) && thread_p->conn_entry->reset_on_commit == true)
{
should_conn_reset = true;
thread_p->conn_entry->reset_on_commit = false;
er_log_debug (ARG_FILE_LINE,
"xtran_should_connection_reset: (standby && conn->reset_on_commit && normal client)"
" DB_CONNECTION_STATUS_RESET\n");
}
else if (BOOT_BROKER_AND_DEFAULT_CLIENT_TYPE (client_type) && css_is_ha_repl_delayed () == true)
{
if (thread_p->conn_entry->ignore_repl_delay == false)
{
should_conn_reset = true;
er_log_debug (ARG_FILE_LINE,
"xtran_should_connection_reset: (standby && replication delay && broker and default client)"
" DB_CONNECTION_STATUS_RESET\n");
}
thread_p->conn_entry->reset_on_commit = false;
}
}
else if (ha_state == HA_SERVER_STATE_ACTIVE && client_type == DB_CLIENT_TYPE_SLAVE_ONLY_BROKER)
{
should_conn_reset = true;
er_log_debug (ARG_FILE_LINE,
"xtran_should_connection_reset: (active && slave only broker) DB_CONNECTION_STATUS_RESET\n");
}
else if (ha_state == HA_SERVER_STATE_MAINTENANCE
&& !BOOT_IS_ALLOWED_CLIENT_TYPE_IN_MT_MODE (hostname, boot_Host_name, client_type))
{
should_conn_reset = true;
er_log_debug (ARG_FILE_LINE,
"xtran_should_connection_reset: (maintenance && remote normal client type)"
" DB_CONNECTION_STATUS_RESET\n");
}
return should_conn_reset;
}
#endif