Repository: googleapis/google-cloud-python

Issue:
Calling run_in_transaction twice causes the second call to fail
It seems that the `_transaction` object in Session is not being reset once the transaction commits. This leads to a "Transaction is already committed" error. Here is a simple code to demonstrate this:

```python
def run_txn():
    from google.cloud import spanner
    spanner_client = spanner.Client()
    instance_id = 'your-instance'
    instance = spanner_client.instance(instance_id)
    database_id = 'example-db'
    database = instance.database(database_id)
    def f(t):
      t.insert_or_update(
          table="Singers",
          columns=('SingerId', 'FirstName'),
          values = [(6, "A"),])
    database.run_in_transaction(f)
    database.run_in_transaction(f)

if __name__ == '__main__':
    run_txn()

```

The error that is raised is:

```python
Traceback (most recent call last):
  File "txn.py", line 17, in <module>
    run_txn()
  File "txn.py", line 14, in run_txn
    database.run_in_transaction(f)
  File "/usr/local/google/home/vikask/venvs/pythonq/local/lib/python2.7/site-packages/google/cloud/spanner/database.py", line 379, in run_in_transaction
    return session.run_in_transaction(func, *args, **kw)
  File "/usr/local/google/home/vikask/venvs/pythonq/local/lib/python2.7/site-packages/google/cloud/spanner/session.py", line 309, in run_in_transaction
    txn.commit()
  File "/usr/local/google/home/vikask/venvs/pythonq/local/lib/python2.7/site-packages/google/cloud/spanner/transaction.py", line 104, in commit
    self._check_state()
  File "/usr/local/google/home/vikask/venvs/pythonq/local/lib/python2.7/site-packages/google/cloud/spanner/transaction.py", line 46, in _check_state
    raise ValueError("Transaction is already committed")
ValueError: Transaction is already committed
```



Discussion:
The version of google-cloud-spanner I used was 0.24.1

Resolving patch:
diff --git a/spanner/google/cloud/spanner/session.py b/spanner/google/cloud/spanner/session.py
--- a/spanner/google/cloud/spanner/session.py
+++ b/spanner/google/cloud/spanner/session.py
@@ -311,7 +311,9 @@ def run_in_transaction(self, func, *args, **kw):
                 _delay_until_retry(exc, deadline)
                 del self._transaction
             else:
-                return txn.committed
+                committed = txn.committed
+                del self._transaction
+                return committed
 
 
 # pylint: disable=misplaced-bare-raise


Test patch:
diff --git a/spanner/tests/system/test_system.py b/spanner/tests/system/test_system.py
--- a/spanner/tests/system/test_system.py
+++ b/spanner/tests/system/test_system.py
@@ -323,6 +323,23 @@ def _unit_of_work(transaction, test):
         rows = list(self._db.execute_sql(self.SQL))
         self._check_row_data(rows)
 
+    def test_db_run_in_transaction_twice(self):
+        retry = RetryInstanceState(_has_all_ddl)
+        retry(self._db.reload)()
+
+        with self._db.batch() as batch:
+            batch.delete(self.TABLE, self.ALL)
+
+        def _unit_of_work(transaction, test):
+            transaction.insert_or_update(
+                test.TABLE, test.COLUMNS, test.ROW_DATA)
+
+        self._db.run_in_transaction(_unit_of_work, test=self)
+        self._db.run_in_transaction(_unit_of_work, test=self)
+
+        rows = list(self._db.execute_sql(self.SQL))
+        self._check_row_data(rows)
+
 
 class TestSessionAPI(unittest.TestCase, _TestData):
     ALL_TYPES_TABLE = 'all_types'
diff --git a/spanner/tests/unit/test_session.py b/spanner/tests/unit/test_session.py
--- a/spanner/tests/unit/test_session.py
+++ b/spanner/tests/unit/test_session.py
@@ -503,6 +503,7 @@ def unit_of_work(txn, *args, **kw):
             unit_of_work, 'abc', some_arg='def')
 
         self.assertEqual(committed, now)
+        self.assertIsNone(session._transaction)
         self.assertEqual(len(called_with), 1)
         txn, args, kw = called_with[0]
         self.assertIsInstance(txn, Transaction)