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
This commit is contained in:
Peter Šurda 2016-06-30 12:30:05 +02:00
parent e5c9e6d383
commit 4865659d72
Signed by: PeterSurda
GPG Key ID: 0C5F50C0B5F37D87
3 changed files with 51 additions and 19 deletions

View File

@ -38,6 +38,7 @@ from class_outgoingSynSender import outgoingSynSender
from class_singleListener import singleListener from class_singleListener import singleListener
from class_singleWorker import singleWorker from class_singleWorker import singleWorker
from class_addressGenerator import addressGenerator from class_addressGenerator import addressGenerator
from class_smtpDeliver import smtpDeliver
from debug import logger from debug import logger
# Helper Functions # Helper Functions
@ -153,16 +154,21 @@ class Main:
_fixWinsock() _fixWinsock()
shared.daemon = daemon 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. # is the application already running? If yes then exit.
shared.thisapp = singleton.singleinstance("", daemon) shared.thisapp = singleton.singleinstance("", daemon)
# get curses flag if daemon:
curses = False with shared.printLock:
if '-c' in sys.argv: print('Running as a daemon. Send TERM signal to end.')
curses = True self.daemonize()
signal.signal(signal.SIGINT, helper_generic.signal_handler) self.setSignalHandler()
# signal.signal(signal.SIGINT, signal.SIG_DFL)
helper_bootstrap.knownNodes() helper_bootstrap.knownNodes()
# Start the address generation thread # 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.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() sqlLookup.start()
# SMTP delivery thread
if daemon and shared.config.get("bitmessagesettings", "smtpdeliver", None):
smtpDeliveryThread = smtpDeliver()
smtpDeliveryThread.start()
# Start the thread that calculates POWs # Start the thread that calculates POWs
objectProcessorThread = objectProcessor() 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. 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() upnpThread.start()
if daemon == False and shared.safeConfigGetBoolean('bitmessagesettings', 'daemon') == False: if daemon == False and shared.safeConfigGetBoolean('bitmessagesettings', 'daemon') == False:
if curses == False: if shared.curses == False:
if not depends.check_pyqt(): 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('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.') 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 import bitmessageqt
bitmessageqt.run() bitmessageqt.run()
else: else:
if depends.check_curses(): if True:
# if depends.check_curses():
print('Running with curses') print('Running with curses')
import bitmessagecurses import bitmessagecurses
bitmessagecurses.runwrapper() bitmessagecurses.runwrapper()
else: else:
shared.config.remove_option('bitmessagesettings', 'dontconnect') shared.config.remove_option('bitmessagesettings', 'dontconnect')
if daemon: while True:
with shared.printLock: time.sleep(20)
print('Running as a daemon. The main program should exit this thread.')
else: def daemonize(self):
with shared.printLock: if os.fork():
print('Running as a daemon. You can use Ctrl+C to exit.') exit(0)
while True: os.umask(0)
time.sleep(20) 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): def stop(self):
with shared.printLock: with shared.printLock:

View File

@ -2,6 +2,7 @@ import socket
import sys import sys
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from debug import logger
import shared import shared
def convertIntToString(n): def convertIntToString(n):
@ -17,11 +18,10 @@ def convertIntToString(n):
def convertStringToInt(s): def convertStringToInt(s):
return int(hexlify(s), 16) return int(hexlify(s), 16)
def signal_handler(signal, frame): def signal_handler(signal, frame):
logger.error("Got signal %i", signal)
if shared.safeConfigGetBoolean('bitmessagesettings', 'daemon'): if shared.safeConfigGetBoolean('bitmessagesettings', 'daemon'):
shared.doCleanShutdown() shared.doCleanShutdown()
sys.exit(0)
else: else:
print 'Unfortunately you cannot use Ctrl+C when running the UI because the UI captures the signal.' print 'Unfortunately you cannot use Ctrl+C when running the UI because the UI captures the signal.'

View File

@ -21,10 +21,11 @@ class singleinstance:
""" """
def __init__(self, flavor_id="", daemon=False): def __init__(self, flavor_id="", daemon=False):
self.initialized = False self.initialized = False
self.counter = 0
self.daemon = daemon self.daemon = daemon
self.lockfile = os.path.normpath(os.path.join(shared.appdata, 'singleton%s.lock' % flavor_id)) 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. # Tells the already running (if any) application to get focus.
import bitmessageqt import bitmessageqt
bitmessageqt.init() bitmessageqt.init()
@ -55,6 +56,10 @@ class singleinstance:
def cleanup(self): def cleanup(self):
if not self.initialized: if not self.initialized:
return return
self.counter += 1
if self.daemon and self.counter < 3:
# these are the two initial forks while daemonizing
return
print "Cleaning up lockfile" print "Cleaning up lockfile"
try: try:
if sys.platform == 'win32': if sys.platform == 'win32':