On Sun, Oct 20, 2019 at 2:16 AM Yasuhito FUTATSUKI <futat...@poem.co.jp>
wrote:

> On 2019/10/20 13:46, Nathan Hartman wrote:
> >  From the "Closing Old Issues" department:
> >
> > SVN-1804: "mailer.py doesn't catch SMTPRecipientsRefused in finish()"
> > * Created and last updated in 2004.
> > * No comments in more than 15 years.
> >
> >  From my reading of this issue, the impact is that any mail delivery
> > hiccup may cause this hook script to exit with an unhandled exception,
> > impeding any further mails that it might have sent.
>

(snip)


> > Option 1: Handle (and ignore) exceptions in SMTPOutput.finish() to let
> > script execution continue after any SMTP failure. This could be as
> > simple as wrapping the contents of SMTPOutput.finish() in a "try:" ..
> > "except:" (catch-all), where the except block does nothing.
>
> With this approach, I think the script should exit with code
> other than 0 if an exception raised, to notify error occured to hook
> scripts. Python script exits with code 1 if the script is termitated
> by unhandled exceptions, so it can notify on current implementation.
> Hook scripts can also watch stderr to log what happens on mailer.py,
> etc., though our post-*.tmpl don't do it.


I think the attached patch accomplishes that. (I'm not putting it inline
because it's 200 lines long! Most of them due to indent changes.)

I'd appreciate a review!

The changes are pretty basic:
* A new exception class is added: MessageSendFailure.
* The contents of SMTPOutput.finish() are wrapped in try..except. We
  catch any smtplib.SMTPException, print an error to stderr, and raise
  MessageSendFailure.
* In all classes' generate(), we wrap the body of the for-loop in a
  try..except. We catch MessageSendFailure and increment a counter;
  generate() returns the value of the counter.
* In main, the return value of messenger.generate() is returned to
  main's caller.
* In the __main__ program, the call to svn.core.run_app() is wrapped
  with sys.exit().
Index: tools/hook-scripts/mailer/mailer.py
===================================================================
--- tools/hook-scripts/mailer/mailer.py (revision 1868807)
+++ tools/hook-scripts/mailer/mailer.py (working copy)
@@ -123,7 +123,7 @@
   else:
     raise UnknownSubcommand(cmd)
 
-  messenger.generate()
+  return messenger.generate()
 
 
 def remove_leading_slashes(path):
@@ -285,15 +285,19 @@
     self.write(self.mail_headers(group, params))
 
   def finish(self):
-    if self.cfg.is_set('general.smtp_ssl') and self.cfg.general.smtp_ssl == 
'yes':
-      server = smtplib.SMTP_SSL(self.cfg.general.smtp_hostname)
-    else:
-      server = smtplib.SMTP(self.cfg.general.smtp_hostname)
-    if self.cfg.is_set('general.smtp_username'):
-      server.login(self.cfg.general.smtp_username,
-                   self.cfg.general.smtp_password)
-    server.sendmail(self.from_addr, self.to_addrs, self.buffer.getvalue())
-    server.quit()
+    try:
+      if self.cfg.is_set('general.smtp_ssl') and self.cfg.general.smtp_ssl == 
'yes':
+        server = smtplib.SMTP_SSL(self.cfg.general.smtp_hostname)
+      else:
+        server = smtplib.SMTP(self.cfg.general.smtp_hostname)
+      if self.cfg.is_set('general.smtp_username'):
+        server.login(self.cfg.general.smtp_username,
+                     self.cfg.general.smtp_password)
+      server.sendmail(self.from_addr, self.to_addrs, self.buffer.getvalue())
+      server.quit()
+    except smtplib.SMTPException as detail:
+      sys.stderr.write("SMTP error occurred: %s" % detail);
+      raise MessageSendFailure;
 
 
 class StandardOutput(OutputBase):
@@ -421,21 +425,26 @@
     ### rather than rebuilding it each time.
 
     subpool = svn.core.svn_pool_create(self.pool)
+    ret = 0
 
     # build a renderer, tied to our output stream
     renderer = TextCommitRenderer(self.output)
 
     for (group, param_tuple), (params, paths) in self.groups.items():
-      self.output.start(group, params)
+      try:
+        self.output.start(group, params)
 
-      # generate the content for this group and set of params
-      generate_content(renderer, self.cfg, self.repos, self.changelist,
-                       group, params, paths, subpool)
+        # generate the content for this group and set of params
+        generate_content(renderer, self.cfg, self.repos, self.changelist,
+                         group, params, paths, subpool)
 
-      self.output.finish()
+        self.output.finish()
+      except MessageSendFailure:
+        ret += 1
       svn.core.svn_pool_clear(subpool)
 
     svn.core.svn_pool_destroy(subpool)
+    return ret
 
 
 class PropChange(Messenger):
@@ -456,35 +465,40 @@
 
   def generate(self):
     actions = { 'A': 'added', 'M': 'modified', 'D': 'deleted' }
+    ret = 0
     for (group, param_tuple), params in self.groups.items():
-      self.output.start(group, params)
-      self.output.write('Author: %s\n'
-                        'Revision: %s\n'
-                        'Property Name: %s\n'
-                        'Action: %s\n'
-                        '\n'
-                        % (self.author, self.repos.rev, self.propname,
-                           actions.get(self.action, 'Unknown (\'%s\')' \
-                                       % self.action)))
-      if self.action == 'A' or self.action not in actions:
-        self.output.write('Property value:\n')
-        propvalue = self.repos.get_rev_prop(self.propname)
-        self.output.write(propvalue)
-      elif self.action == 'M':
-        self.output.write('Property diff:\n')
-        tempfile1 = tempfile.NamedTemporaryFile()
-        tempfile1.write(sys.stdin.read())
-        tempfile1.flush()
-        tempfile2 = tempfile.NamedTemporaryFile()
-        tempfile2.write(self.repos.get_rev_prop(self.propname))
-        tempfile2.flush()
-        self.output.run(self.cfg.get_diff_cmd(group, {
-          'label_from' : 'old property value',
-          'label_to' : 'new property value',
-          'from' : tempfile1.name,
-          'to' : tempfile2.name,
-          }))
-      self.output.finish()
+      try:
+        self.output.start(group, params)
+        self.output.write('Author: %s\n'
+                          'Revision: %s\n'
+                          'Property Name: %s\n'
+                          'Action: %s\n'
+                          '\n'
+                          % (self.author, self.repos.rev, self.propname,
+                             actions.get(self.action, 'Unknown (\'%s\')' \
+                                         % self.action)))
+        if self.action == 'A' or self.action not in actions:
+          self.output.write('Property value:\n')
+          propvalue = self.repos.get_rev_prop(self.propname)
+          self.output.write(propvalue)
+        elif self.action == 'M':
+          self.output.write('Property diff:\n')
+          tempfile1 = tempfile.NamedTemporaryFile()
+          tempfile1.write(sys.stdin.read())
+          tempfile1.flush()
+          tempfile2 = tempfile.NamedTemporaryFile()
+          tempfile2.write(self.repos.get_rev_prop(self.propname))
+          tempfile2.flush()
+          self.output.run(self.cfg.get_diff_cmd(group, {
+            'label_from' : 'old property value',
+            'label_to' : 'new property value',
+            'from' : tempfile1.name,
+            'to' : tempfile2.name,
+            }))
+        self.output.finish()
+      except MessageSendFailure:
+        ret += 1
+    return ret
 
 
 def get_commondir(dirlist):
@@ -564,21 +578,26 @@
                                        self.dirlist[0], self.pool)
 
   def generate(self):
