blob: fbd78bc796265a2778aedafbd24968795cc8aa8a [file] [log] [blame]
/*
* Copyright (C) 2007, 2008, 2013 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/modules/webdatabase/sql_transaction_backend.h"
#include <memory>
#include "base/stl_util.h"
#include "third_party/blink/renderer/modules/webdatabase/database.h"
#include "third_party/blink/renderer/modules/webdatabase/database_authorizer.h"
#include "third_party/blink/renderer/modules/webdatabase/database_context.h"
#include "third_party/blink/renderer/modules/webdatabase/database_thread.h"
#include "third_party/blink/renderer/modules/webdatabase/database_tracker.h"
#include "third_party/blink/renderer/modules/webdatabase/sql_error.h"
#include "third_party/blink/renderer/modules/webdatabase/sql_statement_backend.h"
#include "third_party/blink/renderer/modules/webdatabase/sql_transaction.h"
#include "third_party/blink/renderer/modules/webdatabase/sql_transaction_client.h"
#include "third_party/blink/renderer/modules/webdatabase/sql_transaction_coordinator.h"
#include "third_party/blink/renderer/modules/webdatabase/sqlite/sql_value.h"
#include "third_party/blink/renderer/modules/webdatabase/sqlite/sqlite_transaction.h"
#include "third_party/blink/renderer/modules/webdatabase/storage_log.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
// How does a SQLTransaction work?
// ==============================
// The SQLTransaction is a state machine that executes a series of states /
// steps.
//
// The work of the transaction states are defined in section of 4.3.2 of the
// webdatabase spec: http://dev.w3.org/html5/webdatabase/#processing-model
//
// the State Transition Graph at a glance:
// ======================================
//
// Backend . Frontend
// (works with SQLiteDatabase) . (works with Script)
// =========================== . ===================
// .
// 1. Idle .
// v .
// 2. AcquireLock .
// v .
// 3. OpenTransactionAndPreflight -----------------------------------.
// | . |
// `-------------------------> 8. DeliverTransactionCallback --. |
// . | v v
// ,------------------------------' 9. DeliverTransactionErrorCallback +
// | . ^ ^ ^ |
// v . | | | |
// 4. RunStatements -----------------------------------------------' | | |
// | ^ ^ | ^ | . | | |
// |--------' | | | `------> 10. DeliverStatementCallback +----' | |
// | | | `---------------------------------------' | |
// | | `-----------> 11. DeliverQuotaIncreaseCallback + | |
// | `-----------------------------------------------' | |
// v . | |
// 5. PostflightAndCommit --+------------------------------------------' |
// |----> 12. DeliverSuccessCallback + |
// ,--------------------' . | |
// v . | |
// 6. CleanupAndTerminate <-----------------------------------' |
// v ^ . |
// 0. End | . |
// | . |
// 7: CleanupAfterTransactionErrorCallback <--------------------'
// .
//
// the States and State Transitions:
// ================================
// 0. SQLTransactionState::End
// - the end state.
//
// 1. SQLTransactionState::Idle
// - placeholder state while waiting on frontend/backend, etc. See
// comment on "State transitions between SQLTransaction and
// SQLTransactionBackend" below.
//
// 2. SQLTransactionState::AcquireLock (runs in backend)
// - this is the start state.
// - acquire the "lock".
// - on "lock" acquisition, goto
// SQLTransactionState::OpenTransactionAndPreflight.
//
// 3. SQLTransactionState::openTransactionAndPreflight (runs in backend)
// - Sets up an SQLiteTransaction.
// - begin the SQLiteTransaction.
// - call the SQLTransactionWrapper preflight if available.
// - schedule script callback.
// - on error, goto
// SQLTransactionState::DeliverTransactionErrorCallback.
// - goto SQLTransactionState::DeliverTransactionCallback.
//
// 4. SQLTransactionState::DeliverTransactionCallback (runs in frontend)
// - invoke the script function callback() if available.
// - on error, goto
// SQLTransactionState::DeliverTransactionErrorCallback.
// - goto SQLTransactionState::RunStatements.
//
// 5. SQLTransactionState::DeliverTransactionErrorCallback (runs in
// frontend)
// - invoke the script function errorCallback if available.
// - goto SQLTransactionState::CleanupAfterTransactionErrorCallback.
//
// 6. SQLTransactionState::RunStatements (runs in backend)
// - while there are statements {
// - run a statement.
// - if statementCallback is available, goto
// SQLTransactionState::DeliverStatementCallback.
// - on error,
// goto SQLTransactionState::DeliverQuotaIncreaseCallback, or
// goto SQLTransactionState::DeliverStatementCallback, or
// goto SQLTransactionState::deliverTransactionErrorCallback.
// }
// - goto SQLTransactionState::PostflightAndCommit.
//
// 7. SQLTransactionState::DeliverStatementCallback (runs in frontend)
// - invoke script statement callback (assume available).
// - on error, goto
// SQLTransactionState::DeliverTransactionErrorCallback.
// - goto SQLTransactionState::RunStatements.
//
// 8. SQLTransactionState::DeliverQuotaIncreaseCallback (runs in frontend)
// - give client a chance to increase the quota.
// - goto SQLTransactionState::RunStatements.
//
// 9. SQLTransactionState::PostflightAndCommit (runs in backend)
// - call the SQLTransactionWrapper postflight if available.
// - commit the SQLiteTansaction.
// - on error, goto
// SQLTransactionState::DeliverTransactionErrorCallback.
// - if successCallback is available, goto
// SQLTransactionState::DeliverSuccessCallback.
// else goto SQLTransactionState::CleanupAndTerminate.
//
// 10. SQLTransactionState::DeliverSuccessCallback (runs in frontend)
// - invoke the script function successCallback() if available.
// - goto SQLTransactionState::CleanupAndTerminate.
//
// 11. SQLTransactionState::CleanupAndTerminate (runs in backend)
// - stop and clear the SQLiteTransaction.
// - release the "lock".
// - goto SQLTransactionState::End.
//
// 12. SQLTransactionState::CleanupAfterTransactionErrorCallback (runs in
// backend)
// - rollback the SQLiteTransaction.
// - goto SQLTransactionState::CleanupAndTerminate.
//
// State transitions between SQLTransaction and SQLTransactionBackend
// ==================================================================
// As shown above, there are state transitions that crosses the boundary between
// the frontend and backend. For example,
//
// OpenTransactionAndPreflight (state 3 in the backend)
// transitions to DeliverTransactionCallback (state 8 in the frontend),
// which in turn transitions to RunStatements (state 4 in the backend).
//
// This cross boundary transition is done by posting transition requests to the
// other side and letting the other side's state machine execute the state
// transition in the appropriate thread (i.e. the script thread for the
// frontend, and the database thread for the backend).
//
// Logically, the state transitions work as shown in the graph above. But
// physically, the transition mechanism uses the Idle state (both in the
// frontend and backend) as a waiting state for further activity. For example,
// taking a closer look at the 3 state transition example above, what actually
// happens is as follows:
//
// Step 1:
// ======
// In the frontend thread:
// - waiting quietly is Idle. Not doing any work.
//
// In the backend:
// - is in OpenTransactionAndPreflight, and doing its work.
// - when done, it transits to the backend DeliverTransactionCallback.
// - the backend DeliverTransactionCallback sends a request to the frontend
// to transit to DeliverTransactionCallback, and then itself transits to
// Idle.
//
// Step 2:
// ======
// In the frontend thread:
// - transits to DeliverTransactionCallback and does its work.
// - when done, it transits to the frontend RunStatements.
// - the frontend RunStatements sends a request to the backend to transit
// to RunStatements, and then itself transits to Idle.
//
// In the backend:
// - waiting quietly in Idle.
//
// Step 3:
// ======
// In the frontend thread:
// - waiting quietly is Idle. Not doing any work.
//
// In the backend:
// - transits to RunStatements, and does its work.
// ...
//
// So, when the frontend or backend are not active, they will park themselves in
// their Idle states. This means their m_nextState is set to Idle, but they
// never actually run the corresponding state function. Note: for both the
// frontend and backend, the state function for Idle is unreachableState().
//
// The states that send a request to their peer across the front/back boundary
// are implemented with just 2 functions: SQLTransaction::sendToBackendState()
// and SQLTransactionBackend::sendToFrontendState(). These state functions do
// nothing but sends a request to the other side to transit to the current
// state (indicated by m_nextState), and then transits itself to the Idle state
// to wait for further action.
// The Life-Cycle of a SQLTransaction i.e. Who's keeping the SQLTransaction
// alive?
// ==============================================================================
// The RefPtr chain goes something like this:
//
// At birth (in Database::runTransaction()):
// ====================================================
// Database
// // HeapDeque<Member<SQLTransactionBackend>> m_transactionQueue
// // points to ...
// --> SQLTransactionBackend
// // Member<SQLTransaction> m_frontend points to ...
// --> SQLTransaction
// // Member<SQLTransactionBackend> m_backend points to ...
// --> SQLTransactionBackend // which is a circular reference.
//
// Note: there's a circular reference between the SQLTransaction front-end
// and back-end. This circular reference is established in the constructor
// of the SQLTransactionBackend. The circular reference will be broken by
// calling doCleanup() to nullify m_frontend. This is done at the end of the
// transaction's clean up state (i.e. when the transaction should no longer
// be in use thereafter), or if the database was interrupted. See comments
// on "What happens if a transaction is interrupted?" below for details.
//
// After scheduling the transaction with the DatabaseThread
// (Database::scheduleTransaction()):
// ======================================================================================================
// DatabaseThread
// // MessageQueue<DatabaseTask> m_queue points to ...
// --> DatabaseTransactionTask
// // Member<SQLTransactionBackend> m_transaction points to ...
// --> SQLTransactionBackend
// // Member<SQLTransaction> m_frontend points to ...
// --> SQLTransaction
// // Member<SQLTransactionBackend> m_backend points to ...
// --> SQLTransactionBackend // which is a circular reference.
//
// When executing the transaction (in DatabaseThread::databaseThread()):
// ====================================================================
// std::unique_ptr<DatabaseTask> task;
// // points to ...
// --> DatabaseTransactionTask
// // Member<SQLTransactionBackend> m_transaction points to ...
// --> SQLTransactionBackend
// // Member<SQLTransaction> m_frontend;
// --> SQLTransaction
// // Member<SQLTransactionBackend> m_backend points to ...
// --> SQLTransactionBackend // which is a circular reference.
//
// At the end of cleanupAndTerminate():
// ===================================
// At the end of the cleanup state, the SQLTransactionBackend::m_frontend is
// nullified. If by then, a JSObject wrapper is referring to the
// SQLTransaction, then the reference chain looks like this:
//
// JSObjectWrapper
// --> SQLTransaction
// // in Member<SQLTransactionBackend> m_backend points to ...
// --> SQLTransactionBackend
// // which no longer points back to its SQLTransaction.
//
// When the GC collects the corresponding JSObject, the above chain will be
// cleaned up and deleted.
//
// If there is no JSObject wrapper referring to the SQLTransaction when the
// cleanup states nullify SQLTransactionBackend::m_frontend, the
// SQLTransaction will deleted then. However, there will still be a
// DatabaseTask pointing to the SQLTransactionBackend (see the "When
// executing the transaction" chain above). This will keep the
// SQLTransactionBackend alive until DatabaseThread::databaseThread()
// releases its task std::unique_ptr.
//
// What happens if a transaction is interrupted?
// ============================================
// If the transaction is interrupted half way, it won't get to run to state
// CleanupAndTerminate, and hence, would not have called
// SQLTransactionBackend's doCleanup(). doCleanup() is where we nullify
// SQLTransactionBackend::m_frontend to break the reference cycle between
// the frontend and backend. Hence, we need to cleanup the transaction by
// other means.
//
// Note: calling SQLTransactionBackend::notifyDatabaseThreadIsShuttingDown()
// is effectively the same as calling SQLTransactionBackend::doClean().
//
// In terms of who needs to call doCleanup(), there are 5 phases in the
// SQLTransactionBackend life-cycle. These are the phases and how the clean
// up is done:
//
// Phase 1. After Birth, before scheduling
//
// - To clean up, DatabaseThread::databaseThread() will call
// Database::close() during its shutdown.
// - Database::close() will iterate
// Database::m_transactionQueue and call
// notifyDatabaseThreadIsShuttingDown() on each transaction there.
//
// Phase 2. After scheduling, before state AcquireLock
//
// - If the interruption occures before the DatabaseTransactionTask is
// scheduled in DatabaseThread::m_queue but hasn't gotten to execute
// (i.e. DatabaseTransactionTask::performTask() has not been called),
// then the DatabaseTransactionTask may get destructed before it ever
// gets to execute.
// - To clean up, the destructor will check if the task's m_wasExecuted is
// set. If not, it will call notifyDatabaseThreadIsShuttingDown() on
// the task's transaction.
//
// Phase 3. After state AcquireLock, before "lockAcquired"
//
// - In this phase, the transaction would have been added to the
// SQLTransactionCoordinator's CoordinationInfo's pendingTransactions.
// - To clean up, during shutdown, DatabaseThread::databaseThread() calls
// SQLTransactionCoordinator::shutdown(), which calls
// notifyDatabaseThreadIsShuttingDown().
//
// Phase 4: After "lockAcquired", before state CleanupAndTerminate
//
// - In this phase, the transaction would have been added either to the
// SQLTransactionCoordinator's CoordinationInfo's activeWriteTransaction
// or activeReadTransactions.
// - To clean up, during shutdown, DatabaseThread::databaseThread() calls
// SQLTransactionCoordinator::shutdown(), which calls
// notifyDatabaseThreadIsShuttingDown().
//
// Phase 5: After state CleanupAndTerminate
//
// - This is how a transaction ends normally.
// - state CleanupAndTerminate calls doCleanup().
namespace blink {
SQLTransactionBackend* SQLTransactionBackend::Create(
Database* db,
SQLTransaction* frontend,
SQLTransactionWrapper* wrapper,
bool read_only) {
return MakeGarbageCollected<SQLTransactionBackend>(db, frontend, wrapper,
read_only);
}
SQLTransactionBackend::SQLTransactionBackend(Database* db,
SQLTransaction* frontend,
SQLTransactionWrapper* wrapper,
bool read_only)
: frontend_(frontend),
database_(db),
wrapper_(wrapper),
has_callback_(frontend_->HasCallback()),
has_success_callback_(frontend_->HasSuccessCallback()),
has_error_callback_(frontend_->HasErrorCallback()),
should_retry_current_statement_(false),
modified_database_(false),
lock_acquired_(false),
read_only_(read_only),
has_version_mismatch_(false) {
DCHECK(IsMainThread());
DCHECK(database_);
frontend_->SetBackend(this);
requested_state_ = SQLTransactionState::kAcquireLock;
}
SQLTransactionBackend::~SQLTransactionBackend() {
DCHECK(!sqlite_transaction_);
}
void SQLTransactionBackend::Trace(blink::Visitor* visitor) {
visitor->Trace(wrapper_);
}
void SQLTransactionBackend::DoCleanup() {
if (!frontend_)
return;
// Break the reference cycle. See comment about the life-cycle above.
frontend_ = nullptr;
DCHECK(GetDatabase()
->GetDatabaseContext()
->GetDatabaseThread()
->IsDatabaseThread());
MutexLocker locker(statement_mutex_);
statement_queue_.clear();
if (sqlite_transaction_) {
// In the event we got here because of an interruption or error (i.e. if
// the transaction is in progress), we should roll it back here. Clearing
// m_sqliteTransaction invokes SQLiteTransaction's destructor which does
// just that. We might as well do this unconditionally and free up its
// resources because we're already terminating.
sqlite_transaction_.reset();
}
// Release the lock on this database
if (lock_acquired_)
database_->TransactionCoordinator()->ReleaseLock(this);
// Do some aggresive clean up here except for m_database.
//
// We can't clear m_database here because the frontend may asynchronously
// invoke SQLTransactionBackend::requestTransitToState(), and that function
// uses m_database to schedule a state transition. This may occur because
// the frontend (being in another thread) may already be on the way to
// requesting our next state before it detects an interruption.
//
// There is no harm in letting it finish making the request. It'll set
// m_requestedState, but we won't execute a transition to that state because
// we've already shut down the transaction.
//
// We also can't clear m_currentStatementBackend and m_transactionError.
// m_currentStatementBackend may be accessed asynchronously by the
// frontend's deliverStatementCallback() state. Similarly,
// m_transactionError may be accessed by deliverTransactionErrorCallback().
// This occurs if requests for transition to those states have already been
// registered with the frontend just prior to a clean up request arriving.
//
// So instead, let our destructor handle their clean up since this
// SQLTransactionBackend is guaranteed to not destruct until the frontend
// is also destructing.
wrapper_ = nullptr;
}
SQLStatement* SQLTransactionBackend::CurrentStatement() {
return current_statement_backend_->GetFrontend();
}
SQLErrorData* SQLTransactionBackend::TransactionError() {
return transaction_error_.get();
}
void SQLTransactionBackend::SetShouldRetryCurrentStatement(bool should_retry) {
DCHECK(!should_retry_current_statement_);
should_retry_current_statement_ = should_retry;
}
SQLTransactionBackend::StateFunction SQLTransactionBackend::StateFunctionFor(
SQLTransactionState state) {
static const StateFunction kStateFunctions[] = {
&SQLTransactionBackend::UnreachableState, // 0. end
&SQLTransactionBackend::UnreachableState, // 1. idle
&SQLTransactionBackend::AcquireLock, // 2.
&SQLTransactionBackend::OpenTransactionAndPreflight, // 3.
&SQLTransactionBackend::RunStatements, // 4.
&SQLTransactionBackend::PostflightAndCommit, // 5.
&SQLTransactionBackend::CleanupAndTerminate, // 6.
&SQLTransactionBackend::CleanupAfterTransactionErrorCallback, // 7.
// 8. deliverTransactionCallback
&SQLTransactionBackend::SendToFrontendState,
// 9. deliverTransactionErrorCallback
&SQLTransactionBackend::SendToFrontendState,
// 10. deliverStatementCallback
&SQLTransactionBackend::SendToFrontendState,
// 11. deliverQuotaIncreaseCallback
&SQLTransactionBackend::SendToFrontendState,
// 12. deliverSuccessCallback
&SQLTransactionBackend::SendToFrontendState,
};
DCHECK(base::size(kStateFunctions) ==
static_cast<int>(SQLTransactionState::kNumberOfStates));
DCHECK_LT(state, SQLTransactionState::kNumberOfStates);
return kStateFunctions[static_cast<int>(state)];
}
void SQLTransactionBackend::EnqueueStatementBackend(
SQLStatementBackend* statement_backend) {
DCHECK(IsMainThread());
MutexLocker locker(statement_mutex_);
statement_queue_.push_back(statement_backend);
}
void SQLTransactionBackend::ComputeNextStateAndCleanupIfNeeded() {
DCHECK(GetDatabase()
->GetDatabaseContext()
->GetDatabaseThread()
->IsDatabaseThread());
// Only honor the requested state transition if we're not supposed to be
// cleaning up and shutting down:
if (database_->Opened()) {
SetStateToRequestedState();
DCHECK(next_state_ == SQLTransactionState::kAcquireLock ||
next_state_ == SQLTransactionState::kOpenTransactionAndPreflight ||
next_state_ == SQLTransactionState::kRunStatements ||
next_state_ == SQLTransactionState::kPostflightAndCommit ||
next_state_ == SQLTransactionState::kCleanupAndTerminate ||
next_state_ ==
SQLTransactionState::kCleanupAfterTransactionErrorCallback);
#if DCHECK_IS_ON()
STORAGE_DVLOG(1) << "State " << NameForSQLTransactionState(next_state_);
#endif
return;
}
// If we get here, then we should be shutting down. Do clean up if needed:
if (next_state_ == SQLTransactionState::kEnd)
return;
next_state_ = SQLTransactionState::kEnd;
// If the database was stopped, don't do anything and cancel queued work
STORAGE_DVLOG(1) << "Database was stopped or interrupted - cancelling work "
"for this transaction";
// The current SQLite transaction should be stopped, as well
if (sqlite_transaction_) {
sqlite_transaction_->Stop();
sqlite_transaction_.reset();
}
// Terminate the frontend state machine. This also gets the frontend to
// call computeNextStateAndCleanupIfNeeded() and clear its wrappers
// if needed.
frontend_->RequestTransitToState(SQLTransactionState::kEnd);
// Redirect to the end state to abort, clean up, and end the transaction.
DoCleanup();
}
void SQLTransactionBackend::PerformNextStep() {
ComputeNextStateAndCleanupIfNeeded();
RunStateMachine();
}
void SQLTransactionBackend::ExecuteSQL(SQLStatement* statement,
const String& sql_statement,
const Vector<SQLValue>& arguments,
int permissions) {
DCHECK(IsMainThread());
EnqueueStatementBackend(SQLStatementBackend::Create(statement, sql_statement,
arguments, permissions));
}
void SQLTransactionBackend::NotifyDatabaseThreadIsShuttingDown() {
DCHECK(GetDatabase()
->GetDatabaseContext()
->GetDatabaseThread()
->IsDatabaseThread());
// If the transaction is in progress, we should roll it back here, since this
// is our last opportunity to do something related to this transaction on the
// DB thread. Amongst other work, doCleanup() will clear m_sqliteTransaction
// which invokes SQLiteTransaction's destructor, which will do the roll back
// if necessary.
DoCleanup();
}
SQLTransactionState SQLTransactionBackend::AcquireLock() {
database_->TransactionCoordinator()->AcquireLock(this);
return SQLTransactionState::kIdle;
}
void SQLTransactionBackend::LockAcquired() {
lock_acquired_ = true;
RequestTransitToState(SQLTransactionState::kOpenTransactionAndPreflight);
}
SQLTransactionState SQLTransactionBackend::OpenTransactionAndPreflight() {
DCHECK(GetDatabase()
->GetDatabaseContext()
->GetDatabaseThread()
->IsDatabaseThread());
DCHECK(!database_->SqliteDatabase().TransactionInProgress());
DCHECK(lock_acquired_);
STORAGE_DVLOG(1) << "Opening and preflighting transaction " << this;
// Set the maximum usage for this transaction if this transactions is not
// read-only.
if (!read_only_)
database_->SqliteDatabase().SetMaximumSize(database_->MaximumSize());
DCHECK(!sqlite_transaction_);
sqlite_transaction_ = std::make_unique<SQLiteTransaction>(
database_->SqliteDatabase(), read_only_);
database_->ResetDeletes();
database_->DisableAuthorizer();
sqlite_transaction_->begin();
database_->EnableAuthorizer();
// Spec 4.3.2.1+2: Open a transaction to the database, jumping to the error
// callback if that fails.
if (!sqlite_transaction_->InProgress()) {
DCHECK(!database_->SqliteDatabase().TransactionInProgress());
database_->ReportSqliteError(database_->SqliteDatabase().LastError());
transaction_error_ = SQLErrorData::Create(
SQLError::kDatabaseErr, "unable to begin transaction",
database_->SqliteDatabase().LastError(),
database_->SqliteDatabase().LastErrorMsg());
sqlite_transaction_.reset();
return NextStateForTransactionError();
}
// Note: We intentionally retrieve the actual version even with an empty
// expected version. In multi-process browsers, we take this opportunity to
// update the cached value for the actual version. In single-process browsers,
// this is just a map lookup.
String actual_version;
if (!database_->GetActualVersionForTransaction(actual_version)) {
database_->ReportSqliteError(database_->SqliteDatabase().LastError());
transaction_error_ =
SQLErrorData::Create(SQLError::kDatabaseErr, "unable to read version",
database_->SqliteDatabase().LastError(),
database_->SqliteDatabase().LastErrorMsg());
database_->DisableAuthorizer();
sqlite_transaction_.reset();
database_->EnableAuthorizer();
return NextStateForTransactionError();
}
has_version_mismatch_ = !database_->ExpectedVersion().IsEmpty() &&
(database_->ExpectedVersion() != actual_version);
// Spec 4.3.2.3: Perform preflight steps, jumping to the error callback if
// they fail.
if (wrapper_ && !wrapper_->PerformPreflight(this)) {
database_->DisableAuthorizer();
sqlite_transaction_.reset();
database_->EnableAuthorizer();
if (wrapper_->SqlError()) {
transaction_error_ = SQLErrorData::Create(*wrapper_->SqlError());
} else {
transaction_error_ = SQLErrorData::Create(
SQLError::kUnknownErr,
"unknown error occurred during transaction preflight");
}
return NextStateForTransactionError();
}
// Spec 4.3.2.4: Invoke the transaction callback with the new SQLTransaction
// object.
if (has_callback_)
return SQLTransactionState::kDeliverTransactionCallback;
// If we have no callback to make, skip pass to the state after:
return SQLTransactionState::kRunStatements;
}
SQLTransactionState SQLTransactionBackend::RunStatements() {
DCHECK(GetDatabase()
->GetDatabaseContext()
->GetDatabaseThread()
->IsDatabaseThread());
DCHECK(lock_acquired_);
SQLTransactionState next_state;
// If there is a series of statements queued up that are all successful and
// have no associated SQLStatementCallback objects, then we can burn through
// the queue.
do {
if (should_retry_current_statement_ &&
!sqlite_transaction_->WasRolledBackBySqlite()) {
should_retry_current_statement_ = false;
// FIXME - Another place that needs fixing up after
// <rdar://problem/5628468> is addressed.
// See ::openTransactionAndPreflight() for discussion
// Reset the maximum size here, as it was increased to allow us to retry
// this statement. m_shouldRetryCurrentStatement is set to true only when
// a statement exceeds the quota, which can happen only in a read-write
// transaction. Therefore, there is no need to check here if the
// transaction is read-write.
database_->SqliteDatabase().SetMaximumSize(database_->MaximumSize());
} else {
// If the current statement has already been run, failed due to quota
// constraints, and we're not retrying it, that means it ended in an
// error. Handle it now.
if (current_statement_backend_ &&
current_statement_backend_->LastExecutionFailedDueToQuota()) {
return NextStateForCurrentStatementError();
}
// Otherwise, advance to the next statement
GetNextStatement();
}
next_state = RunCurrentStatementAndGetNextState();
} while (next_state == SQLTransactionState::kRunStatements);
return next_state;
}
void SQLTransactionBackend::GetNextStatement() {
DCHECK(GetDatabase()
->GetDatabaseContext()
->GetDatabaseThread()
->IsDatabaseThread());
current_statement_backend_ = nullptr;
MutexLocker locker(statement_mutex_);
if (!statement_queue_.IsEmpty())
current_statement_backend_ = statement_queue_.TakeFirst();
}
SQLTransactionState
SQLTransactionBackend::RunCurrentStatementAndGetNextState() {
if (!current_statement_backend_) {
// No more statements to run. So move on to the next state.
return SQLTransactionState::kPostflightAndCommit;
}
database_->ResetAuthorizer();
if (has_version_mismatch_)
current_statement_backend_->SetVersionMismatchedError(database_.Get());
if (current_statement_backend_->Execute(database_.Get())) {
if (database_->LastActionChangedDatabase()) {
// Flag this transaction as having changed the database for later delegate
// notification.
modified_database_ = true;
}
if (current_statement_backend_->HasStatementCallback()) {
return SQLTransactionState::kDeliverStatementCallback;
}
// If we get here, then the statement doesn't have a callback to invoke.
// We can move on to the next statement. Hence, stay in this state.
return SQLTransactionState::kRunStatements;
}
if (current_statement_backend_->LastExecutionFailedDueToQuota()) {
return SQLTransactionState::kDeliverQuotaIncreaseCallback;
}
return NextStateForCurrentStatementError();
}
SQLTransactionState SQLTransactionBackend::NextStateForCurrentStatementError() {
// Spec 4.3.2.6.6: error - Call the statement's error callback, but if there
// was no error callback, or the transaction was rolled back, jump to the
// transaction error callback.
if (current_statement_backend_->HasStatementErrorCallback() &&
!sqlite_transaction_->WasRolledBackBySqlite())
return SQLTransactionState::kDeliverStatementCallback;
if (current_statement_backend_->SqlError()) {
transaction_error_ =
SQLErrorData::Create(*current_statement_backend_->SqlError());
} else {
transaction_error_ = SQLErrorData::Create(
SQLError::kDatabaseErr, "the statement failed to execute");
}
return NextStateForTransactionError();
}
SQLTransactionState SQLTransactionBackend::PostflightAndCommit() {
DCHECK(lock_acquired_);
// Spec 4.3.2.7: Perform postflight steps, jumping to the error callback if
// they fail.
if (wrapper_ && !wrapper_->PerformPostflight(this)) {
if (wrapper_->SqlError()) {
transaction_error_ = SQLErrorData::Create(*wrapper_->SqlError());
} else {
transaction_error_ = SQLErrorData::Create(
SQLError::kUnknownErr,
"unknown error occurred during transaction postflight");
}
return NextStateForTransactionError();
}
// Spec 4.3.2.7: Commit the transaction, jumping to the error callback if that
// fails.
DCHECK(sqlite_transaction_);
database_->DisableAuthorizer();
sqlite_transaction_->Commit();
database_->EnableAuthorizer();
// If the commit failed, the transaction will still be marked as "in progress"
if (sqlite_transaction_->InProgress()) {
if (wrapper_)
wrapper_->HandleCommitFailedAfterPostflight(this);
database_->ReportSqliteError(database_->SqliteDatabase().LastError());
transaction_error_ = SQLErrorData::Create(
SQLError::kDatabaseErr, "unable to commit transaction",
database_->SqliteDatabase().LastError(),
database_->SqliteDatabase().LastErrorMsg());
return NextStateForTransactionError();
}
// Vacuum the database if anything was deleted.
if (database_->HadDeletes())
database_->IncrementalVacuumIfNeeded();
// The commit was successful. If the transaction modified this database,
// notify the delegates.
if (modified_database_)
database_->TransactionClient()->DidCommitWriteTransaction(GetDatabase());
// Spec 4.3.2.8: Deliver success callback, if there is one.
return SQLTransactionState::kDeliverSuccessCallback;
}
SQLTransactionState SQLTransactionBackend::CleanupAndTerminate() {
DCHECK(lock_acquired_);
// Spec 4.3.2.9: End transaction steps. There is no next step.
STORAGE_DVLOG(1) << "Transaction " << this << " is complete";
DCHECK(!database_->SqliteDatabase().TransactionInProgress());
// Phase 5 cleanup. See comment on the SQLTransaction life-cycle above.
DoCleanup();
database_->InProgressTransactionCompleted();
return SQLTransactionState::kEnd;
}
SQLTransactionState SQLTransactionBackend::NextStateForTransactionError() {
DCHECK(transaction_error_);
if (has_error_callback_)
return SQLTransactionState::kDeliverTransactionErrorCallback;
// No error callback, so fast-forward to the next state and rollback the
// transaction.
return SQLTransactionState::kCleanupAfterTransactionErrorCallback;
}
SQLTransactionState
SQLTransactionBackend::CleanupAfterTransactionErrorCallback() {
DCHECK(lock_acquired_);
STORAGE_DVLOG(1) << "Transaction " << this << " is complete with an error";
database_->DisableAuthorizer();
if (sqlite_transaction_) {
// Spec 4.3.2.10: Rollback the transaction.
sqlite_transaction_->Rollback();
DCHECK(!database_->SqliteDatabase().TransactionInProgress());
sqlite_transaction_.reset();
}
database_->EnableAuthorizer();
DCHECK(!database_->SqliteDatabase().TransactionInProgress());
return SQLTransactionState::kCleanupAndTerminate;
}
// requestTransitToState() can be called from the frontend. Hence, it should
// NOT be modifying SQLTransactionBackend in general. The only safe field to
// modify is m_requestedState which is meant for this purpose.
void SQLTransactionBackend::RequestTransitToState(
SQLTransactionState next_state) {
#if DCHECK_IS_ON()
STORAGE_DVLOG(1) << "Scheduling " << NameForSQLTransactionState(next_state)
<< " for transaction " << this;
#endif
requested_state_ = next_state;
DCHECK_NE(requested_state_, SQLTransactionState::kEnd);
database_->ScheduleTransactionStep(this);
}
// This state function is used as a stub function to plug unimplemented states
// in the state dispatch table. They are unimplemented because they should
// never be reached in the course of correct execution.
SQLTransactionState SQLTransactionBackend::UnreachableState() {
NOTREACHED();
return SQLTransactionState::kEnd;
}
SQLTransactionState SQLTransactionBackend::SendToFrontendState() {
DCHECK_NE(next_state_, SQLTransactionState::kIdle);
frontend_->RequestTransitToState(next_state_);
return SQLTransactionState::kIdle;
}
} // namespace blink