Commit 1883504f authored by Colomban Wendling's avatar Colomban Wendling

Avoid spamming the SMTP server with many concurrent connections

Use a single SMTP connection and send all emails through it from a
thread to avoid spamming the SMTP server with connections.
parent 993cb1ff
...@@ -19,6 +19,8 @@ config.install_attr('emails.addr_from', ...@@ -19,6 +19,8 @@ config.install_attr('emails.addr_from',
'Picomon <picomon@%s>' % socket.getfqdn()) 'Picomon <picomon@%s>' % socket.getfqdn())
# The SMTP host, with optional :port suffix # The SMTP host, with optional :port suffix
config.install_attr('emails.smtp_host', 'localhost:25') config.install_attr('emails.smtp_host', 'localhost:25')
# The inactive timeout after which to close the SMTP connection
config.install_attr('emails.smtp_keepalive_timeout', 60)
# Subject template for state change email notifications # Subject template for state change email notifications
# available substitutions: # available substitutions:
......
...@@ -3,12 +3,72 @@ from email.mime.text import MIMEText ...@@ -3,12 +3,72 @@ from email.mime.text import MIMEText
from collections import defaultdict from collections import defaultdict
from sys import stderr from sys import stderr
import email.charset import email.charset
from threading import Thread
import queue
# Switch to quoted-printable so that we don't get something completely # Switch to quoted-printable so that we don't get something completely
# unreadable for non-ASCII chars if we have to look at raw email # unreadable for non-ASCII chars if we have to look at raw email
email.charset.add_charset('utf-8', email.charset.QP, email.charset.QP, 'utf-8') email.charset.add_charset('utf-8', email.charset.QP, email.charset.QP, 'utf-8')
class ThreadedSMTP(object):
"""A helper class managing a thread sending emails through smtplib"""
def __init__(self):
self._queue = queue.Queue()
self._loop = True
self._thread = Thread(target=self.__loop)
self._thread.deamon = True
self._thread.start()
def quit(self):
if self._loop:
self._loop = False
self._queue.put(((), {})) # put a dummy item to wake up the thread
self._queue.join()
self._thread.join()
def __server_quit(self, server=None):
if server is not None:
server.quit()
return None
def __loop(self):
from . import config
host = config.emails.smtp_host
timeout = config.emails.smtp_keepalive_timeout
server = None
while self._loop or not self._queue.empty():
try:
args, kwargs = self._queue.get(timeout=timeout)
except queue.Empty:
server = self.__server_quit(server)
except KeyboardInterrupt as e:
break
else:
if len(args) or len(kwargs): # ignore empty items
try:
if server is None:
server = smtplib.SMTP(host)
server.sendmail(*args, **kwargs)
except Exception as e:
print("Couldn't send email: %s" % str(e), file=stderr)
finally:
self._queue.task_done()
self.__server_quit(server)
def sendmail(self, *args, **kwargs):
self._queue.put((args, kwargs))
_mailer = ThreadedSMTP()
def quit():
_mailer.quit()
def send_email_for_check(check): def send_email_for_check(check):
from . import config from . import config
# ensure we do not traceback with unknown substitutions # ensure we do not traceback with unknown substitutions
...@@ -30,11 +90,5 @@ def send_email_for_check(check): ...@@ -30,11 +90,5 @@ def send_email_for_check(check):
msg['From'] = config.emails.addr_from msg['From'] = config.emails.addr_from
msg['To'] = ", ".join(config.emails.to) msg['To'] = ", ".join(config.emails.to)
try: _mailer.sendmail(config.emails.addr_from, config.emails.to,
server = smtplib.SMTP(config.emails.smtp_host) msg.as_string())
# server.set_debuglevel(1)
server.sendmail(config.emails.addr_from, config.emails.to,
msg.as_string())
server.quit()
except Exception as e:
print("Couldn't send email: %s" % str(e), file=stderr)
...@@ -4,6 +4,7 @@ import argparse ...@@ -4,6 +4,7 @@ import argparse
from time import sleep from time import sleep
import config as user_config import config as user_config
from lib import config from lib import config
from lib import mails
def usr1_handler(signum, frame): def usr1_handler(signum, frame):
...@@ -59,3 +60,4 @@ if __name__ == '__main__': ...@@ -59,3 +60,4 @@ if __name__ == '__main__':
for check in config.checks: for check in config.checks:
executor.submit(check.run()) executor.submit(check.run())
sleep(config.base_tick) sleep(config.base_tick)
mails.quit()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment