2012-02-05 11:55:26 +01:00
|
|
|
# Copyright (C) 2002-2012 John Goerzen & contributors
|
2002-07-04 02:02:10 +02:00
|
|
|
# Thread support module
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
2003-04-16 21:23:45 +02:00
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
2002-07-04 02:02:10 +02:00
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, write to the Free Software
|
2006-08-12 06:15:55 +02:00
|
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
2002-07-04 02:02:10 +02:00
|
|
|
|
2012-02-05 12:34:27 +01:00
|
|
|
from threading import Lock, Thread, BoundedSemaphore, currentThread
|
2012-02-05 11:55:26 +01:00
|
|
|
try:
|
|
|
|
from Queue import Queue, Empty
|
|
|
|
except ImportError: # python3
|
|
|
|
from queue import Queue, Empty
|
2011-03-11 22:13:21 +01:00
|
|
|
import traceback
|
2011-09-29 16:02:51 +02:00
|
|
|
import os.path
|
2011-03-11 22:13:21 +01:00
|
|
|
import sys
|
2011-01-05 17:00:55 +01:00
|
|
|
from offlineimap.ui import getglobalui
|
2002-07-23 03:48:15 +02:00
|
|
|
|
2002-07-05 04:34:39 +02:00
|
|
|
######################################################################
|
|
|
|
# General utilities
|
|
|
|
######################################################################
|
2002-07-04 02:02:10 +02:00
|
|
|
|
2002-07-04 03:35:05 +02:00
|
|
|
def semaphorereset(semaphore, originalstate):
|
2011-11-02 11:29:23 +01:00
|
|
|
"""Block until `semaphore` gets back to its original state, ie all acquired
|
|
|
|
resources have been released."""
|
2002-07-04 02:02:10 +02:00
|
|
|
for i in range(originalstate):
|
|
|
|
semaphore.acquire()
|
|
|
|
# Now release these.
|
|
|
|
for i in range(originalstate):
|
|
|
|
semaphore.release()
|
2011-10-26 16:47:21 +02:00
|
|
|
|
2003-01-04 05:57:46 +01:00
|
|
|
class threadlist:
|
|
|
|
def __init__(self):
|
|
|
|
self.lock = Lock()
|
|
|
|
self.list = []
|
|
|
|
|
|
|
|
def add(self, thread):
|
|
|
|
self.lock.acquire()
|
|
|
|
try:
|
|
|
|
self.list.append(thread)
|
|
|
|
finally:
|
|
|
|
self.lock.release()
|
|
|
|
|
|
|
|
def remove(self, thread):
|
|
|
|
self.lock.acquire()
|
|
|
|
try:
|
|
|
|
self.list.remove(thread)
|
|
|
|
finally:
|
|
|
|
self.lock.release()
|
|
|
|
|
|
|
|
def pop(self):
|
|
|
|
self.lock.acquire()
|
|
|
|
try:
|
|
|
|
if not len(self.list):
|
|
|
|
return None
|
|
|
|
return self.list.pop()
|
|
|
|
finally:
|
|
|
|
self.lock.release()
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
while 1:
|
|
|
|
thread = self.pop()
|
|
|
|
if not thread:
|
|
|
|
return
|
|
|
|
thread.join()
|
2011-10-26 16:47:21 +02:00
|
|
|
|
2003-01-04 05:57:46 +01:00
|
|
|
|
2002-07-05 04:34:39 +02:00
|
|
|
######################################################################
|
|
|
|
# Exit-notify threads
|
|
|
|
######################################################################
|
|
|
|
|
2008-08-05 07:05:29 +02:00
|
|
|
exitthreads = Queue(100)
|
2002-07-05 04:34:39 +02:00
|
|
|
|
|
|
|
def exitnotifymonitorloop(callback):
|
2010-12-04 22:43:56 +01:00
|
|
|
"""An infinite "monitoring" loop watching for finished ExitNotifyThread's.
|
|
|
|
|
2011-10-26 16:47:21 +02:00
|
|
|
This one is supposed to run in the main thread.
|
|
|
|
:param callback: the function to call when a thread terminated. That
|
|
|
|
function is called with a single argument -- the
|
|
|
|
ExitNotifyThread that has terminated. The monitor will
|
2010-12-04 22:43:56 +01:00
|
|
|
not continue to monitor for other threads until
|
|
|
|
'callback' returns, so if it intends to perform long
|
|
|
|
calculations, it should start a new thread itself -- but
|
2011-10-26 16:47:21 +02:00
|
|
|
NOT an ExitNotifyThread, or else an infinite loop
|
2010-12-04 22:43:56 +01:00
|
|
|
may result.
|
2011-10-26 16:47:21 +02:00
|
|
|
Furthermore, the monitor will hold the lock all the
|
2010-12-04 22:43:56 +01:00
|
|
|
while the other thread is waiting.
|
|
|
|
:type callback: a callable function
|
2002-07-05 04:34:39 +02:00
|
|
|
"""
|
2008-08-02 22:10:11 +02:00
|
|
|
global exitthreads
|
2011-11-02 11:55:05 +01:00
|
|
|
do_loop = True
|
|
|
|
while do_loop:
|
2010-12-04 22:43:56 +01:00
|
|
|
# Loop forever and call 'callback' for each thread that exited
|
2008-08-05 07:05:29 +02:00
|
|
|
try:
|
2010-12-04 22:43:56 +01:00
|
|
|
# we need a timeout in the get() call, so that ctrl-c can throw
|
|
|
|
# a SIGINT (http://bugs.python.org/issue1360). A timeout with empty
|
|
|
|
# Queue will raise `Empty`.
|
|
|
|
thrd = exitthreads.get(True, 60)
|
2011-11-02 11:55:05 +01:00
|
|
|
# request to abort when callback returns true
|
|
|
|
do_loop = (callback(thrd) != True)
|
2008-08-05 07:05:29 +02:00
|
|
|
except Empty:
|
2010-12-04 22:43:56 +01:00
|
|
|
pass
|
2002-07-05 04:34:39 +02:00
|
|
|
|
2002-10-07 23:17:13 +02:00
|
|
|
def threadexited(thread):
|
2011-11-02 11:55:05 +01:00
|
|
|
"""Called when a thread exits.
|
|
|
|
|
|
|
|
Main thread is aborted when this returns True."""
|
2011-01-05 17:00:55 +01:00
|
|
|
ui = getglobalui()
|
2011-10-27 17:23:43 +02:00
|
|
|
if thread.exit_exception:
|
|
|
|
if isinstance(thread.exit_exception, SystemExit):
|
2002-10-07 23:17:13 +02:00
|
|
|
# Bring a SystemExit into the main thread.
|
|
|
|
# Do not send it back to UI layer right now.
|
|
|
|
# Maybe later send it to ui.terminate?
|
|
|
|
raise SystemExit
|
|
|
|
ui.threadException(thread) # Expected to terminate
|
|
|
|
sys.exit(100) # Just in case...
|
2011-11-02 11:55:05 +01:00
|
|
|
elif thread.exit_message == 'SYNCRUNNER_EXITED_NORMALLY':
|
|
|
|
return True
|
2002-10-07 23:17:13 +02:00
|
|
|
else:
|
|
|
|
ui.threadExited(thread)
|
2011-11-02 11:55:05 +01:00
|
|
|
return False
|
2002-10-07 23:17:13 +02:00
|
|
|
|
2002-07-05 04:34:39 +02:00
|
|
|
class ExitNotifyThread(Thread):
|
2011-10-26 16:47:21 +02:00
|
|
|
"""This class is designed to alert a "monitor" to the fact that a
|
|
|
|
thread has exited and to provide for the ability for it to find out
|
|
|
|
why. All instances are made daemon threads (setDaemon(True), so we
|
2011-10-27 17:23:43 +02:00
|
|
|
bail out when the mainloop dies.
|
|
|
|
|
|
|
|
The thread can set instance variables self.exit_message for a human
|
|
|
|
readable reason of the thread exit."""
|
2011-09-29 16:02:51 +02:00
|
|
|
profiledir = None
|
|
|
|
"""class variable that is set to the profile directory if required"""
|
|
|
|
|
2011-10-26 16:47:21 +02:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super(ExitNotifyThread, self).__init__(*args, **kwargs)
|
|
|
|
# These are all child threads that are supposed to go away when
|
|
|
|
# the main thread is killed.
|
|
|
|
self.setDaemon(True)
|
2011-10-27 17:23:43 +02:00
|
|
|
self.exit_message = None
|
|
|
|
self._exit_exc = None
|
|
|
|
self._exit_stacktrace = None
|
2011-10-26 16:47:21 +02:00
|
|
|
|
2002-07-05 04:34:39 +02:00
|
|
|
def run(self):
|
2011-09-29 16:02:51 +02:00
|
|
|
global exitthreads
|
2002-07-05 04:34:39 +02:00
|
|
|
try:
|
2011-09-29 16:02:51 +02:00
|
|
|
if not ExitNotifyThread.profiledir: # normal case
|
2002-07-23 03:48:15 +02:00
|
|
|
Thread.run(self)
|
|
|
|
else:
|
2010-12-06 13:19:31 +01:00
|
|
|
try:
|
|
|
|
import cProfile as profile
|
|
|
|
except ImportError:
|
|
|
|
import profile
|
2002-07-23 03:48:15 +02:00
|
|
|
prof = profile.Profile()
|
|
|
|
try:
|
|
|
|
prof = prof.runctx("Thread.run(self)", globals(), locals())
|
|
|
|
except SystemExit:
|
|
|
|
pass
|
2011-09-29 16:02:51 +02:00
|
|
|
prof.dump_stats(os.path.join(ExitNotifyThread.profiledir,
|
2012-02-05 12:34:27 +01:00
|
|
|
"%s_%s.prof" % (self.ident, self.getName())))
|
2012-02-05 10:14:23 +01:00
|
|
|
except Exception as e:
|
2011-10-27 17:23:43 +02:00
|
|
|
# Thread exited with Exception, store it
|
|
|
|
tb = traceback.format_exc()
|
|
|
|
self.set_exit_exception(e, tb)
|
2008-08-02 22:10:11 +02:00
|
|
|
|
2008-12-01 23:10:49 +01:00
|
|
|
if exitthreads:
|
|
|
|
exitthreads.put(self, True)
|
2002-07-05 04:34:39 +02:00
|
|
|
|
2011-10-27 17:23:43 +02:00
|
|
|
def set_exit_exception(self, exc, st=None):
|
|
|
|
"""Sets Exception and stacktrace of a thread, so that other
|
|
|
|
threads can query its exit status"""
|
|
|
|
self._exit_exc = exc
|
|
|
|
self._exit_stacktrace = st
|
|
|
|
|
|
|
|
@property
|
|
|
|
def exit_exception(self):
|
2002-07-05 04:34:39 +02:00
|
|
|
"""Returns the cause of the exit, one of:
|
2011-10-27 17:23:43 +02:00
|
|
|
Exception() -- the thread aborted with this exception
|
|
|
|
None -- normal termination."""
|
|
|
|
return self._exit_exc
|
|
|
|
|
|
|
|
@property
|
|
|
|
def exit_stacktrace(self):
|
|
|
|
"""Returns a string representing the stack trace if set"""
|
|
|
|
return self._exit_stacktrace
|
2011-09-29 16:02:51 +02:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def set_profiledir(cls, directory):
|
|
|
|
"""If set, will output profile information to 'directory'"""
|
|
|
|
cls.profiledir = directory
|
|
|
|
|
2002-07-05 04:34:39 +02:00
|
|
|
|
|
|
|
######################################################################
|
|
|
|
# Instance-limited threads
|
|
|
|
######################################################################
|
|
|
|
|
2002-07-04 05:59:19 +02:00
|
|
|
instancelimitedsems = {}
|
|
|
|
instancelimitedlock = Lock()
|
|
|
|
|
|
|
|
def initInstanceLimit(instancename, instancemax):
|
2002-07-05 04:34:39 +02:00
|
|
|
"""Initialize the instance-limited thread implementation to permit
|
|
|
|
up to intancemax threads with the given instancename."""
|
2002-07-04 05:59:19 +02:00
|
|
|
instancelimitedlock.acquire()
|
2012-02-05 13:40:06 +01:00
|
|
|
if not instancename in instancelimitedsems:
|
2002-07-04 05:59:19 +02:00
|
|
|
instancelimitedsems[instancename] = BoundedSemaphore(instancemax)
|
|
|
|
instancelimitedlock.release()
|
|
|
|
|
2002-07-05 04:34:39 +02:00
|
|
|
class InstanceLimitedThread(ExitNotifyThread):
|
2002-07-04 05:59:19 +02:00
|
|
|
def __init__(self, instancename, *args, **kwargs):
|
|
|
|
self.instancename = instancename
|
2011-09-27 12:58:23 +02:00
|
|
|
super(InstanceLimitedThread, self).__init__(*args, **kwargs)
|
2002-07-04 05:59:19 +02:00
|
|
|
|
|
|
|
def start(self):
|
|
|
|
instancelimitedsems[self.instancename].acquire()
|
2002-07-05 04:34:39 +02:00
|
|
|
ExitNotifyThread.start(self)
|
2011-10-26 16:47:21 +02:00
|
|
|
|
2002-07-04 05:59:19 +02:00
|
|
|
def run(self):
|
|
|
|
try:
|
2002-07-05 04:34:39 +02:00
|
|
|
ExitNotifyThread.run(self)
|
2002-07-04 05:59:19 +02:00
|
|
|
finally:
|
2008-12-01 23:10:49 +01:00
|
|
|
if instancelimitedsems and instancelimitedsems[self.instancename]:
|
|
|
|
instancelimitedsems[self.instancename].release()
|