storage/StorageBaseStatementInternal.cpp
author Brian Stack <bstack@mozilla.com>
Wed, 12 Apr 2017 21:01:46 -0700
changeset 404168 5748607f3014e8cec573fccebce5dcbf83b6ffd6
parent 352374 b09d90288666fb3afb11877fc45527b904ef23db
child 415860 ec459f72f2ea2d7ed50f1935e1da3e8efe5c1185
child 415972 89963ba78c353b23feab0042ebe264049f8d4533
permissions -rw-r--r--
Bug 591688 - Notify when decision task fails r=dustin,gps This uses the email provided by mozilla-taskcluster to find who to email about failed decision tasks. It also adds some validation of the try syntax that we've previously ignored. Any platforms or build types specified in try sytax that don't exist in the full task graph will throw an error. MozReview-Commit-ID: JOKkLle7hEe

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "StorageBaseStatementInternal.h"

#include "nsProxyRelease.h"

#include "mozStorageBindingParamsArray.h"
#include "mozStorageStatementData.h"
#include "mozStorageAsyncStatementExecution.h"

namespace mozilla {
namespace storage {

////////////////////////////////////////////////////////////////////////////////
//// Local Classes

/**
 * Used to finalize an asynchronous statement on the background thread.
 */
class AsyncStatementFinalizer : public Runnable
{
public:
  /**
   * Constructor for the event.
   *
   * @param aStatement
   *        We need the AsyncStatement to be able to get at the sqlite3_stmt;
   *        we only access/create it on the async thread.
   * @param aConnection
   *        We need the connection to know what thread to release the statement
   *        on.  We release the statement on that thread since releasing the
   *        statement might end up releasing the connection too.
   */
  AsyncStatementFinalizer(StorageBaseStatementInternal *aStatement,
                          Connection *aConnection)
  : mStatement(aStatement)
  , mConnection(aConnection)
  {
  }

  NS_IMETHOD Run() override
  {
    if (mStatement->mAsyncStatement) {
      sqlite3_finalize(mStatement->mAsyncStatement);
      mStatement->mAsyncStatement = nullptr;
    }

    nsCOMPtr<nsIThread> targetThread(mConnection->threadOpenedOn);
    NS_ProxyRelease(targetThread, mStatement.forget());
    return NS_OK;
  }
private:
  RefPtr<StorageBaseStatementInternal> mStatement;
  RefPtr<Connection> mConnection;
};

/**
 * Finalize a sqlite3_stmt on the background thread for a statement whose
 * destructor was invoked and the statement was non-null.
 */
class LastDitchSqliteStatementFinalizer : public Runnable
{
public:
  /**
   * Event constructor.
   *
   * @param aConnection
   *        Used to keep the connection alive.  If we failed to do this, it
   *        is possible that the statement going out of scope invoking us
   *        might have the last reference to the connection and so trigger
   *        an attempt to close the connection which is doomed to fail
   *        (because the asynchronous execution thread must exist which will
   *        trigger the failure case).
   * @param aStatement
   *        The sqlite3_stmt to finalize.  This object takes ownership /
   *        responsibility for the instance and all other references to it
   *        should be forgotten.
   */
  LastDitchSqliteStatementFinalizer(RefPtr<Connection> &aConnection,
                                    sqlite3_stmt *aStatement)
  : mConnection(aConnection)
  , mAsyncStatement(aStatement)
  {
    NS_PRECONDITION(aConnection, "You must provide a Connection");
  }

