hydrus/hydrus/core/HydrusThreading.py

963 lines
23 KiB
Python
Raw Normal View History

2018-02-14 21:47:18 +00:00
import bisect
2018-02-07 23:40:33 +00:00
import collections
2020-07-29 20:52:44 +00:00
import os
2019-01-09 22:59:03 +00:00
import queue
2018-05-23 21:05:06 +00:00
import random
2020-06-17 21:31:54 +00:00
import subprocess
2014-05-21 21:37:35 +00:00
import threading
import time
import traceback
2020-07-29 20:52:44 +00:00
2020-04-22 21:00:35 +00:00
from hydrus.core import HydrusData
2020-07-29 20:52:44 +00:00
from hydrus.core import HydrusExceptions
2020-04-22 21:00:35 +00:00
from hydrus.core import HydrusGlobals as HG
2014-05-21 21:37:35 +00:00
2018-04-11 22:30:40 +00:00
NEXT_THREAD_CLEAROUT = 0
2015-09-16 18:11:00 +00:00
THREADS_TO_THREAD_INFO = {}
THREAD_INFO_LOCK = threading.Lock()
2019-01-23 22:19:16 +00:00
def CheckIfThreadShuttingDown():
if IsThreadShuttingDown():
raise HydrusExceptions.ShutdownException( 'Thread is shutting down!' )
2018-04-11 22:30:40 +00:00
def ClearOutDeadThreads():
with THREAD_INFO_LOCK:
all_threads = list( THREADS_TO_THREAD_INFO.keys() )
for thread in all_threads:
if not thread.is_alive():
del THREADS_TO_THREAD_INFO[ thread ]
2015-09-16 18:11:00 +00:00
def GetThreadInfo( thread = None ):
2018-04-11 22:30:40 +00:00
global NEXT_THREAD_CLEAROUT
if HydrusData.TimeHasPassed( NEXT_THREAD_CLEAROUT ):
ClearOutDeadThreads()
NEXT_THREAD_CLEAROUT = HydrusData.GetNow() + 600
2015-09-16 18:11:00 +00:00
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():
if HG.controller.DoingFastExit():
2020-06-17 21:31:54 +00:00
return True
2018-02-28 22:30:36 +00:00
me = threading.current_thread()
if isinstance( me, DAEMON ):
2015-09-16 18:11:00 +00:00
2018-02-28 22:30:36 +00:00
if HG.view_shutdown:
return True
else:
if HG.model_shutdown:
return True
2015-09-16 18:11:00 +00:00
thread_info = GetThreadInfo()
return thread_info[ 'shutting_down' ]
def ShutdownThread( thread ):
thread_info = GetThreadInfo( thread )
thread_info[ 'shutting_down' ] = True
2020-06-17 21:31:54 +00:00
def SubprocessCommunicate( process: subprocess.Popen ):
def do_test():
if HG.model_shutdown:
try:
process.kill()
except:
pass
raise HydrusExceptions.ShutdownException( 'Application is shutting down!' )
do_test()
while True:
try:
return process.communicate( timeout = 10 )
except subprocess.TimeoutExpired:
do_test()
2014-05-21 21:37:35 +00:00
class DAEMON( threading.Thread ):
2018-02-14 21:47:18 +00:00
def __init__( self, controller, name ):
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.' )
2018-09-12 21:36:26 +00:00
def GetCurrentJobSummary( self ):
return 'unknown job'
def GetName( self ):
return self._name
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()
2019-01-23 22:19:16 +00:00
def _CanStart( self ):
2016-02-17 22:06:47 +00:00
2019-01-23 22:19:16 +00:00
return 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
2019-01-23 22:19:16 +00:00
def _DoAWait( self, wait_time, event_can_wake = True ):
time_to_start = HydrusData.GetNow() + wait_time
while not HydrusData.TimeHasPassed( time_to_start ):
if event_can_wake:
event_was_set = self._event.wait( 1.0 )
if event_was_set:
self._event.clear()
return
else:
time.sleep( 1.0 )
CheckIfThreadShuttingDown()
2014-05-21 21:37:35 +00:00
2019-01-23 22:19:16 +00:00
def _WaitUntilCanStart( self ):
2014-05-21 21:37:35 +00:00
2019-01-23 22:19:16 +00:00
while not self._CanStart():
time.sleep( 1.0 )
CheckIfThreadShuttingDown()
2014-05-21 21:37:35 +00:00
2018-09-12 21:36:26 +00:00
def GetCurrentJobSummary( self ):
return self._callable
2014-05-21 21:37:35 +00:00
def run( self ):
2019-01-23 22:19:16 +00:00
try:
2014-07-23 21:21:37 +00:00
2019-01-23 22:19:16 +00:00
self._DoAWait( self._init_wait )
2015-06-17 20:01:41 +00:00
2019-01-23 22:19:16 +00:00
while True:
2014-10-29 21:39:01 +00:00
2019-01-23 22:19:16 +00:00
CheckIfThreadShuttingDown()
2014-10-29 21:39:01 +00:00
2019-01-23 22:19:16 +00:00
self._DoAWait( self._pre_call_wait, event_can_wake = False )
2014-10-29 21:39:01 +00:00
2019-01-23 22:19:16 +00:00
CheckIfThreadShuttingDown()
2015-04-29 19:20:35 +00:00
2019-01-23 22:19:16 +00:00
self._WaitUntilCanStart()
2015-04-29 19:20:35 +00:00
2019-01-23 22:19:16 +00:00
CheckIfThreadShuttingDown()
2015-04-29 19:20:35 +00:00
2019-01-23 22:19:16 +00:00
self._DoPreCall()
2014-07-23 21:21:37 +00:00
2019-01-23 22:19:16 +00:00
try:
self._callable( self._controller )
except HydrusExceptions.ShutdownException:
return
except Exception as e:
HydrusData.ShowText( 'Daemon ' + self._name + ' encountered an exception:' )
HydrusData.ShowException( e )
2014-09-24 21:50:07 +00:00
2019-01-23 22:19:16 +00:00
self._DoAWait( self._period )
2014-07-23 21:21:37 +00:00
2014-05-21 21:37:35 +00:00
2019-01-23 22:19:16 +00:00
except HydrusExceptions.ShutdownException:
2014-05-21 21:37:35 +00:00
2019-01-23 22:19:16 +00:00
return
2014-05-21 21:37:35 +00:00
2019-01-23 22:19:16 +00:00
def set( self, *args, **kwargs ):
self._event.set()
2014-05-21 21:37:35 +00:00
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 ):
2019-06-19 22:08:48 +00:00
return self._controller.GoodTimeToStartBackgroundWork()
2016-12-14 21:19:07 +00:00
# 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 ):
2019-06-19 22:08:48 +00:00
return self._controller.GoodTimeToStartForegroundWork()
2016-12-14 21:19:07 +00:00
2018-02-14 21:47:18 +00:00
class THREADCallToThread( DAEMON ):
def __init__( self, controller, name ):
DAEMON.__init__( self, controller, name )
2018-09-12 21:36:26 +00:00
self._callable = None
2019-01-09 22:59:03 +00:00
self._queue = queue.Queue()
2018-02-14 21:47:18 +00:00
self._currently_working = True # start off true so new threads aren't used twice by two quick successive calls
def CurrentlyWorking( self ):
return self._currently_working
2018-09-12 21:36:26 +00:00
def GetCurrentJobSummary( self ):
return self._callable
2018-02-14 21:47:18 +00:00
def put( self, callable, *args, **kwargs ):
self._currently_working = True
self._queue.put( ( callable, args, kwargs ) )
self._event.set()
def run( self ):
2019-01-23 22:19:16 +00:00
try:
2018-02-14 21:47:18 +00:00
2019-01-23 22:19:16 +00:00
while True:
2018-02-14 21:47:18 +00:00
while self._queue.empty():
2019-01-23 22:19:16 +00:00
CheckIfThreadShuttingDown()
2018-02-14 21:47:18 +00:00
2019-01-23 22:19:16 +00:00
self._event.wait( 10.0 )
2018-02-14 21:47:18 +00:00
self._event.clear()
2019-01-23 22:19:16 +00:00
CheckIfThreadShuttingDown()
2018-02-14 21:47:18 +00:00
2019-01-23 22:19:16 +00:00
try:
2021-01-07 01:10:01 +00:00
try:
( callable, args, kwargs ) = self._queue.get( 1.0 )
except queue.Empty:
# https://github.com/hydrusnetwork/hydrus/issues/750
# this shouldn't happen, but...
# even if we assume we'll never get this, we don't want to make a business of hanging forever on things
continue
self._DoPreCall()
2019-01-23 22:19:16 +00:00
self._callable = ( callable, args, kwargs )
2021-07-14 20:42:19 +00:00
if HG.profile_mode:
2021-01-20 22:22:03 +00:00
summary = 'Profiling CallTo Job: {}'.format( callable )
2021-07-14 20:42:19 +00:00
HydrusData.Profile( summary, 'callable( *args, **kwargs )', globals(), locals(), min_duration_ms = HG.callto_profile_min_job_time_ms )
2021-01-20 22:22:03 +00:00
else:
callable( *args, **kwargs )
2019-01-23 22:19:16 +00:00
self._callable = None
del callable
except HydrusExceptions.ShutdownException:
return
except Exception as e:
HydrusData.Print( traceback.format_exc() )
HydrusData.ShowException( e )
finally:
self._currently_working = False
2018-02-14 21:47:18 +00:00
2019-01-23 22:19:16 +00:00
time.sleep( 0.00001 )
2018-02-14 21:47:18 +00:00
2019-01-23 22:19:16 +00:00
except HydrusExceptions.ShutdownException:
return
2018-02-14 21:47:18 +00:00
class JobScheduler( threading.Thread ):
2018-02-07 23:40:33 +00:00
def __init__( self, controller ):
2018-02-14 21:47:18 +00:00
threading.Thread.__init__( self, name = 'Job Scheduler' )
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
self._controller = controller
2018-02-07 23:40:33 +00:00
self._waiting = []
self._waiting_lock = threading.Lock()
2018-02-14 21:47:18 +00:00
self._new_job_arrived = threading.Event()
2018-02-07 23:40:33 +00:00
2018-09-12 21:36:26 +00:00
self._current_job = None
2018-02-14 21:47:18 +00:00
self._cancel_filter_needed = threading.Event()
2018-02-07 23:40:33 +00:00
self._sort_needed = threading.Event()
2018-02-14 21:47:18 +00:00
self._controller.sub( self, 'shutdown', 'shutdown' )
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
def _FilterCancelled( self ):
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
with self._waiting_lock:
self._waiting = [ job for job in self._waiting if not job.IsCancelled() ]
def _GetLoopWaitTime( self ):
2018-02-07 23:40:33 +00:00
with self._waiting_lock:
2018-02-14 21:47:18 +00:00
if len( self._waiting ) == 0:
return 0.2
next_job = self._waiting[0]
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
time_delta_until_due = next_job.GetTimeDeltaUntilDue()
return min( 1.0, time_delta_until_due )
2018-02-07 23:40:33 +00:00
def _NoWorkToStart( self ):
with self._waiting_lock:
if len( self._waiting ) == 0:
return True
next_job = self._waiting[0]
2018-02-14 21:47:18 +00:00
if next_job.IsDue():
2018-02-07 23:40:33 +00:00
return False
else:
return True
def _SortWaiting( self ):
# sort the waiting jobs in ascending order of expected work time
2018-02-14 21:47:18 +00:00
with self._waiting_lock: # this uses __lt__ to sort
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
self._waiting.sort()
2018-02-07 23:40:33 +00:00
def _StartWork( self ):
2018-05-23 21:05:06 +00:00
jobs_started = 0
2018-02-07 23:40:33 +00:00
while True:
with self._waiting_lock:
if len( self._waiting ) == 0:
break
2018-05-23 21:05:06 +00:00
if jobs_started >= 10: # try to avoid spikes
break
2018-02-07 23:40:33 +00:00
next_job = self._waiting[0]
2021-06-09 20:28:09 +00:00
if not next_job.IsDue():
2018-02-07 23:40:33 +00:00
2021-06-09 20:28:09 +00:00
# front is not due, so nor is the rest of the list
break
2018-05-23 21:05:06 +00:00
2021-06-09 20:28:09 +00:00
next_job = self._waiting.pop( 0 )
if next_job.IsCancelled():
continue
if next_job.SlotOK():
# important this happens outside of the waiting lock lmao!
next_job.StartWork()
jobs_started += 1
else:
# delay is automatically set by SlotOK
with self._waiting_lock:
2018-02-07 23:40:33 +00:00
2021-06-09 20:28:09 +00:00
bisect.insort( self._waiting, next_job )
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
def AddJob( self, job ):
with self._waiting_lock:
bisect.insort( self._waiting, job )
self._new_job_arrived.set()
2018-02-28 22:30:36 +00:00
def ClearOutDead( self ):
with self._waiting_lock:
self._waiting = [ job for job in self._waiting if not job.IsDead() ]
2018-09-12 21:36:26 +00:00
def GetName( self ):
return 'Job Scheduler'
def GetCurrentJobSummary( self ):
with self._waiting_lock:
return HydrusData.ToHumanInt( len( self._waiting ) ) + ' jobs'
2021-06-30 21:27:35 +00:00
def GetJobs( self ):
with self._waiting_lock:
return list( self._waiting )
2018-02-21 21:59:37 +00:00
def GetPrettyJobSummary( self ):
with self._waiting_lock:
num_jobs = len( self._waiting )
job_lines = [ repr( job ) for job in self._waiting ]
2018-07-04 20:48:28 +00:00
lines = [ HydrusData.ToHumanInt( num_jobs ) + ' jobs:' ] + job_lines
2018-02-21 21:59:37 +00:00
text = os.linesep.join( lines )
return text
2018-02-14 21:47:18 +00:00
def JobCancelled( self ):
self._cancel_filter_needed.set()
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
def shutdown( self ):
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
ShutdownThread( self )
2018-02-07 23:40:33 +00:00
2020-01-02 03:05:35 +00:00
self._new_job_arrived.set()
2018-02-07 23:40:33 +00:00
def WorkTimesHaveChanged( self ):
self._sort_needed.set()
def run( self ):
while True:
try:
while self._NoWorkToStart():
2018-02-28 22:30:36 +00:00
if IsThreadShuttingDown():
2018-02-07 23:40:33 +00:00
return
#
2018-02-14 21:47:18 +00:00
if self._cancel_filter_needed.is_set():
self._FilterCancelled()
self._cancel_filter_needed.clear()
2018-02-07 23:40:33 +00:00
if self._sort_needed.is_set():
self._SortWaiting()
self._sort_needed.clear()
2018-02-14 21:47:18 +00:00
continue # if some work is now due, let's do it!
#
wait_time = self._GetLoopWaitTime()
self._new_job_arrived.wait( wait_time )
self._new_job_arrived.clear()
2018-02-07 23:40:33 +00:00
self._StartWork()
except HydrusExceptions.ShutdownException:
return
except Exception as e:
HydrusData.Print( traceback.format_exc() )
HydrusData.ShowException( e )
time.sleep( 0.00001 )
2018-02-14 21:47:18 +00:00
class SchedulableJob( object ):
2018-02-07 23:40:33 +00:00
2021-06-30 21:27:35 +00:00
PRETTY_CLASS_NAME = 'job base'
def __init__( self, controller, scheduler: JobScheduler, initial_delay, work_callable ):
2018-02-07 23:40:33 +00:00
self._controller = controller
2018-02-14 21:47:18 +00:00
self._scheduler = scheduler
2018-02-07 23:40:33 +00:00
self._work_callable = work_callable
2019-02-13 22:26:43 +00:00
self._should_delay_on_wakeup = False
2018-02-14 21:47:18 +00:00
self._next_work_time = HydrusData.GetNowFloat() + initial_delay
2018-02-07 23:40:33 +00:00
2019-01-16 22:40:53 +00:00
self._thread_slot_type = None
2018-02-07 23:40:33 +00:00
self._work_lock = threading.Lock()
self._currently_working = threading.Event()
2018-02-14 21:47:18 +00:00
self._is_cancelled = threading.Event()
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
def __lt__( self, other ): # for the scheduler to do bisect.insort noice
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
return self._next_work_time < other._next_work_time
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
def __repr__( self ):
2018-02-07 23:40:33 +00:00
2021-06-30 21:27:35 +00:00
return '{}: {} {}'.format( self.PRETTY_CLASS_NAME, self.GetPrettyJob(), self.GetDueString() )
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
def _BootWorker( self ):
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
self._controller.CallToThread( self.Work )
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
def Cancel( self ):
self._is_cancelled.set()
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
self._scheduler.JobCancelled()
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
def CurrentlyWorking( self ):
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
return self._currently_working.is_set()
2018-02-07 23:40:33 +00:00
2021-06-30 21:27:35 +00:00
def GetDueString( self ):
due_delta = self._next_work_time - HydrusData.GetNowFloat()
due_string = HydrusData.TimeDeltaToPrettyTimeDelta( due_delta )
if due_delta < 0:
due_string = 'was due {} ago'.format( due_string )
else:
due_string = 'due in {}'.format( due_string )
return due_string
def GetNextWorkTime( self ):
return self._next_work_time
def GetPrettyJob( self ):
return repr( self._work_callable )
2018-02-14 21:47:18 +00:00
def GetTimeDeltaUntilDue( self ):
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
return HydrusData.GetTimeDeltaUntilTimeFloat( self._next_work_time )
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
def IsCancelled( self ):
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
return self._is_cancelled.is_set()
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
2018-02-28 22:30:36 +00:00
def IsDead( self ):
return False
2018-02-14 21:47:18 +00:00
def IsDue( self ):
return HydrusData.TimeHasPassedFloat( self._next_work_time )
2018-02-07 23:40:33 +00:00
2019-02-13 22:26:43 +00:00
def PubSubWake( self, *args, **kwargs ):
self.Wake()
2019-01-16 22:40:53 +00:00
def SetThreadSlotType( self, thread_type ):
self._thread_slot_type = thread_type
2019-02-13 22:26:43 +00:00
def ShouldDelayOnWakeup( self, value ):
self._should_delay_on_wakeup = value
2019-01-16 22:40:53 +00:00
def SlotOK( self ):
if self._thread_slot_type is not None:
if HG.controller.AcquireThreadSlot( self._thread_slot_type ):
return True
else:
self._next_work_time = HydrusData.GetNowFloat() + 10 + random.random()
return False
return True
2018-02-14 21:47:18 +00:00
def StartWork( self ):
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
if self._is_cancelled.is_set():
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
return
2018-02-07 23:40:33 +00:00
2018-02-14 21:47:18 +00:00
self._currently_working.set()
self._BootWorker()
2018-02-07 23:40:33 +00:00
2018-05-23 21:05:06 +00:00
def Wake( self, next_work_time = None ):
2018-05-16 20:09:50 +00:00
2018-05-23 21:05:06 +00:00
if next_work_time is None:
next_work_time = HydrusData.GetNowFloat()
self._next_work_time = next_work_time
2018-05-16 20:09:50 +00:00
self._scheduler.WorkTimesHaveChanged()
2019-02-13 22:26:43 +00:00
def WakeOnPubSub( self, topic ):
HG.controller.sub( self, 'PubSubWake', topic )
2018-02-07 23:40:33 +00:00
def Work( self ):
2018-02-14 21:47:18 +00:00
try:
2018-02-07 23:40:33 +00:00
2019-02-13 22:26:43 +00:00
if self._should_delay_on_wakeup:
while HG.controller.JustWokeFromSleep():
if IsThreadShuttingDown():
return
time.sleep( 1 )
2018-02-14 21:47:18 +00:00
with self._work_lock:
2018-02-07 23:40:33 +00:00
self._work_callable()
2018-02-14 21:47:18 +00:00
finally:
2019-01-16 22:40:53 +00:00
if self._thread_slot_type is not None:
HG.controller.ReleaseThreadSlot( self._thread_slot_type )
2018-02-14 21:47:18 +00:00
self._currently_working.clear()
2018-02-07 23:40:33 +00:00
2020-02-26 22:28:52 +00:00
class SingleJob( SchedulableJob ):
2021-06-30 21:27:35 +00:00
PRETTY_CLASS_NAME = 'single job'
2021-06-09 20:28:09 +00:00
def __init__( self, controller, scheduler: JobScheduler, initial_delay, work_callable ):
2020-02-26 22:28:52 +00:00
SchedulableJob.__init__( self, controller, scheduler, initial_delay, work_callable )
self._work_complete = threading.Event()
def IsWorkComplete( self ):
return self._work_complete.is_set()
def Work( self ):
SchedulableJob.Work( self )
self._work_complete.set()
2018-02-14 21:47:18 +00:00
class RepeatingJob( SchedulableJob ):
2014-05-21 21:37:35 +00:00
2021-06-30 21:27:35 +00:00
PRETTY_CLASS_NAME = 'repeating job'
2021-06-09 20:28:09 +00:00
def __init__( self, controller, scheduler: JobScheduler, initial_delay, period, work_callable ):
2014-05-21 21:37:35 +00:00
2018-05-16 20:09:50 +00:00
SchedulableJob.__init__( self, controller, scheduler, initial_delay, work_callable )
2014-05-21 21:37:35 +00:00
2018-02-14 21:47:18 +00:00
self._period = period
2014-05-21 21:37:35 +00:00
2018-02-14 21:47:18 +00:00
self._stop_repeating = threading.Event()
2016-07-20 19:57:10 +00:00
2018-02-14 21:47:18 +00:00
def Cancel( self ):
2016-07-20 19:57:10 +00:00
2018-02-14 21:47:18 +00:00
SchedulableJob.Cancel( self )
self._stop_repeating.set()
2016-07-20 19:57:10 +00:00
2014-05-21 21:37:35 +00:00
2018-02-14 21:47:18 +00:00
def Delay( self, delay ):
2014-05-21 21:37:35 +00:00
2018-02-14 21:47:18 +00:00
self._next_work_time = HydrusData.GetNowFloat() + delay
2017-08-09 21:33:51 +00:00
2018-02-14 21:47:18 +00:00
self._scheduler.WorkTimesHaveChanged()
2014-05-21 21:37:35 +00:00
2018-02-14 21:47:18 +00:00
2020-02-26 22:28:52 +00:00
def IsRepeatingWorkFinished( self ):
2018-02-14 21:47:18 +00:00
return self._stop_repeating.is_set()
2014-05-21 21:37:35 +00:00
2018-02-14 21:47:18 +00:00
def StartWork( self ):
if self._stop_repeating.is_set():
2014-05-21 21:37:35 +00:00
2018-02-14 21:47:18 +00:00
return
2014-05-21 21:37:35 +00:00
2018-02-14 21:47:18 +00:00
SchedulableJob.StartWork( self )
def Work( self ):
SchedulableJob.Work( self )
if not self._stop_repeating.is_set():
self._next_work_time = HydrusData.GetNowFloat() + self._period
self._scheduler.AddJob( self )
2014-05-28 21:03:24 +00:00
2014-05-21 21:37:35 +00:00
2016-12-14 21:19:07 +00:00