Files
DevOps/Kodi/Lenovo/addons/plugin.video.pseudotv.live/resources/lib/pool.py

167 lines
7.0 KiB
Python

# Copyright (C) 2024 Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
#
# -*- coding: utf-8 -*-
from globals import *
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, TimeoutError
from itertools import repeat, count
from functools import partial, wraps, reduce, update_wrapper
try:
import multiprocessing
cpu_count = multiprocessing.cpu_count()
ENABLE_POOL = False #True force disable multiproc. until monkeypatch/wrapper to fix pickling error.
except:
ENABLE_POOL = False
cpu_count = os.cpu_count()
def wrapped_partial(func, *args, **kwargs):
partial_func = partial(func, *args, **kwargs)
update_wrapper(partial_func, func)
return partial_func
def timeit(method):
@wraps(method)
def wrapper(*args, **kwargs):
start_time = time.time()
result = method(*args, **kwargs)
end_time = time.time()
if REAL_SETTINGS.getSetting('Debug_Enable').lower() == 'true':
log('%s timeit => %.2f ms'%(method.__qualname__.replace('.',': '),(end_time-start_time)*1000))
return result
return wrapper
def killit(method):
@wraps(method)
def wrapper(wait=30, *args, **kwargs):
class waiter(Thread):
def __init__(self):
Thread.__init__(self)
self.result = None
self.error = None
def run(self):
try: self.result = method(*args, **kwargs)
except: self.error = sys.exc_info()[0]
timer = waiter()
timer.name = '%s.%s'%('killit',method.__qualname__.replace('.',': '))
timer.daemon=True
timer.start()
try: timer.join(wait)
except: pass
log('%s, starting %s waiting (%s)'%(method.__qualname__.replace('.',': => -:'),timer.name,wait))
if (timer.is_alive() or timer.error): log('%s, Timed out! Errors: %s'%(method.__qualname__.replace('.',': '),timer.error), xbmc.LOGERROR)
return timer.result
return wrapper
def poolit(method):
@wraps(method)
def wrapper(items=[], *args, **kwargs):
try:
pool = ExecutorPool()
name = '%s.%s'%('poolit',method.__qualname__.replace('.',': '))
log('%s, starting %s'%(method.__qualname__.replace('.',': '),name))
results = pool.executors(method, items, *args, **kwargs)
except Exception as e:
log('poolit, failed! %s'%(e), xbmc.LOGERROR)
results = pool.generator(method, items, *args, **kwargs)
log('%s poolit => %s'%(pool.__class__.__name__, method.__qualname__.replace('.',': ')))
return list([_f for _f in results if _f])
return wrapper
def threadit(method):
@wraps(method)
def wrapper(*args, **kwargs):
thread_name = 'threadit.%s'%(method.__qualname__.replace('.',': '))
for thread in thread_enumerate():
if thread.name == thread_name and thread.is_alive():
if hasattr(thread, 'cancel'):
thread.cancel()
log('%s, canceling %s'%(method.__qualname__.replace('.',': '),thread_name))
thread = Thread(None, method, None, args, kwargs)
thread.name = thread_name
thread.daemon=True
thread.start()
log('%s, starting %s'%(method.__qualname__.replace('.',': '),thread.name))
return thread
return wrapper
def timerit(method):
@wraps(method)
def wrapper(wait, *args, **kwargs):
thread_name = 'timerit.%s'%(method.__qualname__.replace('.',': '))
for timer in thread_enumerate():
if timer.name == thread_name and timer.is_alive():
if hasattr(timer, 'cancel'):
timer.cancel()
log('%s, canceling %s'%(method.__qualname__.replace('.',': '),thread_name))
try:
timer.join()
log('%s, joining %s'%(method.__qualname__.replace('.',': '),thread_name))
except: pass
timer = Timer(float(wait), method, *args, **kwargs)
timer.name = thread_name
timer.start()
log('%s, starting %s wait = %s'%(method.__qualname__.replace('.',': '),thread_name,wait))
return timer
return wrapper
def executeit(method):
@wraps(method)
def wrapper(*args, **kwargs):
pool = ExecutorPool()
log('%s executeit => %s'%(pool.__class__.__name__, method.__qualname__.replace('.',': ')))
return pool.executor(method, None, *args, **kwargs)
return wrapper
class ExecutorPool:
def __init__(self):
self.CPUCount = cpu_count
if ENABLE_POOL: self.pool = ProcessPoolExecutor
else: self.pool = ThreadPoolExecutor
self.log(f"__init__, multiprocessing = {ENABLE_POOL}, CORES = {self.CPUCount}, THREADS = {self._calculate_thread_count()}")
def _calculate_thread_count(self):
if ENABLE_POOL: return self.CPUCount
else: return int(os.getenv('THREAD_COUNT', self.CPUCount * 2))
def log(self, msg, level=xbmc.LOGDEBUG):
return log('%s: %s'%(self.__class__.__name__,msg),level)
def executor(self, func, timeout=None, *args, **kwargs):
self.log("executor, func = %s, timeout = %s"%(func.__name__,timeout))
with self.pool(self._calculate_thread_count()) as executor:
try: return executor.submit(func, *args, **kwargs).result(timeout)
except Exception as e: self.log("executor, func = %s failed! %s\nargs = %s, kwargs = %s"%(func.__name__,e,args,kwargs), xbmc.LOGERROR)
def executors(self, func, items=[], *args, **kwargs):
self.log("executors, func = %s, items = %s"%(func.__name__,len(items)))
with self.pool(self._calculate_thread_count()) as executor:
try: return executor.map(wrapped_partial(func, *args, **kwargs), items)
except Exception as e: self.log("executors, func = %s, items = %s failed! %s\nargs = %s, kwargs = %s"%(func.__name__,len(items),e,args,kwargs), xbmc.LOGERROR)
def generator(self, func, items=[], *args, **kwargs):
self.log("generator, items = %s"%(len(items)))
try: return [wrapped_partial(func, *args, **kwargs)(i) for i in items]
except Exception as e: self.log("generator, func = %s, items = %s failed! %s\nargs = %s, kwargs = %s"%(func.__name__,len(items),e,args,kwargs), xbmc.LOGERROR)