← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:opt-in-zopeless-immediate-mail into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:opt-in-zopeless-immediate-mail into launchpad:master.

Commit message:
Mostly disable Zopeless immediate mail delivery

BaseMailer, job OOPS/error notifications, and a few other places still
need it.

This is a step towards being able to disable it across the board so that
we can avoid ever having situations where operation/mail/operation/mail
sequences send duplicate mail if a non-first operation fails and has to
be retried. There are still several things that need to be fixed,
notably archiveuploader; but this branch at least means that it's
disabled for tests by default, which will simplify the process of fixing
archiveuploader.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #29744 in Launchpad itself: "In Zopeless, mails should be sent only when the transaction is committed"
  https://bugs.launchpad.net/launchpad/+bug/29744

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/373727

This is essentially the same as https://code.launchpad.net/~cjwatson/launchpad/opt-in-zopeless-immediate-mail/+merge/270383, converted to git and rebased on master.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:opt-in-zopeless-immediate-mail into launchpad:master.
diff --git a/daemons/buildd-manager.tac b/daemons/buildd-manager.tac
index 06f99e5..c263ed4 100644
--- a/daemons/buildd-manager.tac
+++ b/daemons/buildd-manager.tac
@@ -9,22 +9,18 @@ import resource
 from twisted.application import service
 from twisted.scripts.twistd import ServerOptions
 
+from lp.buildmaster.manager import BuilddManager
 from lp.services.config import (
     config,
     dbconfig,
     )
 from lp.services.daemons import readyservice
 from lp.services.scripts import execute_zcml_for_scripts
-from lp.buildmaster.manager import BuilddManager
-from lp.services.mail.sendmail import set_immediate_mail_delivery
 from lp.services.twistedsupport.features import setup_feature_controller
 from lp.services.twistedsupport.loggingsupport import RotatableFileLogObserver
 
 execute_zcml_for_scripts()
 dbconfig.override(dbuser='buildd_manager', isolation_level='read_committed')
-# XXX wgrant 2011-09-24 bug=29744: initZopeless used to do this.
-# Should be removed from callsites verified to not need it.
-set_immediate_mail_delivery(True)
 
 # Allow generous slack for database connections, idle download connections,
 # etc.
diff --git a/lib/lp/bugs/scripts/bugnotification.py b/lib/lp/bugs/scripts/bugnotification.py
index fbcdbb6..c6ac115 100644
--- a/lib/lp/bugs/scripts/bugnotification.py
+++ b/lib/lp/bugs/scripts/bugnotification.py
@@ -35,7 +35,10 @@ from lp.registry.model.person import get_recipients
 from lp.services.database.constants import UTC_NOW
 from lp.services.mail.helpers import get_email_template
 from lp.services.mail.mailwrapper import MailWrapper
-from lp.services.mail.sendmail import sendmail
+from lp.services.mail.sendmail import (
+    immediate_mail_delivery,
+    sendmail,
+    )
 from lp.services.scripts.base import LaunchpadCronScript
 from lp.services.scripts.logger import log
 from lp.services.webapp import canonical_url
@@ -338,7 +341,7 @@ def process_deferred_notifications(bug_notifications):
 
 
 class SendBugNotifications(LaunchpadCronScript):
-    def main(self):
+    def send_notifications(self):
         notifications_sent = False
         bug_notification_set = getUtility(IBugNotificationSet)
         deferred_notifications = \
@@ -391,3 +394,9 @@ class SendBugNotifications(LaunchpadCronScript):
 
         if not notifications_sent:
             self.logger.debug("No notifications are pending to be sent.")
+
+    def main(self):
+        # XXX cjwatson 2015-09-05 bug=29744: SMTP exception handling here
+        # currently relies on this.
+        with immediate_mail_delivery(True):
+            self.send_notifications()
diff --git a/lib/lp/bugs/scripts/tests/test_bugnotification.py b/lib/lp/bugs/scripts/tests/test_bugnotification.py
index 41b7ba4..4200fc4 100644
--- a/lib/lp/bugs/scripts/tests/test_bugnotification.py
+++ b/lib/lp/bugs/scripts/tests/test_bugnotification.py
@@ -1368,7 +1368,7 @@ class TestSendBugNotifications(TestCaseWithFactory):
                 "send-bug-notifications", config.malone.bugnotification_dbuser,
                 ["-q"])
             script.txn = self.layer.txn