+    ret = 0
     for (group, param_tuple), (params, paths) in self.groups.items():
-      self.output.start(group, params)
+      try:
+        self.output.start(group, params)
 
-      self.output.write('Author: %s\n'
-                        '%s paths:\n' %
-                        (self.author, self.do_lock and 'Locked' or 'Unlocked'))
+        self.output.write('Author: %s\n'
+                          '%s paths:\n' %
+                          (self.author, self.do_lock and 'Locked' or 
'Unlocked'))
 
-      self.dirlist.sort()
-      for dir in self.dirlist:
-        self.output.write('   %s\n\n' % dir)
+        self.dirlist.sort()
+        for dir in self.dirlist:
+          self.output.write('   %s\n\n' % dir)
 
-      if self.do_lock:
-        self.output.write('Comment:\n%s\n' % (self.lock.comment or ''))
+        if self.do_lock:
+          self.output.write('Comment:\n%s\n' % (self.lock.comment or ''))
 
-      self.output.finish()
+        self.output.finish()
+      except MessageSendFailure:
+        ret += 1
+    return ret
 
 
 class DiffSelections:
@@ -1394,6 +1413,8 @@
   pass
 class UnknownSubcommand(Exception):
   pass
+class MessageSendFailure(Exception):
+  pass
 
 
 if __name__ == '__main__':
@@ -1455,8 +1476,8 @@
   if not os.path.exists(config_fname):
     raise MissingConfig(config_fname)
 
-  svn.core.run_app(main, cmd, config_fname, repos_dir,
-                   sys.argv[3:3+expected_args])
+  sys.exit(svn.core.run_app(main, cmd, config_fname, repos_dir,
+                   sys.argv[3:3+expected_args]))
 
 # ------------------------------------------------------------------------
 # TODO

Reply via email to