1+ import inspect
2+ import time
3+ from threading import Thread , Event
4+ from .task import Task
5+ from datetime import timedelta
6+ from concurrent .futures import ThreadPoolExecutor
7+ import logging
8+ import traceback
9+
10+ class BackgroundTask (Thread ):
11+ def __init__ (self , interval , task , max_repeat , callback = None , max_thread :int = 1 , pass_fail_job :bool = False ):
12+ assert callable (task )
13+
14+ Thread .__init__ (self )
15+ self .stopped = Event ()
16+ self .is_done = False
17+ self .interval = interval
18+ self .task = task
19+ self .max_repeat = max_repeat
20+ self .callback = callback
21+ self .output = None
22+ self .pool_limit = ThreadPoolExecutor (max_workers = max_thread )
23+ self .pass_fail_job = pass_fail_job
24+
25+ if callback is not None :
26+ self .pool_limit_callback = ThreadPoolExecutor (max_workers = 1 )
27+
28+ def stop (self ):
29+ self .stopped .set ()
30+ self .join ()
31+
32+ def get_output (self , task , * args , ** kwargs ):
33+ try :
34+ self .output = task (* args , ** kwargs )
35+ except Exception as ex :
36+ self .output = ("MLCHAIN_BACKGROUND_ERROR" , traceback .format_exc ())
37+ self .call_the_callback ()
38+
39+ def call_the_callback (self ):
40+ if self .callback :
41+ self .pool_limit_callback .submit (self .callback )
42+
43+ if isinstance (self .output , tuple ) and len (self .output ) == 2 and self .output [0 ] == "MLCHAIN_BACKGROUND_ERROR" :
44+ if self .pass_fail_job :
45+ logging .error ("BACKGROUND CALL ERROR: {0}" .format (self .output [1 ]))
46+ else :
47+ raise Exception ("BACKGROUND CALL ERROR: {0}" .format (self .output [1 ]))
48+
49+ def run (self ):
50+ if self .interval is not None :
51+ count_repeat = 0
52+ while (self .max_repeat < 0 or count_repeat < self .max_repeat ) \
53+ and (not self .stopped .wait (self .interval .total_seconds ())):
54+
55+ if isinstance (type (self .task ), Task ) \
56+ or issubclass (type (self .task ), Task ):
57+ self .pool_limit .submit (self .get_output , self .task .func_ , * self .task .args , ** self .task .kwargs )
58+ else :
59+ self .pool_limit .submit (self .get_output , self .task )
60+ count_repeat += 1
61+ else :
62+ if isinstance (type (self .task ), Task ) \
63+ or issubclass (type (self .task ), Task ):
64+ self .pool_limit .submit (self .get_output , self .task .func_ , * self .task .args , ** self .task .kwargs )
65+ else :
66+ self .pool_limit .submit (self .get_output , self .task )
67+
68+ self .pool_limit .shutdown (wait = True )
69+ self .is_done = True
70+
71+ if isinstance (self .output , tuple ) and len (self .output ) == 2 and self .output [0 ] == "MLCHAIN_BACKGROUND_ERROR" :
72+ if self .pass_fail_job :
73+ logging .error ("BACKGROUND CALL ERROR: {0}" .format (self .output [1 ]))
74+ else :
75+ raise Exception ("BACKGROUND CALL ERROR: {0}" .format (self .output [1 ]))
76+
77+ if self .callback is not None :
78+ self .pool_limit_callback .shutdown (wait = True )
79+ self .is_done = True
80+
81+ def wait (self , interval : float = 0.1 ):
82+ while not self .is_done :
83+ time .sleep (interval )
84+ return self .output
85+
86+ def wait (self , interval : float = 0.1 ):
87+ while not self .is_done :
88+ time .sleep (interval )
89+ return self .output
90+
91+ class Background :
92+ """
93+ Run a task in background using Threading.Event
94+ :task: [Task, function] item
95+ :interval: timedelta or float seconds
96+ """
97+
98+ def __init__ (self , task , interval :float = None , max_repeat :int = - 1 , callback = None ):
99+ assert callable (task ), 'You have to transfer a callable instance or an mlchain.Task'
100+ assert (max_repeat > 0 and interval is not None and interval > 0 ) or max_repeat == - 1 , "interval need to be set when max_repeat > 0"
101+ assert callback is None or callable (callback ), "callback need to be callable"
102+
103+ if interval is not None :
104+ if isinstance (interval , int ) or isinstance (interval , float ):
105+ interval = timedelta (seconds = interval )
106+
107+ self .task = task
108+ self .interval = interval
109+ self .max_repeat = max_repeat
110+ self .callback = callback
111+
112+ def run (self , max_thread :int = 1 , pass_fail_job :bool = False ):
113+ task = BackgroundTask (interval = self .interval , task = self .task ,
114+ max_repeat = self .max_repeat , callback = self .callback , max_thread = max_thread , pass_fail_job = pass_fail_job )
115+ task .start ()
116+
117+ return task
0 commit comments