2018-02-07 23:40:33 +00:00
|
|
|
import collections
|
2015-04-29 19:20:35 +00:00
|
|
|
import HydrusExceptions
|
2014-05-21 21:37:35 +00:00
|
|
|
import Queue
|
|
|
|
import threading
|
|
|
|
import time
|
|
|
|
import traceback
|
2015-03-25 22:04:19 +00:00
|
|
|
import HydrusData
|
2017-05-10 21:33:58 +00:00
|
|
|
import HydrusGlobals as HG
|
2015-09-16 18:11:00 +00:00
|
|
|
import os
|
2014-05-21 21:37:35 +00:00
|
|
|
|
2015-09-16 18:11:00 +00:00
|
|
|
THREADS_TO_THREAD_INFO = {}
|
|
|
|
THREAD_INFO_LOCK = threading.Lock()
|
|
|
|
|
|
|
|
def GetThreadInfo( thread = None ):
|
|
|
|
|
|
|
|
if thread is None:
|
|
|
|
|
|
|
|
thread = threading.current_thread()
|
|
|
|
|
|
|
|
|
|
|
|
with THREAD_INFO_LOCK:
|
|
|
|
|
|
|
|
if thread not in THREADS_TO_THREAD_INFO:
|
|
|
|
|
|
|
|
thread_info = {}
|
|
|
|
|
|
|
|
thread_info[ 'shutting_down' ] = False
|
|
|
|
|
|
|
|
THREADS_TO_THREAD_INFO[ thread ] = thread_info
|
|
|
|
|
|
|
|
|
|
|
|
return THREADS_TO_THREAD_INFO[ thread ]
|
|
|
|
|
|
|
|
|
|
|
|
def IsThreadShuttingDown():
|
|
|
|
|
2017-05-10 21:33:58 +00:00
|
|
|
if HG.view_shutdown:
|
2015-09-16 18:11:00 +00:00
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
thread_info = GetThreadInfo()
|
|
|
|
|
|
|
|
return thread_info[ 'shutting_down' ]
|
|
|
|
|
|
|
|
def ShutdownThread( thread ):
|
|
|
|
|
|
|
|
thread_info = GetThreadInfo( thread )
|
|
|
|
|
|
|
|
thread_info[ 'shutting_down' ] = True
|
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
class DAEMON( threading.Thread ):
|
|
|
|
|
2015-08-26 21:18:39 +00:00
|
|
|
def __init__( self, controller, name, period = 1200 ):
|
2014-05-21 21:37:35 +00:00
|
|
|
|
|
|
|
threading.Thread.__init__( self, name = name )
|
|
|
|
|
2015-08-26 21:18:39 +00:00
|
|
|
self._controller = controller
|
2014-05-21 21:37:35 +00:00
|
|
|
self._name = name
|
|
|
|
|
|
|
|
self._event = threading.Event()
|
|
|
|
|
2015-08-26 21:18:39 +00:00
|
|
|
self._controller.sub( self, 'wake', 'wake_daemons' )
|
2015-09-16 18:11:00 +00:00
|
|
|
self._controller.sub( self, 'shutdown', 'shutdown' )
|
|
|
|
|
|
|
|
|
2017-12-13 22:33:07 +00:00
|
|
|
def _DoPreCall( self ):
|
|
|
|
|
|
|
|
if HG.daemon_report_mode:
|
|
|
|
|
|
|
|
HydrusData.ShowText( self._name + ' doing a job.' )
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-09-16 18:11:00 +00:00
|
|
|
def shutdown( self ):
|
|
|
|
|
|
|
|
ShutdownThread( self )
|
|
|
|
|
|
|
|
self.wake()
|
2014-05-21 21:37:35 +00:00
|
|
|
|
|
|
|
|
2015-08-26 21:18:39 +00:00
|
|
|
def wake( self ):
|
|
|
|
|
|
|
|
self._event.set()
|
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
|
|
|
|
class DAEMONWorker( DAEMON ):
|
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
def __init__( self, controller, name, callable, topics = None, period = 3600, init_wait = 3, pre_call_wait = 0 ):
|
2016-02-17 22:06:47 +00:00
|
|
|
|
2017-08-09 21:33:51 +00:00
|
|
|
if topics is None:
|
|
|
|
|
|
|
|
topics = []
|
|
|
|
|
2016-02-17 22:06:47 +00:00
|
|
|
|
|
|
|
DAEMON.__init__( self, controller, name )
|
|
|
|
|
|
|
|
self._callable = callable
|
|
|
|
self._topics = topics
|
|
|
|
self._period = period
|
2016-11-30 20:24:17 +00:00
|
|
|
self._init_wait = init_wait
|
2016-12-14 21:19:07 +00:00
|
|
|
self._pre_call_wait = pre_call_wait
|
2016-02-17 22:06:47 +00:00
|
|
|
|
2017-08-09 21:33:51 +00:00
|
|
|
for topic in topics:
|
|
|
|
|
|
|
|
self._controller.sub( self, 'set', topic )
|
|
|
|
|
2016-02-17 22:06:47 +00:00
|
|
|
|
|
|
|
self.start()
|
|
|
|
|
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
def _CanStart( self, time_started_waiting ):
|
2016-02-17 22:06:47 +00:00
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
return self._PreCallWaitIsDone( time_started_waiting ) and self._ControllerIsOKWithIt()
|
2016-02-17 22:06:47 +00:00
|
|
|
|
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
def _ControllerIsOKWithIt( self ):
|
2015-03-25 22:04:19 +00:00
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
return True
|
2014-05-21 21:37:35 +00:00
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
|
|
|
|
def _PreCallWaitIsDone( self, time_started_waiting ):
|
2014-05-21 21:37:35 +00:00
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
# just shave a bit off so things that don't have any wait won't somehow have to wait a single accidentaly cycle
|
|
|
|
time_to_start = ( float( time_started_waiting ) - 0.1 ) + self._pre_call_wait
|
2014-05-21 21:37:35 +00:00
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
return HydrusData.TimeHasPassed( time_to_start )
|
2014-05-21 21:37:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
def run( self ):
|
|
|
|
|
|
|
|
self._event.wait( self._init_wait )
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
if IsThreadShuttingDown():
|
|
|
|
|
|
|
|
return
|
|
|
|
|
2014-07-23 21:21:37 +00:00
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
time_started_waiting = HydrusData.GetNow()
|
2015-06-17 20:01:41 +00:00
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
while not self._CanStart( time_started_waiting ):
|
2014-10-29 21:39:01 +00:00
|
|
|
|
2015-09-16 18:11:00 +00:00
|
|
|
time.sleep( 1 )
|
2014-10-29 21:39:01 +00:00
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
if IsThreadShuttingDown():
|
|
|
|
|
|
|
|
return
|
|
|
|
|
2014-10-29 21:39:01 +00:00
|
|
|
|
|
|
|
|
2017-12-13 22:33:07 +00:00
|
|
|
self._DoPreCall()
|
|
|
|
|
2015-04-29 19:20:35 +00:00
|
|
|
try:
|
|
|
|
|
2015-11-04 22:30:28 +00:00
|
|
|
self._callable( self._controller )
|
2015-04-29 19:20:35 +00:00
|
|
|
|
|
|
|
except HydrusExceptions.ShutdownException:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
2014-07-23 21:21:37 +00:00
|
|
|
except Exception as e:
|
|
|
|
|
2015-03-25 22:04:19 +00:00
|
|
|
HydrusData.ShowText( 'Daemon ' + self._name + ' encountered an exception:' )
|
2014-09-24 21:50:07 +00:00
|
|
|
|
2015-03-25 22:04:19 +00:00
|
|
|
HydrusData.ShowException( e )
|
2014-07-23 21:21:37 +00:00
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
|
2015-09-16 18:11:00 +00:00
|
|
|
if IsThreadShuttingDown(): return
|
2014-05-21 21:37:35 +00:00
|
|
|
|
|
|
|
self._event.wait( self._period )
|
|
|
|
|
|
|
|
self._event.clear()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set( self, *args, **kwargs ): self._event.set()
|
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
# Big stuff like DB maintenance that we don't want to run while other important stuff is going on, like user interaction or vidya on another process
|
|
|
|
class DAEMONBackgroundWorker( DAEMONWorker ):
|
|
|
|
|
|
|
|
def _ControllerIsOKWithIt( self ):
|
|
|
|
|
|
|
|
return self._controller.GoodTimeToDoBackgroundWork()
|
|
|
|
|
|
|
|
|
|
|
|
# Big stuff that we want to run when the user sees, but not at the expense of something else, like laggy session load
|
|
|
|
class DAEMONForegroundWorker( DAEMONWorker ):
|
|
|
|
|
|
|
|
def _ControllerIsOKWithIt( self ):
|
|
|
|
|
|
|
|
return self._controller.GoodTimeToDoForegroundWork()
|
|
|
|
|
|
|
|
|
2018-02-07 23:40:33 +00:00
|
|
|
class JobScheduler( DAEMON ):
|
|
|
|
|
|
|
|
def __init__( self, controller ):
|
|
|
|
|
|
|
|
DAEMON.__init__( self, controller, 'JobScheduler' )
|
|
|
|
|
|
|
|
self._currently_working = []
|
|
|
|
|
|
|
|
self._waiting = []
|
|
|
|
|
|
|
|
self._waiting_lock = threading.Lock()
|
|
|
|
|
|
|
|
self._new_action = threading.Event()
|
|
|
|
|
|
|
|
self._sort_needed = threading.Event()
|
|
|
|
|
|
|
|
|
|
|
|
def _InsertJob( self, job ):
|
|
|
|
|
|
|
|
# write __lt__, __gt__, stuff and do a bisect insort_left here
|
|
|
|
|
|
|
|
with self._waiting_lock:
|
|
|
|
|
|
|
|
self._waiting.append( job )
|
|
|
|
|
|
|
|
|
|
|
|
self._sort_needed.set()
|
|
|
|
|
|
|
|
|
|
|
|
def _NoWorkToStart( self ):
|
|
|
|
|
|
|
|
with self._waiting_lock:
|
|
|
|
|
|
|
|
if len( self._waiting ) == 0:
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
next_job = self._waiting[0]
|
|
|
|
|
|
|
|
|
|
|
|
if HydrusData.TimeHasPassed( next_job.GetNextWorkTime() ):
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _RescheduleFinishedJobs( self ):
|
|
|
|
|
|
|
|
def reschedule_finished_job( job ):
|
|
|
|
|
|
|
|
if job.CurrentlyWorking():
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
self._InsertJob( job )
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self._currently_working = filter( reschedule_finished_job, self._currently_working )
|
|
|
|
|
|
|
|
|
|
|
|
def _SortWaiting( self ):
|
|
|
|
|
|
|
|
# sort the waiting jobs in ascending order of expected work time
|
|
|
|
|
|
|
|
def key( job ):
|
|
|
|
|
|
|
|
return job.GetNextWorkTime()
|
|
|
|
|
|
|
|
|
|
|
|
with self._waiting_lock:
|
|
|
|
|
|
|
|
self._waiting.sort( key = key )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _StartWork( self ):
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
|
|
|
with self._waiting_lock:
|
|
|
|
|
|
|
|
if len( self._waiting ) == 0:
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
next_job = self._waiting[0]
|
|
|
|
|
|
|
|
if HydrusData.TimeHasPassed( next_job.GetNextWorkTime() ):
|
|
|
|
|
|
|
|
next_job = self._waiting.pop( 0 )
|
|
|
|
|
|
|
|
if not next_job.IsDead():
|
|
|
|
|
|
|
|
next_job.StartWork()
|
|
|
|
|
|
|
|
self._currently_working.append( next_job )
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
break # all the rest in the queue are not due
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def RegisterJob( self, job ):
|
|
|
|
|
|
|
|
job.SetScheduler( self )
|
|
|
|
|
|
|
|
self._InsertJob( job )
|
|
|
|
|
|
|
|
|
|
|
|
def WorkTimesHaveChanged( self ):
|
|
|
|
|
|
|
|
self._sort_needed.set()
|
|
|
|
|
|
|
|
|
|
|
|
def run( self ):
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
while self._NoWorkToStart():
|
|
|
|
|
|
|
|
if self._controller.ModelIsShutdown():
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
|
|
self._RescheduleFinishedJobs()
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
|
|
self._sort_needed.wait( 0.2 )
|
|
|
|
|
|
|
|
if self._sort_needed.is_set():
|
|
|
|
|
|
|
|
self._SortWaiting()
|
|
|
|
|
|
|
|
self._sort_needed.clear()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self._StartWork()
|
|
|
|
|
|
|
|
except HydrusExceptions.ShutdownException:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
HydrusData.Print( traceback.format_exc() )
|
|
|
|
|
|
|
|
HydrusData.ShowException( e )
|
|
|
|
|
|
|
|
|
|
|
|
time.sleep( 0.00001 )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RepeatingJob( object ):
|
|
|
|
|
|
|
|
def __init__( self, controller, work_callable, period, initial_delay = 0 ):
|
|
|
|
|
|
|
|
self._controller = controller
|
|
|
|
self._work_callable = work_callable
|
|
|
|
self._period = period
|
|
|
|
|
|
|
|
self._is_dead = threading.Event()
|
|
|
|
|
|
|
|
self._work_lock = threading.Lock()
|
|
|
|
|
|
|
|
self._currently_working = threading.Event()
|
|
|
|
|
|
|
|
self._next_work_time = HydrusData.GetNow() + initial_delay
|
|
|
|
|
|
|
|
self._scheduler = None
|
|
|
|
|
|
|
|
# registers itself with controller here
|
|
|
|
|
|
|
|
|
|
|
|
def CurrentlyWorking( self ):
|
|
|
|
|
|
|
|
return self._currently_working.is_set()
|
|
|
|
|
|
|
|
|
|
|
|
def GetNextWorkTime( self ):
|
|
|
|
|
|
|
|
return self._next_work_time
|
|
|
|
|
|
|
|
|
|
|
|
def IsDead( self ):
|
|
|
|
|
|
|
|
return self._is_dead.is_set()
|
|
|
|
|
|
|
|
|
|
|
|
def Kill( self ):
|
|
|
|
|
|
|
|
self._is_dead.set()
|
|
|
|
|
|
|
|
|
|
|
|
def SetScheduler( self, scheduler ):
|
|
|
|
|
|
|
|
self._scheduler = scheduler
|
|
|
|
|
|
|
|
|
|
|
|
def StartWork( self ):
|
|
|
|
|
|
|
|
self._currently_working.set()
|
|
|
|
|
|
|
|
self._controller.CallToThread( self.Work )
|
|
|
|
|
|
|
|
|
|
|
|
def WakeAndWork( self ):
|
|
|
|
|
|
|
|
self._next_work_time = HydrusData.GetNow()
|
|
|
|
|
|
|
|
if self._scheduler is not None:
|
|
|
|
|
|
|
|
self._scheduler.WorkTimesHaveChanged()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def Work( self ):
|
|
|
|
|
|
|
|
with self._work_lock:
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
self._work_callable()
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
|
|
|
self._next_work_time = HydrusData.GetNow() + self._period
|
|
|
|
|
|
|
|
self._currently_working.clear()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-12-23 22:51:04 +00:00
|
|
|
class THREADCallToThread( DAEMON ):
|
2014-05-21 21:37:35 +00:00
|
|
|
|
2015-08-26 21:18:39 +00:00
|
|
|
def __init__( self, controller ):
|
2014-05-21 21:37:35 +00:00
|
|
|
|
2015-08-26 21:18:39 +00:00
|
|
|
DAEMON.__init__( self, controller, 'CallToThread' )
|
2014-05-21 21:37:35 +00:00
|
|
|
|
|
|
|
self._queue = Queue.Queue()
|
|
|
|
|
2017-08-09 21:33:51 +00:00
|
|
|
self._currently_working = True # start off true so new threads aren't used twice by two quick successive calls
|
2016-07-20 19:57:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
def CurrentlyWorking( self ):
|
|
|
|
|
|
|
|
return self._currently_working
|
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
|
|
|
|
def put( self, callable, *args, **kwargs ):
|
|
|
|
|
2017-08-09 21:33:51 +00:00
|
|
|
self._currently_working = True
|
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
self._queue.put( ( callable, args, kwargs ) )
|
|
|
|
|
|
|
|
self._event.set()
|
|
|
|
|
|
|
|
|
|
|
|
def run( self ):
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
2014-05-28 21:03:24 +00:00
|
|
|
try:
|
|
|
|
|
2017-08-09 21:33:51 +00:00
|
|
|
while self._queue.empty():
|
|
|
|
|
|
|
|
if self._controller.ModelIsShutdown():
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self._event.wait( 1200 )
|
|
|
|
|
|
|
|
self._event.clear()
|
|
|
|
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2017-12-13 22:33:07 +00:00
|
|
|
self._DoPreCall()
|
|
|
|
|
2017-08-09 21:33:51 +00:00
|
|
|
( callable, args, kwargs ) = self._queue.get()
|
2016-07-20 19:57:10 +00:00
|
|
|
|
2014-05-28 21:03:24 +00:00
|
|
|
callable( *args, **kwargs )
|
|
|
|
|
2014-06-18 21:53:48 +00:00
|
|
|
del callable
|
|
|
|
|
2015-04-29 19:20:35 +00:00
|
|
|
except HydrusExceptions.ShutdownException:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
2015-03-04 22:44:32 +00:00
|
|
|
except Exception as e:
|
|
|
|
|
2017-08-16 21:58:06 +00:00
|
|
|
HydrusData.Print( traceback.format_exc() )
|
|
|
|
|
2015-03-25 22:04:19 +00:00
|
|
|
HydrusData.ShowException( e )
|
2015-03-04 22:44:32 +00:00
|
|
|
|
2016-07-20 19:57:10 +00:00
|
|
|
finally:
|
|
|
|
|
|
|
|
self._currently_working = False
|
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
|
2014-05-28 21:03:24 +00:00
|
|
|
time.sleep( 0.00001 )
|
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
|