mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Fix regression in 0.8.33 that caused calibre to crash when starting the Content Server, if the port the content server is trying to listen on is blocked/busy. Fixes #910512 (calibre crashs after start without message)
This commit is contained in:
parent
87c2406cd8
commit
b117148241
@ -63,16 +63,21 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
|
|
||||||
def start_server(self):
|
def start_server(self):
|
||||||
ConfigWidgetBase.commit(self)
|
ConfigWidgetBase.commit(self)
|
||||||
self.gui.start_content_server(check_started=False)
|
self.setCursor(Qt.BusyCursor)
|
||||||
while not self.gui.content_server.is_running and self.gui.content_server.exception is None:
|
try:
|
||||||
time.sleep(1)
|
self.gui.start_content_server(check_started=False)
|
||||||
if self.gui.content_server.exception is not None:
|
while (not self.gui.content_server.is_running and
|
||||||
error_dialog(self, _('Failed to start content server'),
|
self.gui.content_server.exception is None):
|
||||||
as_unicode(self.gui.content_server.exception)).exec_()
|
time.sleep(0.1)
|
||||||
return
|
if self.gui.content_server.exception is not None:
|
||||||
self.start_button.setEnabled(False)
|
error_dialog(self, _('Failed to start content server'),
|
||||||
self.test_button.setEnabled(True)
|
as_unicode(self.gui.content_server.exception)).exec_()
|
||||||
self.stop_button.setEnabled(True)
|
return
|
||||||
|
self.start_button.setEnabled(False)
|
||||||
|
self.test_button.setEnabled(True)
|
||||||
|
self.stop_button.setEnabled(True)
|
||||||
|
finally:
|
||||||
|
self.unsetCursor()
|
||||||
|
|
||||||
def stop_server(self):
|
def stop_server(self):
|
||||||
self.gui.content_server.threaded_exit()
|
self.gui.content_server.threaded_exit()
|
||||||
|
@ -368,9 +368,14 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
self.library_view.model().db, server_config().parse())
|
self.library_view.model().db, server_config().parse())
|
||||||
self.content_server.state_callback = Dispatcher(
|
self.content_server.state_callback = Dispatcher(
|
||||||
self.iactions['Connect Share'].content_server_state_changed)
|
self.iactions['Connect Share'].content_server_state_changed)
|
||||||
self.content_server.state_callback(True)
|
|
||||||
if check_started:
|
if check_started:
|
||||||
QTimer.singleShot(10000, self.test_server)
|
self.content_server.start_failure_callback = \
|
||||||
|
Dispatcher(self.content_server_start_failed)
|
||||||
|
|
||||||
|
def content_server_start_failed(self, msg):
|
||||||
|
error_dialog(self, _('Failed to start Content Server'),
|
||||||
|
_('Could not start the content server. Error:\n\n%s')%msg,
|
||||||
|
show=True)
|
||||||
|
|
||||||
def resizeEvent(self, ev):
|
def resizeEvent(self, ev):
|
||||||
MainWindow.resizeEvent(self, ev)
|
MainWindow.resizeEvent(self, ev)
|
||||||
|
@ -26,7 +26,7 @@ from calibre.library.server.cache import Cache
|
|||||||
from calibre.library.server.browse import BrowseServer
|
from calibre.library.server.browse import BrowseServer
|
||||||
from calibre.library.server.ajax import AjaxServer
|
from calibre.library.server.ajax import AjaxServer
|
||||||
from calibre.utils.search_query_parser import saved_searches
|
from calibre.utils.search_query_parser import saved_searches
|
||||||
from calibre import prints
|
from calibre import prints, as_unicode
|
||||||
|
|
||||||
|
|
||||||
class DispatchController(object): # {{{
|
class DispatchController(object): # {{{
|
||||||
@ -112,6 +112,7 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
self.opts = opts
|
self.opts = opts
|
||||||
self.embedded = embedded
|
self.embedded = embedded
|
||||||
self.state_callback = None
|
self.state_callback = None
|
||||||
|
self.start_failure_callback = None
|
||||||
try:
|
try:
|
||||||
self.max_cover_width, self.max_cover_height = \
|
self.max_cover_width, self.max_cover_height = \
|
||||||
map(int, self.opts.max_cover.split('x'))
|
map(int, self.opts.max_cover.split('x'))
|
||||||
@ -225,41 +226,57 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
h.setFormatter(cherrypy._cplogging.logfmt)
|
h.setFormatter(cherrypy._cplogging.logfmt)
|
||||||
log.access_log.addHandler(h)
|
log.access_log.addHandler(h)
|
||||||
|
|
||||||
|
def start_cherrypy(self):
|
||||||
|
try:
|
||||||
|
cherrypy.engine.start()
|
||||||
|
except:
|
||||||
|
ip = get_external_ip()
|
||||||
|
if not ip or ip.startswith('127.'):
|
||||||
|
raise
|
||||||
|
cherrypy.log('Trying to bind to single interface: '+ip)
|
||||||
|
# Change the host we listen on
|
||||||
|
cherrypy.config.update({'server.socket_host' : ip})
|
||||||
|
# This ensures that the change is actually applied
|
||||||
|
cherrypy.server.socket_host = ip
|
||||||
|
cherrypy.server.httpserver = cherrypy.server.instance = None
|
||||||
|
|
||||||
|
cherrypy.engine.start()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
self.exception = None
|
||||||
cherrypy.tree.mount(root=None, config=self.config)
|
cherrypy.tree.mount(root=None, config=self.config)
|
||||||
try:
|
try:
|
||||||
try:
|
self.start_cherrypy()
|
||||||
cherrypy.engine.start()
|
|
||||||
except:
|
|
||||||
ip = get_external_ip()
|
|
||||||
if not ip or ip.startswith('127.'):
|
|
||||||
raise
|
|
||||||
cherrypy.log('Trying to bind to single interface: '+ip)
|
|
||||||
# Change the host we listen on
|
|
||||||
cherrypy.config.update({'server.socket_host' : ip})
|
|
||||||
# This ensures that the change is actually applied
|
|
||||||
cherrypy.server.socket_host = ip
|
|
||||||
cherrypy.server.httpserver = cherrypy.server.instance = None
|
|
||||||
|
|
||||||
cherrypy.engine.start()
|
|
||||||
|
|
||||||
self.is_running = True
|
|
||||||
#if hasattr(cherrypy.engine, 'signal_handler'):
|
|
||||||
# cherrypy.engine.signal_handler.subscribe()
|
|
||||||
|
|
||||||
cherrypy.engine.block()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.exception = e
|
self.exception = e
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
if callable(self.start_failure_callback):
|
||||||
|
try:
|
||||||
|
self.start_failure_callback(as_unicode(e))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.is_running = True
|
||||||
|
self.notify_listener()
|
||||||
|
cherrypy.engine.block()
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
self.exception = e
|
||||||
finally:
|
finally:
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
try:
|
self.notify_listener()
|
||||||
if callable(self.state_callback):
|
|
||||||
self.state_callback(self.is_running)
|
def notify_listener(self):
|
||||||
except:
|
try:
|
||||||
pass
|
if callable(self.state_callback):
|
||||||
|
self.state_callback(self.is_running)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
try:
|
try:
|
||||||
@ -267,11 +284,7 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
finally:
|
finally:
|
||||||
cherrypy.server.httpserver = None
|
cherrypy.server.httpserver = None
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
try:
|
self.notify_listener()
|
||||||
if callable(self.state_callback):
|
|
||||||
self.state_callback(self.is_running)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def threaded_exit(self):
|
def threaded_exit(self):
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
@ -81,21 +81,21 @@ _startup_cwd = os.getcwd()
|
|||||||
class ChannelFailures(Exception):
|
class ChannelFailures(Exception):
|
||||||
"""Exception raised when errors occur in a listener during Bus.publish()."""
|
"""Exception raised when errors occur in a listener during Bus.publish()."""
|
||||||
delimiter = '\n'
|
delimiter = '\n'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Don't use 'super' here; Exceptions are old-style in Py2.4
|
# Don't use 'super' here; Exceptions are old-style in Py2.4
|
||||||
# See http://www.cherrypy.org/ticket/959
|
# See http://www.cherrypy.org/ticket/959
|
||||||
Exception.__init__(self, *args, **kwargs)
|
Exception.__init__(self, *args, **kwargs)
|
||||||
self._exceptions = list()
|
self._exceptions = list()
|
||||||
|
|
||||||
def handle_exception(self):
|
def handle_exception(self):
|
||||||
"""Append the current exception to self."""
|
"""Append the current exception to self."""
|
||||||
self._exceptions.append(sys.exc_info()[1])
|
self._exceptions.append(sys.exc_info()[1])
|
||||||
|
|
||||||
def get_instances(self):
|
def get_instances(self):
|
||||||
"""Return a list of seen exception instances."""
|
"""Return a list of seen exception instances."""
|
||||||
return self._exceptions[:]
|
return self._exceptions[:]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
exception_strings = map(repr, self.get_instances())
|
exception_strings = map(repr, self.get_instances())
|
||||||
return self.delimiter.join(exception_strings)
|
return self.delimiter.join(exception_strings)
|
||||||
@ -112,7 +112,7 @@ class _StateEnum(object):
|
|||||||
name = None
|
name = None
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "states.%s" % self.name
|
return "states.%s" % self.name
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
def __setattr__(self, key, value):
|
||||||
if isinstance(value, self.State):
|
if isinstance(value, self.State):
|
||||||
value.name = key
|
value.name = key
|
||||||
@ -138,19 +138,19 @@ else:
|
|||||||
|
|
||||||
class Bus(object):
|
class Bus(object):
|
||||||
"""Process state-machine and messenger for HTTP site deployment.
|
"""Process state-machine and messenger for HTTP site deployment.
|
||||||
|
|
||||||
All listeners for a given channel are guaranteed to be called even
|
All listeners for a given channel are guaranteed to be called even
|
||||||
if others at the same channel fail. Each failure is logged, but
|
if others at the same channel fail. Each failure is logged, but
|
||||||
execution proceeds on to the next listener. The only way to stop all
|
execution proceeds on to the next listener. The only way to stop all
|
||||||
processing from inside a listener is to raise SystemExit and stop the
|
processing from inside a listener is to raise SystemExit and stop the
|
||||||
whole server.
|
whole server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
states = states
|
states = states
|
||||||
state = states.STOPPED
|
state = states.STOPPED
|
||||||
execv = False
|
execv = False
|
||||||
max_cloexec_files = max_files
|
max_cloexec_files = max_files
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.execv = False
|
self.execv = False
|
||||||
self.state = states.STOPPED
|
self.state = states.STOPPED
|
||||||
@ -158,32 +158,32 @@ class Bus(object):
|
|||||||
[(channel, set()) for channel
|
[(channel, set()) for channel
|
||||||
in ('start', 'stop', 'exit', 'graceful', 'log', 'main')])
|
in ('start', 'stop', 'exit', 'graceful', 'log', 'main')])
|
||||||
self._priorities = {}
|
self._priorities = {}
|
||||||
|
|
||||||
def subscribe(self, channel, callback, priority=None):
|
def subscribe(self, channel, callback, priority=None):
|
||||||
"""Add the given callback at the given channel (if not present)."""
|
"""Add the given callback at the given channel (if not present)."""
|
||||||
if channel not in self.listeners:
|
if channel not in self.listeners:
|
||||||
self.listeners[channel] = set()
|
self.listeners[channel] = set()
|
||||||
self.listeners[channel].add(callback)
|
self.listeners[channel].add(callback)
|
||||||
|
|
||||||
if priority is None:
|
if priority is None:
|
||||||
priority = getattr(callback, 'priority', 50)
|
priority = getattr(callback, 'priority', 50)
|
||||||
self._priorities[(channel, callback)] = priority
|
self._priorities[(channel, callback)] = priority
|
||||||
|
|
||||||
def unsubscribe(self, channel, callback):
|
def unsubscribe(self, channel, callback):
|
||||||
"""Discard the given callback (if present)."""
|
"""Discard the given callback (if present)."""
|
||||||
listeners = self.listeners.get(channel)
|
listeners = self.listeners.get(channel)
|
||||||
if listeners and callback in listeners:
|
if listeners and callback in listeners:
|
||||||
listeners.discard(callback)
|
listeners.discard(callback)
|
||||||
del self._priorities[(channel, callback)]
|
del self._priorities[(channel, callback)]
|
||||||
|
|
||||||
def publish(self, channel, *args, **kwargs):
|
def publish(self, channel, *args, **kwargs):
|
||||||
"""Return output of all subscribers for the given channel."""
|
"""Return output of all subscribers for the given channel."""
|
||||||
if channel not in self.listeners:
|
if channel not in self.listeners:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
exc = ChannelFailures()
|
exc = ChannelFailures()
|
||||||
output = []
|
output = []
|
||||||
|
|
||||||
items = [(self._priorities[(channel, listener)], listener)
|
items = [(self._priorities[(channel, listener)], listener)
|
||||||
for listener in self.listeners[channel]]
|
for listener in self.listeners[channel]]
|
||||||
try:
|
try:
|
||||||
@ -214,7 +214,7 @@ class Bus(object):
|
|||||||
if exc:
|
if exc:
|
||||||
raise exc
|
raise exc
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def _clean_exit(self):
|
def _clean_exit(self):
|
||||||
"""An atexit handler which asserts the Bus is not running."""
|
"""An atexit handler which asserts the Bus is not running."""
|
||||||
if self.state != states.EXITING:
|
if self.state != states.EXITING:
|
||||||
@ -224,11 +224,11 @@ class Bus(object):
|
|||||||
"bus.block() after start(), or call bus.exit() before the "
|
"bus.block() after start(), or call bus.exit() before the "
|
||||||
"main thread exits." % self.state, RuntimeWarning)
|
"main thread exits." % self.state, RuntimeWarning)
|
||||||
self.exit()
|
self.exit()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start all services."""
|
"""Start all services."""
|
||||||
atexit.register(self._clean_exit)
|
atexit.register(self._clean_exit)
|
||||||
|
|
||||||
self.state = states.STARTING
|
self.state = states.STARTING
|
||||||
self.log('Bus STARTING')
|
self.log('Bus STARTING')
|
||||||
try:
|
try:
|
||||||
@ -248,13 +248,13 @@ class Bus(object):
|
|||||||
pass
|
pass
|
||||||
# Re-raise the original error
|
# Re-raise the original error
|
||||||
raise e_info
|
raise e_info
|
||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
"""Stop all services and prepare to exit the process."""
|
"""Stop all services and prepare to exit the process."""
|
||||||
exitstate = self.state
|
exitstate = self.state
|
||||||
try:
|
try:
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
self.state = states.EXITING
|
self.state = states.EXITING
|
||||||
self.log('Bus EXITING')
|
self.log('Bus EXITING')
|
||||||
self.publish('exit')
|
self.publish('exit')
|
||||||
@ -267,31 +267,35 @@ class Bus(object):
|
|||||||
# can't just let exceptions propagate out unhandled.
|
# can't just let exceptions propagate out unhandled.
|
||||||
# Assume it's been logged and just die.
|
# Assume it's been logged and just die.
|
||||||
os._exit(70) # EX_SOFTWARE
|
os._exit(70) # EX_SOFTWARE
|
||||||
|
|
||||||
if exitstate == states.STARTING:
|
# Changed by Kovid, we cannot have all of calibre being quit
|
||||||
|
# Also we want to catch the port blocked/busy error and try listening only on
|
||||||
|
# the external ip
|
||||||
|
# See https://bitbucket.org/cherrypy/cherrypy/issue/1017/exit-behavior-is-not-good-when-running-in
|
||||||
|
if False and exitstate == states.STARTING:
|
||||||
# exit() was called before start() finished, possibly due to
|
# exit() was called before start() finished, possibly due to
|
||||||
# Ctrl-C because a start listener got stuck. In this case,
|
# Ctrl-C because a start listener got stuck. In this case,
|
||||||
# we could get stuck in a loop where Ctrl-C never exits the
|
# we could get stuck in a loop where Ctrl-C never exits the
|
||||||
# process, so we just call os.exit here.
|
# process, so we just call os.exit here.
|
||||||
os._exit(70) # EX_SOFTWARE
|
os._exit(70) # EX_SOFTWARE
|
||||||
|
|
||||||
def restart(self):
|
def restart(self):
|
||||||
"""Restart the process (may close connections).
|
"""Restart the process (may close connections).
|
||||||
|
|
||||||
This method does not restart the process from the calling thread;
|
This method does not restart the process from the calling thread;
|
||||||
instead, it stops the bus and asks the main thread to call execv.
|
instead, it stops the bus and asks the main thread to call execv.
|
||||||
"""
|
"""
|
||||||
self.execv = True
|
self.execv = True
|
||||||
self.exit()
|
self.exit()
|
||||||
|
|
||||||
def graceful(self):
|
def graceful(self):
|
||||||
"""Advise all services to reload."""
|
"""Advise all services to reload."""
|
||||||
self.log('Bus graceful')
|
self.log('Bus graceful')
|
||||||
self.publish('graceful')
|
self.publish('graceful')
|
||||||
|
|
||||||
def block(self, interval=0.1):
|
def block(self, interval=0.1):
|
||||||
"""Wait for the EXITING state, KeyboardInterrupt or SystemExit.
|
"""Wait for the EXITING state, KeyboardInterrupt or SystemExit.
|
||||||
|
|
||||||
This function is intended to be called only by the main thread.
|
This function is intended to be called only by the main thread.
|
||||||
After waiting for the EXITING state, it also waits for all threads
|
After waiting for the EXITING state, it also waits for all threads
|
||||||
to terminate, and then calls os.execv if self.execv is True. This
|
to terminate, and then calls os.execv if self.execv is True. This
|
||||||
@ -309,7 +313,7 @@ class Bus(object):
|
|||||||
self.log('SystemExit raised: shutting down bus')
|
self.log('SystemExit raised: shutting down bus')
|
||||||
self.exit()
|
self.exit()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Waiting for ALL child threads to finish is necessary on OS X.
|
# Waiting for ALL child threads to finish is necessary on OS X.
|
||||||
# See http://www.cherrypy.org/ticket/581.
|
# See http://www.cherrypy.org/ticket/581.
|
||||||
# It's also good to let them all shut down before allowing
|
# It's also good to let them all shut down before allowing
|
||||||
@ -327,22 +331,22 @@ class Bus(object):
|
|||||||
if not d:
|
if not d:
|
||||||
self.log("Waiting for thread %s." % t.getName())
|
self.log("Waiting for thread %s." % t.getName())
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
if self.execv:
|
if self.execv:
|
||||||
self._do_execv()
|
self._do_execv()
|
||||||
|
|
||||||
def wait(self, state, interval=0.1, channel=None):
|
def wait(self, state, interval=0.1, channel=None):
|
||||||
"""Poll for the given state(s) at intervals; publish to channel."""
|
"""Poll for the given state(s) at intervals; publish to channel."""
|
||||||
if isinstance(state, (tuple, list)):
|
if isinstance(state, (tuple, list)):
|
||||||
states = state
|
states = state
|
||||||
else:
|
else:
|
||||||
states = [state]
|
states = [state]
|
||||||
|
|
||||||
def _wait():
|
def _wait():
|
||||||
while self.state not in states:
|
while self.state not in states:
|
||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
self.publish(channel)
|
self.publish(channel)
|
||||||
|
|
||||||
# From http://psyco.sourceforge.net/psycoguide/bugs.html:
|
# From http://psyco.sourceforge.net/psycoguide/bugs.html:
|
||||||
# "The compiled machine code does not include the regular polling
|
# "The compiled machine code does not include the regular polling
|
||||||
# done by Python, meaning that a KeyboardInterrupt will not be
|
# done by Python, meaning that a KeyboardInterrupt will not be
|
||||||
@ -353,18 +357,18 @@ class Bus(object):
|
|||||||
sys.modules['psyco'].cannotcompile(_wait)
|
sys.modules['psyco'].cannotcompile(_wait)
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
_wait()
|
_wait()
|
||||||
|
|
||||||
def _do_execv(self):
|
def _do_execv(self):
|
||||||
"""Re-execute the current process.
|
"""Re-execute the current process.
|
||||||
|
|
||||||
This must be called from the main thread, because certain platforms
|
This must be called from the main thread, because certain platforms
|
||||||
(OS X) don't allow execv to be called in a child thread very well.
|
(OS X) don't allow execv to be called in a child thread very well.
|
||||||
"""
|
"""
|
||||||
args = sys.argv[:]
|
args = sys.argv[:]
|
||||||
self.log('Re-spawning %s' % ' '.join(args))
|
self.log('Re-spawning %s' % ' '.join(args))
|
||||||
|
|
||||||
if sys.platform[:4] == 'java':
|
if sys.platform[:4] == 'java':
|
||||||
from _systemrestart import SystemRestart
|
from _systemrestart import SystemRestart
|
||||||
raise SystemRestart
|
raise SystemRestart
|
||||||
@ -377,16 +381,16 @@ class Bus(object):
|
|||||||
if self.max_cloexec_files:
|
if self.max_cloexec_files:
|
||||||
self._set_cloexec()
|
self._set_cloexec()
|
||||||
os.execv(sys.executable, args)
|
os.execv(sys.executable, args)
|
||||||
|
|
||||||
def _set_cloexec(self):
|
def _set_cloexec(self):
|
||||||
"""Set the CLOEXEC flag on all open files (except stdin/out/err).
|
"""Set the CLOEXEC flag on all open files (except stdin/out/err).
|
||||||
|
|
||||||
If self.max_cloexec_files is an integer (the default), then on
|
If self.max_cloexec_files is an integer (the default), then on
|
||||||
platforms which support it, it represents the max open files setting
|
platforms which support it, it represents the max open files setting
|
||||||
for the operating system. This function will be called just before
|
for the operating system. This function will be called just before
|
||||||
the process is restarted via os.execv() to prevent open files
|
the process is restarted via os.execv() to prevent open files
|
||||||
from persisting into the new process.
|
from persisting into the new process.
|
||||||
|
|
||||||
Set self.max_cloexec_files to 0 to disable this behavior.
|
Set self.max_cloexec_files to 0 to disable this behavior.
|
||||||
"""
|
"""
|
||||||
for fd in range(3, self.max_cloexec_files): # skip stdin/out/err
|
for fd in range(3, self.max_cloexec_files): # skip stdin/out/err
|
||||||
@ -395,7 +399,7 @@ class Bus(object):
|
|||||||
except IOError:
|
except IOError:
|
||||||
continue
|
continue
|
||||||
fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
|
fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Stop all services."""
|
"""Stop all services."""
|
||||||
self.state = states.STOPPING
|
self.state = states.STOPPING
|
||||||
@ -403,7 +407,7 @@ class Bus(object):
|
|||||||
self.publish('stop')
|
self.publish('stop')
|
||||||
self.state = states.STOPPED
|
self.state = states.STOPPED
|
||||||
self.log('Bus STOPPED')
|
self.log('Bus STOPPED')
|
||||||
|
|
||||||
def start_with_callback(self, func, args=None, kwargs=None):
|
def start_with_callback(self, func, args=None, kwargs=None):
|
||||||
"""Start 'func' in a new thread T, then start self (and return T)."""
|
"""Start 'func' in a new thread T, then start self (and return T)."""
|
||||||
if args is None:
|
if args is None:
|
||||||
@ -411,18 +415,18 @@ class Bus(object):
|
|||||||
if kwargs is None:
|
if kwargs is None:
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
args = (func,) + args
|
args = (func,) + args
|
||||||
|
|
||||||
def _callback(func, *a, **kw):
|
def _callback(func, *a, **kw):
|
||||||
self.wait(states.STARTED)
|
self.wait(states.STARTED)
|
||||||
func(*a, **kw)
|
func(*a, **kw)
|
||||||
t = threading.Thread(target=_callback, args=args, kwargs=kwargs)
|
t = threading.Thread(target=_callback, args=args, kwargs=kwargs)
|
||||||
t.setName('Bus Callback ' + t.getName())
|
t.setName('Bus Callback ' + t.getName())
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
def log(self, msg="", level=20, traceback=False):
|
def log(self, msg="", level=20, traceback=False):
|
||||||
"""Log the given message. Append the last traceback if requested."""
|
"""Log the given message. Append the last traceback if requested."""
|
||||||
if traceback:
|
if traceback:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user