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