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 ( ) :
2020-06-24 21:25:24 +00:00
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-01-20 22:22:03 +00:00
if HG . callto_profile_mode :
summary = ' Profiling CallTo Job: {} ' . format ( callable )
HydrusData . Profile ( summary , ' callable( *args, **kwargs ) ' , globals ( ) , locals ( ) , min_duration_ms = 3 , show_summary = True )
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 '
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
2018-05-16 20:09:50 +00:00
def __init__ ( self , controller , scheduler , 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
2018-07-04 20:48:28 +00:00
return repr ( self . __class__ ) + ' : ' + repr ( self . _work_callable ) + ' next in ' + HydrusData . TimeDeltaToPrettyTimeDelta ( self . _next_work_time - HydrusData . GetNowFloat ( ) )
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
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-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-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 SetPeriod ( self , period ) :
2014-05-21 21:37:35 +00:00
2018-05-23 21:05:06 +00:00
if period > 10.0 :
period + = random . random ( ) # smooth out future spikes if ten of these all fire at the same time
2018-02-14 21:47:18 +00:00
self . _period = period
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