-            script.main()
+            script.send_notifications()
 
         self.assertEqual(1, len(self.oopses))
         self.assertIn(
diff --git a/lib/lp/bugs/tests/bugs-emailinterface.txt b/lib/lp/bugs/tests/bugs-emailinterface.txt
index ab8fd0d..22d80d3 100644
--- a/lib/lp/bugs/tests/bugs-emailinterface.txt
+++ b/lib/lp/bugs/tests/bugs-emailinterface.txt
@@ -81,8 +81,8 @@ bug got submitted correctly:
 
     >>> def process_email(raw_mail):
     ...     msg = construct_email(raw_mail)
-    ...     handler.process(msg, msg['To'],
-    ...         )
+    ...     handler.process(msg, msg['To'])
+    ...     transaction.commit()
 
     >>> process_email(submit_mail)
 
@@ -454,9 +454,7 @@ The same will happen if we send the same email without signing it:
     ...     signature = None
     >>> msg = email.message_from_string(
     ...     comment_mail, _class=MockUnsignedMessage)
-    >>> handler.process(
-    ...     msg, msg['To'],
-    ...     )
+    >>> handler.process(msg, msg['To'])
     True
     >>> transaction.commit()
 
@@ -514,9 +512,7 @@ to edit bug 4:
     ...     return construct_email(edit_mail)
 
     >>> def submit_command_email(msg):
-    ...     handler.process(
-    ...         msg, msg['To'],
-    ...         )
+    ...     handler.process(msg, msg['To'])
     ...     transaction.commit()
 
     >>> def submit_commands(bug, *commands):
@@ -1813,10 +1809,9 @@ signing the mail:
     >>> msg = signed_message_from_string(submit_mail)
     >>> import email.utils
     >>> msg['Message-Id'] = email.utils.make_msgid()
-    >>> handler.process(
-    ...     msg, msg['To'],
-    ...     )
+    >>> handler.process(msg, msg['To'])
     True
+    >>> transaction.commit()
     >>> print_latest_email()
     Subject: Submit Request Failure
     To: test@xxxxxxxxxxxxx
diff --git a/lib/lp/code/mail/codehandler.py b/lib/lp/code/mail/codehandler.py
index 80eceff..bd589b4 100644
--- a/lib/lp/code/mail/codehandler.py
+++ b/lib/lp/code/mail/codehandler.py
@@ -314,11 +314,11 @@ class CodeHandler:
                 message, context.vote, context.vote_tags, mail)
 
         except IncomingEmailError as error:
+            transaction.abort()
             send_process_error_notification(
                 str(user.preferredemail.email),
                 'Submit Request Failure',
                 error.message, mail, error.failing_command)
-            transaction.abort()
         return True
 
     @staticmethod
diff --git a/lib/lp/services/job/celeryjob.py b/lib/lp/services/job/celeryjob.py
index 95e5519..40dc0a8 100644
--- a/lib/lp/services/job/celeryjob.py
+++ b/lib/lp/services/job/celeryjob.py
@@ -40,7 +40,6 @@ from lp.services.features import (
     install_feature_controller,
     make_script_feature_controller,
     )
