From 4865659d729bf2e116b2e0f83c85d08f3c457f68 Mon Sep 17 00:00:00 2001 From: Peter Surda Date: Thu, 30 Jun 2016 12:30:05 +0200 Subject: [PATCH] Main process fixes - handles old dialog versions better if using curses - can spawn SMTP delivery thread if configured (only when in daemon mode) - daemonized mode now works more like it's properly supposed to on unix (double fork etc). You may have to adjust your init scripts, when when using upstart for example you should now use "expect daemon" - daemon mode now cleanly shuts down when TERM/INT signal is received --- src/bitmessagemain.py | 59 +++++++++++++++++++++++++++++++------------ src/helper_generic.py | 4 +-- src/singleton.py | 7 ++++- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 02f1396a..aae46f2c 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -38,6 +38,7 @@ from class_outgoingSynSender import outgoingSynSender from class_singleListener import singleListener from class_singleWorker import singleWorker from class_addressGenerator import addressGenerator +from class_smtpDeliver import smtpDeliver from debug import logger # Helper Functions @@ -153,16 +154,21 @@ class Main: _fixWinsock() shared.daemon = daemon + + # get curses flag + shared.curses = False + if '-c' in sys.argv: + shared.curses = True + # is the application already running? If yes then exit. shared.thisapp = singleton.singleinstance("", daemon) - # get curses flag - curses = False - if '-c' in sys.argv: - curses = True + if daemon: + with shared.printLock: + print('Running as a daemon. Send TERM signal to end.') + self.daemonize() - signal.signal(signal.SIGINT, helper_generic.signal_handler) - # signal.signal(signal.SIGINT, signal.SIG_DFL) + self.setSignalHandler() helper_bootstrap.knownNodes() # Start the address generation thread @@ -180,6 +186,11 @@ class Main: sqlLookup.daemon = False # DON'T close the main program even if there are threads left. The closeEvent should command this thread to exit gracefully. sqlLookup.start() + # SMTP delivery thread + if daemon and shared.config.get("bitmessagesettings", "smtpdeliver", None): + smtpDeliveryThread = smtpDeliver() + smtpDeliveryThread.start() + # Start the thread that calculates POWs objectProcessorThread = objectProcessor() objectProcessorThread.daemon = False # DON'T close the main program even the thread remains. This thread checks the shutdown variable after processing each object. @@ -221,7 +232,7 @@ class Main: upnpThread.start() if daemon == False and shared.safeConfigGetBoolean('bitmessagesettings', 'daemon') == False: - if curses == False: + if shared.curses == False: if not depends.check_pyqt(): print('PyBitmessage requires PyQt unless you want to run it as a daemon and interact with it using the API. You can download PyQt from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\'. If you want to run in daemon mode, see https://bitmessage.org/wiki/Daemon') print('You can also run PyBitmessage with the new curses interface by providing \'-c\' as a commandline argument.') @@ -230,21 +241,37 @@ class Main: import bitmessageqt bitmessageqt.run() else: - if depends.check_curses(): + if True: +# if depends.check_curses(): print('Running with curses') import bitmessagecurses bitmessagecurses.runwrapper() else: shared.config.remove_option('bitmessagesettings', 'dontconnect') - if daemon: - with shared.printLock: - print('Running as a daemon. The main program should exit this thread.') - else: - with shared.printLock: - print('Running as a daemon. You can use Ctrl+C to exit.') - while True: - time.sleep(20) + while True: + time.sleep(20) + + def daemonize(self): + if os.fork(): + exit(0) + os.umask(0) + os.setsid() + if os.fork(): + exit(0) + sys.stdout.flush() + sys.stderr.flush() + si = file('/dev/null', 'r') + so = file('/dev/null', 'a+') + se = file('/dev/null', 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + def setSignalHandler(self): + signal.signal(signal.SIGINT, helper_generic.signal_handler) + signal.signal(signal.SIGTERM, helper_generic.signal_handler) + # signal.signal(signal.SIGINT, signal.SIG_DFL) def stop(self): with shared.printLock: diff --git a/src/helper_generic.py b/src/helper_generic.py index b8a38863..b6516c05 100644 --- a/src/helper_generic.py +++ b/src/helper_generic.py @@ -2,6 +2,7 @@ import socket import sys from binascii import hexlify, unhexlify +from debug import logger import shared def convertIntToString(n): @@ -17,11 +18,10 @@ def convertIntToString(n): def convertStringToInt(s): return int(hexlify(s), 16) - def signal_handler(signal, frame): + logger.error("Got signal %i", signal) if shared.safeConfigGetBoolean('bitmessagesettings', 'daemon'): shared.doCleanShutdown() - sys.exit(0) else: print 'Unfortunately you cannot use Ctrl+C when running the UI because the UI captures the signal.' diff --git a/src/singleton.py b/src/singleton.py index a4b6c678..a3a77fd7 100644 --- a/src/singleton.py +++ b/src/singleton.py @@ -21,10 +21,11 @@ class singleinstance: """ def __init__(self, flavor_id="", daemon=False): self.initialized = False + self.counter = 0 self.daemon = daemon self.lockfile = os.path.normpath(os.path.join(shared.appdata, 'singleton%s.lock' % flavor_id)) - if not self.daemon: + if not self.daemon and not shared.curses: # Tells the already running (if any) application to get focus. import bitmessageqt bitmessageqt.init() @@ -55,6 +56,10 @@ class singleinstance: def cleanup(self): if not self.initialized: return + self.counter += 1 + if self.daemon and self.counter < 3: + # these are the two initial forks while daemonizing + return print "Cleaning up lockfile" try: if sys.platform == 'win32':