launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #05981
[Merge] lp:~allenap/launchpad/handle-status-ro-crash-bug-905853 into lp:launchpad
Gavin Panella has proposed merging lp:~allenap/launchpad/handle-status-ro-crash-bug-905853 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #905853 in Launchpad itself: "Most PackageBuild._handleStatus* methods attempt to write in a read-only transaction"
https://bugs.launchpad.net/launchpad/+bug/905853
For more details, see:
https://code.launchpad.net/~allenap/launchpad/handle-status-ro-crash-bug-905853/+merge/86298
This fixes the problem described in the bug, but it also fixes the tests to (a) run correctly (i.e. with AsynchronousDeferredRunTest) and (b) run with a read-only transaction access mode by default. Combined, these brought out the problems described in the bug. This branch also (c) changes DatabaseTransactionPolicy.__enter__() to only care about open transactions when we're moving from a read-write access mode to read-only.
--
https://code.launchpad.net/~allenap/launchpad/handle-status-ro-crash-bug-905853/+merge/86298
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/handle-status-ro-crash-bug-905853 into lp:launchpad.
=== modified file 'lib/lp/buildmaster/model/packagebuild.py'
--- lib/lp/buildmaster/model/packagebuild.py 2011-12-08 11:39:10 +0000
+++ lib/lp/buildmaster/model/packagebuild.py 2011-12-19 21:41:27 +0000
@@ -475,7 +475,9 @@
MANUALDEPWAIT, store available information, remove BuildQueue
entry and release builder slave for another job.
"""
- self.status = BuildStatus.MANUALDEPWAIT
+ with DatabaseTransactionPolicy(read_only=False):
+ self.status = BuildStatus.MANUALDEPWAIT
+ transaction.commit()
def build_info_stored(ignored):
logger.critical("***** %s is MANUALDEPWAIT *****"
@@ -495,7 +497,9 @@
job as CHROOTFAIL, store available information, remove BuildQueue
and release the builder.
"""
- self.status = BuildStatus.CHROOTWAIT
+ with DatabaseTransactionPolicy(read_only=False):
+ self.status = BuildStatus.CHROOTWAIT
+ transaction.commit()
def build_info_stored(ignored):
logger.critical(
=== modified file 'lib/lp/buildmaster/tests/test_packagebuild.py'
--- lib/lp/buildmaster/tests/test_packagebuild.py 2011-11-09 11:50:17 +0000
+++ lib/lp/buildmaster/tests/test_packagebuild.py 2011-12-19 21:41:27 +0000
@@ -12,6 +12,8 @@
import tempfile
from storm.store import Store
+from testtools.deferredruntest import AsynchronousDeferredRunTest
+import transaction
from zope.component import getUtility
from zope.security.interfaces import Unauthorized
from zope.security.proxy import removeSecurityProxy
@@ -37,6 +39,7 @@
from lp.buildmaster.model.packagebuild import PackageBuild
from lp.buildmaster.tests.mock_slaves import WaitingSlave
from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.services.database.transaction_policy import DatabaseTransactionPolicy
from lp.testing import (
login,
login_person,
@@ -285,6 +288,7 @@
"""Tests for `IPackageBuild`s handleStatus method."""
layer = LaunchpadZopelessLayer
+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=20)
def makeBuild(self):
"""Allow classes to override the build with which the test runs."""
@@ -319,6 +323,12 @@
removeSecurityProxy(self.build).verifySuccessfulUpload = FakeMethod(
result=True)
+ # Save everything done so far then shift into a read-only transaction
+ # access mode by default.
+ transaction.commit()
+ policy = DatabaseTransactionPolicy(read_only=True)
+ self.useContext(policy)
+
def assertResultCount(self, count, result):
self.assertEquals(
1, len(os.listdir(os.path.join(self.upload_root, result))))
@@ -363,7 +373,9 @@
def test_handleStatus_OK_sets_build_log(self):
# The build log is set during handleStatus.
- removeSecurityProxy(self.build).log = None
+ with DatabaseTransactionPolicy(read_only=False):
+ removeSecurityProxy(self.build).log = None
+ transaction.commit()
self.assertEqual(None, self.build.log)
d = self.build.handleStatus('OK', None, {
'filemap': {'myfile.py': 'test_file_hash'},
@@ -402,7 +414,10 @@
def test_date_finished_set(self):
# The date finished is updated during handleStatus_OK.
- removeSecurityProxy(self.build).date_finished = None
+ with DatabaseTransactionPolicy(read_only=False):
+ removeSecurityProxy(self.build).date_finished = None
+ transaction.commit()
+
self.assertEqual(None, self.build.date_finished)
d = self.build.handleStatus('OK', None, {
'filemap': {'myfile.py': 'test_file_hash'},
=== modified file 'lib/lp/services/database/tests/test_transaction_policy.py'
--- lib/lp/services/database/tests/test_transaction_policy.py 2011-09-29 05:49:18 +0000
+++ lib/lp/services/database/tests/test_transaction_policy.py 2011-12-19 21:41:27 +0000
@@ -76,16 +76,26 @@
self.assertRaises(InternalError, make_forbidden_update)
- def test_will_not_start_in_ongoing_transaction(self):
- # You cannot enter a DatabaseTransactionPolicy while already in
- # a transaction.
+ def test_will_not_go_read_only_when_read_write_transaction_ongoing(self):
+ # You cannot enter a read-only DatabaseTransactionPolicy while in an
+ # active read-write transaction.
def enter_policy():
- with DatabaseTransactionPolicy():
+ with DatabaseTransactionPolicy(read_only=True):
pass
self.writeToDatabase()
self.assertRaises(TransactionInProgress, enter_policy)
+ def test_will_go_read_write_when_read_write_transaction_ongoing(self):
+ # You can enter a read-write DatabaseTransactionPolicy while in an
+ # active read-write transaction.
+ def enter_policy():
+ with DatabaseTransactionPolicy(read_only=False):
+ pass
+
+ self.writeToDatabase()
+ enter_policy() # No exception.
+
def test_successful_exit_requires_commit_or_abort(self):
# If a read-write policy exits normally (which would probably
# indicate successful completion of its code), it requires that
=== modified file 'lib/lp/services/database/transaction_policy.py'
--- lib/lp/services/database/transaction_policy.py 2011-10-10 06:23:12 +0000
+++ lib/lp/services/database/transaction_policy.py 2011-12-19 21:41:27 +0000
@@ -92,9 +92,18 @@
:raise TransactionInProgress: if a transaction was already ongoing.
"""
- self._checkNoTransaction(
- "Entered DatabaseTransactionPolicy while in a transaction.")
+ # We must check the transaction status before checking the current
+ # policy because getting the policy causes a status change.
+ in_transaction = self._isInTransaction()
self.previous_policy = self._getCurrentPolicy()
+ # If the current transaction is read-write and we're moving to
+ # read-only then we should check for a transaction. If the current
+ # policy is read-only then we don't care if a transaction is in
+ # progress.
+ if in_transaction and self.read_only and not self.previous_policy:
+ raise TransactionInProgress(
+ "Attempting to enter a read-only transaction while holding "
+ "open a read-write transaction.")
self._setPolicy(self.read_only)
# Commit should include the policy itself. If this breaks
# because the transaction was already in a failed state before
=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py'
--- lib/lp/soyuz/tests/test_binarypackagebuild.py 2011-11-14 06:36:57 +0000
+++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2011-12-19 21:41:27 +0000
@@ -10,7 +10,6 @@
import pytz
from storm.store import Store
-from testtools.deferredruntest import AsynchronousDeferredRunTest
from zope.component import getUtility
from zope.security.proxy import removeSecurityProxy
@@ -575,9 +574,6 @@
MakeBinaryPackageBuildMixin, TestHandleStatusMixin, TestCaseWithFactory):
"""IPackageBuild.handleStatus works with binary builds."""
- layer = LaunchpadZopelessLayer
- run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=20)
-
class TestBinaryPackageBuildWebservice(TestCaseWithFactory):
"""Test cases for BinaryPackageBuild on the webservice.