-from lp.services.mail.sendmail import set_immediate_mail_delivery
 from lp.services.job.model.job import (
     Job,
     UniversalJobSource,
@@ -157,7 +156,6 @@ def ensure_zcml():
         return
     transaction.abort()
     scripts.execute_zcml_for_scripts(use_web_security=False)
-    set_immediate_mail_delivery(True)
     needs_zcml = False
 
 
diff --git a/lib/lp/services/job/runner.py b/lib/lp/services/job/runner.py
index 635e075..7c0fe96 100644
--- a/lib/lp/services/job/runner.py
+++ b/lib/lp/services/job/runner.py
@@ -72,8 +72,8 @@ from lp.services.job.interfaces.job import (
     IRunnableJob,
     )
 from lp.services.mail.sendmail import (
+    immediate_mail_delivery,
     MailController,
-    set_immediate_mail_delivery,
     )
 from lp.services.timeout import (
     get_default_timeout_function,
@@ -187,7 +187,9 @@ class BaseRunnableJob(BaseRunnableJobSource):
         """Report this oops."""
         ctrl = self.getOopsMailController(oops['id'])
         if ctrl is not None:
-            ctrl.send()
+            # XXX cjwatson 2015-09-06 bug=29744: Can this be removed?
+            with immediate_mail_delivery(True):
+                ctrl.send()
 
     def getOopsVars(self):
         """See `IRunnableJob`."""
@@ -197,7 +199,9 @@ class BaseRunnableJob(BaseRunnableJobSource):
         """See `IRunnableJob`."""
         ctrl = self.getUserErrorMailController(e)
         if ctrl is not None:
-            ctrl.send()
+            # XXX cjwatson 2015-09-06 bug=29744: Can this be removed?
+            with immediate_mail_delivery(True):
+                ctrl.send()
 
     def makeOopsReport(self, oops_config, info):
         """Generate an OOPS report using the given OOPS configuration."""
@@ -434,9 +438,6 @@ class JobRunnerProcess(child.AMPChild):
         scripts.execute_zcml_for_scripts(use_web_security=False)
         signal(SIGHUP, handler)
         dbconfig.override(dbuser=cls.dbuser, isolation_level='read_committed')
-        # XXX wgrant 2011-09-24 bug=29744: initZopeless used to do this.
-        # Should be removed from callsites verified to not need it.
-        set_immediate_mail_delivery(True)
 
     @staticmethod
     def __exit__(exc_type, exc_val, exc_tb):
diff --git a/lib/lp/services/mail/basemailer.py b/lib/lp/services/mail/basemailer.py
index a2f8a40..36380f6 100644
--- a/lib/lp/services/mail/basemailer.py
+++ b/lib/lp/services/mail/basemailer.py
@@ -22,6 +22,7 @@ from lp.services.mail.notificationrecipientset import NotificationRecipientSet
 from lp.services.mail.sendmail import (
     append_footer,
     format_address,
+    immediate_mail_delivery,
     MailController,
     )
 from lp.services.utils import text_delta
@@ -231,8 +232,12 @@ class BaseMailer:
 
     def sendAll(self):
         """Send notifications to all recipients."""
-        for email, recipient in sorted(self._recipients.getRecipientPersons()):
-            self.sendOne(email, recipient)
+        # XXX cjwatson 2015-09-05 bug=29744: We currently depend on this to
+        # handle SMTPExceptions in the correct place.
+        with immediate_mail_delivery(True):
+            for email, recipient in sorted(
+                    self._recipients.getRecipientPersons()):
+                self.sendOne(email, recipient)
 
 
 class RecipientReason:
diff --git a/lib/lp/services/mail/sendmail.py b/lib/lp/services/mail/sendmail.py
index b344b12..ec17b93 100644
--- a/lib/lp/services/mail/sendmail.py
+++ b/lib/lp/services/mail/sendmail.py
@@ -19,6 +19,7 @@ __all__ = [
     'format_address',
     'format_address_for_person',
     'get_msgid',
+    'immediate_mail_delivery',
     'MailController',
     'sendmail',
     'set_immediate_mail_delivery',
@@ -29,6 +30,7 @@ __all__ = [
 
 
 from binascii import b2a_qp
+from contextlib import contextmanager
 from email import charset
 from email.encoders import encode_base64
 from email.header import Header
@@ -178,6 +180,16 @@ def set_immediate_mail_delivery(enabled):
     _immediate_mail_delivery = enabled
 
 
+@contextmanager
+def immediate_mail_delivery(enabled):
+    """Context manager to temporarily change immediate mail delivery."""
+    global _immediate_mail_delivery
+    previous = _immediate_mail_delivery
+    set_immediate_mail_delivery(enabled)
+    yield
+    set_immediate_mail_delivery(previous)
+
+
 def simple_sendmail(from_addr, to_addrs, subject, body, headers=None,
                     bulk=True):
     """Send an email from from_addr to to_addrs with the subject and body
diff --git a/lib/lp/services/mail/tests/incomingmail.txt b/lib/lp/services/mail/tests/incomingmail.txt
index 996e23d..dccaeb2 100644
--- a/lib/lp/services/mail/tests/incomingmail.txt
+++ b/lib/lp/services/mail/tests/incomingmail.txt
@@ -45,16 +45,20 @@ handle, and register them for some domains:
 
 Now we send a few test mails to foo.com, bar.com, and baz.com:
 
+    >>> import transaction
     >>> from lp.services.mail.tests.helpers import read_test_message
     >>> from lp.services.mail.sendmail import sendmail as original_sendmail
     >>> from lp.testing.dbuser import switch_dbuser
 
 For these examples, we don't want the Precedence header added. Domains
 are treated without regard to case: for incoming mail, foo.com and
-FOO.COM are treated equivalently.
+FOO.COM are treated equivalently. We also commit to ensure that mail is
+sent immediately.
 
     >>> def sendmail(msg, to_addrs=None):
-    ...     return original_sendmail(msg, to_addrs=to_addrs, bulk=False)
+    ...     msgid = original_sendmail(msg, to_addrs=to_addrs, bulk=False)
+    ...     transaction.commit()
+    ...     return msgid
 
     >>> switch_dbuser('launchpad')
     >>> msgids = {'foo.com': [], 'bar.com': [], 'baz.com': []}
diff --git a/lib/lp/services/mail/tests/test_incoming.py b/lib/lp/services/mail/tests/test_incoming.py
index 28bac00..32ce838 100644
--- a/lib/lp/services/mail/tests/test_incoming.py
+++ b/lib/lp/services/mail/tests/test_incoming.py
@@ -105,6 +105,7 @@ class IncomingTestCase(TestCaseWithFactory):
             email_address, 'to@xxxxxxxxxxx', 'subject', invalid_body,
             bulk=False)
         ctrl.send()
+        transaction.commit()
         handleMail()
         self.assertEqual([], self.oopses)
         [notification] = pop_notifications()
@@ -138,6 +139,7 @@ class IncomingTestCase(TestCaseWithFactory):
             email_address, 'to@xxxxxxxxxxx', 'subject', invalid_body,
             bulk=False)
         ctrl.send()
+        transaction.commit()
         handleMail()
         self.assertEqual([], self.oopses)
         [notification] = pop_notifications()
@@ -171,6 +173,7 @@ class IncomingTestCase(TestCaseWithFactory):
             email_address, 'to@xxxxxxxxxxx', 'subject', invalid_body,
             bulk=False)
         ctrl.send()
+        transaction.commit()
         handleMail()
         self.assertEqual([], self.oopses)
         [notification] = pop_notifications()
@@ -197,6 +200,7 @@ class IncomingTestCase(TestCaseWithFactory):
             email_address, 'to@xxxxxxxxxxx', 'subject', fat_body,
             bulk=False)
         ctrl.send()
+        transaction.commit()
         handleMail()
         self.assertEqual([], self.oopses)
         [notification] = pop_notifications()
diff --git a/lib/lp/services/scripts/base.py b/lib/lp/services/scripts/base.py
index 2a19eff..772d4de 100644
--- a/lib/lp/services/scripts/base.py
+++ b/lib/lp/services/scripts/base.py
@@ -44,7 +44,6 @@ from lp.services.features import (
     install_feature_controller,
     make_script_feature_controller,
     )
-from lp.services.mail.sendmail import set_immediate_mail_delivery
 from lp.services.scripts.interfaces.scriptactivity import IScriptActivitySet
 from lp.services.scripts.logger import OopsHandler
 from lp.services.webapp.errorlog import globalErrorUtility
@@ -307,10 +306,6 @@ class LaunchpadScript:
         self._init_zca(use_web_security=use_web_security)
         self._init_db(isolation=isolation)
 
-        # XXX wgrant 2011-09-24 bug=29744: initZopeless used to do this.
-        # Should be called directly by scripts that actually need it.
-        set_immediate_mail_delivery(True)
-
         date_started = datetime.datetime.now(UTC)
         profiler = None
         if self.options.profile:
diff --git a/lib/lp/testing/layers.py b/lib/lp/testing/layers.py
index ffac753..bed3545 100644
--- a/lib/lp/testing/layers.py
+++ b/lib/lp/testing/layers.py
@@ -1486,10 +1486,6 @@ class LaunchpadZopelessLayer(LaunchpadScriptLayer):
     @profiled
     def testSetUp(cls):
         dbconfig.override(isolation_level='read_committed')
-        # XXX wgrant 2011-09-24 bug=29744: initZopeless used to do this.
-        # Tests that still need it should eventually set this directly,
-        # so the whole layer is not polluted.
-        set_immediate_mail_delivery(True)
 
         # Connect Storm
         reconnect_stores()
diff --git a/scripts/mlist-import.py b/scripts/mlist-import.py
index 6aefe78..0d9b4f1 100755
--- a/scripts/mlist-import.py
+++ b/scripts/mlist-import.py
@@ -24,6 +24,7 @@ import textwrap
 
 from lp.registry.scripts.mlistimport import Importer
 from lp.services.config import config
+from lp.services.mail.sendmail import set_immediate_mail_delivery
 from lp.services.scripts.base import LaunchpadScript
 
 
@@ -56,6 +57,12 @@ class MailingListImport(LaunchpadScript):
 
     def main(self):
         """See `LaunchpadScript`."""
+        # XXX cjwatson 2015-09-06 bug=29744: We need immediate mail delivery
+        # for the current implementation of the --notifications switch, and
+        # because the tests expect notifications to end up in
+        # LayerProcessController.smtp_controller.
+        set_immediate_mail_delivery(True)
+
         team_name = None
         if len(self.args) == 0:
             self.parser.error('Missing team name')