  NS_IMETHOD Run() override
  {
    (void)::sqlite3_finalize(mAsyncStatement);
    mAsyncStatement = nullptr;

    nsCOMPtr<nsIThread> target(mConnection->threadOpenedOn);
    (void)::NS_ProxyRelease(target, mConnection.forget());
    return NS_OK;
  }
private:
  RefPtr<Connection> mConnection;
  sqlite3_stmt *mAsyncStatement;
};

////////////////////////////////////////////////////////////////////////////////
//// StorageBaseStatementInternal

StorageBaseStatementInternal::StorageBaseStatementInternal()
: mAsyncStatement(nullptr)
{
}

void
StorageBaseStatementInternal::asyncFinalize()
{
  nsIEventTarget *target = mDBConnection->getAsyncExecutionTarget();
  if (target) {
    // Attempt to finalize asynchronously
    nsCOMPtr<nsIRunnable> event =
      new AsyncStatementFinalizer(this, mDBConnection);

    // Dispatch. Note that dispatching can fail, typically if
    // we have a race condition with asyncClose(). It's ok,
    // let asyncClose() win.
    (void)target->Dispatch(event, NS_DISPATCH_NORMAL);
  }
  // If we cannot get the background thread,
  // mozStorageConnection::AsyncClose() has already been called and
  // the statement either has been or will be cleaned up by
  // internalClose().
}

void
StorageBaseStatementInternal::destructorAsyncFinalize()
{
  if (!mAsyncStatement)
    return;

  // If we reach this point, our owner has not finalized this
  // statement, yet we are being destructed. If possible, we want to
  // auto-finalize it early, to release the resources early.
  nsIEventTarget *target = mDBConnection->getAsyncExecutionTarget();
  if (target) {
    // If we can get the async execution target, we can indeed finalize
    // the statement, as the connection is still open.
    bool isAsyncThread = false;
    (void)target->IsOnCurrentThread(&isAsyncThread);

    nsCOMPtr<nsIRunnable> event =
      new LastDitchSqliteStatementFinalizer(mDBConnection, mAsyncStatement);
    if (isAsyncThread) {
      (void)event->Run();
    } else {
      (void)target->Dispatch(event, NS_DISPATCH_NORMAL);
    }
  }

  // We might not be able to dispatch to the background thread,
  // presumably because it is being shutdown. Since said shutdown will
  // finalize the statement, we just need to clean-up around here.
  mAsyncStatement = nullptr;
}

NS_IMETHODIMP
StorageBaseStatementInternal::NewBindingParamsArray(
  mozIStorageBindingParamsArray **_array
)
{
  nsCOMPtr<mozIStorageBindingParamsArray> array = new BindingParamsArray(this);
  NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);

  array.forget(_array);
  return NS_OK;
}

NS_IMETHODIMP
StorageBaseStatementInternal::ExecuteAsync(
  mozIStorageStatementCallback *aCallback,
  mozIStoragePendingStatement **_stmt
)
{
  // We used to call Connection::ExecuteAsync but it takes a
  // mozIStorageBaseStatement signature because it is also a public API.  Since
  // our 'this' has no static concept of mozIStorageBaseStatement and Connection
  // would just QI it back across to a StorageBaseStatementInternal and the
  // actual logic is very simple, we now roll our own.
  nsTArray<StatementData> stmts(1);
  StatementData data;
  nsresult rv = getAsynchronousStatementData(data);
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_TRUE(stmts.AppendElement(data), NS_ERROR_OUT_OF_MEMORY);

  // Dispatch to the background
  return AsyncExecuteStatements::execute(stmts, mDBConnection,
                                         mNativeConnection, aCallback, _stmt);
}

NS_IMETHODIMP
StorageBaseStatementInternal::EscapeStringForLIKE(
  const nsAString &aValue,
  const char16_t aEscapeChar,
  nsAString &_escapedString
)
{
  const char16_t MATCH_ALL('%');
  const char16_t MATCH_ONE('_');

  _escapedString.Truncate(0);

  for (uint32_t i = 0; i < aValue.Length(); i++) {
    if (aValue[i] == aEscapeChar || aValue[i] == MATCH_ALL ||
        aValue[i] == MATCH_ONE) {
      _escapedString += aEscapeChar;
    }
    _escapedString += aValue[i];
  }
  return NS_OK;
}

} // namespace storage
} // namespace mozilla