|
|
@ -1,18 +1,85 @@ |
|
|
|
from threading import Thread, Lock |
|
|
|
from threading import Thread, Lock, RLock |
|
|
|
from io import StringIO |
|
|
|
from io import StringIO |
|
|
|
from subprocess import Popen, PIPE, STDOUT, DEVNULL |
|
|
|
from subprocess import Popen, PIPE, STDOUT, DEVNULL |
|
|
|
import uuid |
|
|
|
import uuid |
|
|
|
|
|
|
|
from datetime import datetime |
|
|
|
from web_utils.business_exception import BusinessException |
|
|
|
from web_utils.business_exception import BusinessException |
|
|
|
|
|
|
|
import time |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resources = {} |
|
|
|
|
|
|
|
|
|
|
|
tasks = {} |
|
|
|
tasks = {} |
|
|
|
|
|
|
|
pending_tasks = {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
task_management_mutex = RLock() |
|
|
|
|
|
|
|
resource_mutex = RLock() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Resource: |
|
|
|
|
|
|
|
def __init__(self, type_id, amount, is_single_resource): |
|
|
|
|
|
|
|
self.type_id = type_id |
|
|
|
|
|
|
|
self.amount = amount |
|
|
|
|
|
|
|
self.is_single_resource = is_single_resource |
|
|
|
|
|
|
|
self.available_amount = {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_available_amount(self, resource_id): |
|
|
|
|
|
|
|
assert(resource_id == '' or not self.is_single_resource) |
|
|
|
|
|
|
|
resource_mutex.acquire() |
|
|
|
|
|
|
|
if resource_id in self.available_amount: |
|
|
|
|
|
|
|
result = self.available_amount[resource_id] |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
result = self.amount |
|
|
|
|
|
|
|
resource_mutex.release() |
|
|
|
|
|
|
|
return result |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def acquire(self, resource_id, amount): |
|
|
|
|
|
|
|
assert(resource_id == '' or not self.is_single_resource) |
|
|
|
|
|
|
|
resource_mutex.acquire() |
|
|
|
|
|
|
|
if resource_id not in self.available_amount: |
|
|
|
|
|
|
|
self.available_amount[resource_id] = self.amount |
|
|
|
|
|
|
|
available_amount = self.available_amount[resource_id] |
|
|
|
|
|
|
|
assert(available_amount >= amount) |
|
|
|
|
|
|
|
self.available_amount[resource_id] = available_amount - amount |
|
|
|
|
|
|
|
resource_mutex.release() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def release(self, resource_id, amount): |
|
|
|
|
|
|
|
assert(resource_id == '' or not self.is_single_resource) |
|
|
|
|
|
|
|
resource_mutex.acquire() |
|
|
|
|
|
|
|
assert(resource_id in self.available_amount) |
|
|
|
|
|
|
|
available_amount = self.available_amount[resource_id] |
|
|
|
|
|
|
|
assert(available_amount + amount <= self.amount) |
|
|
|
|
|
|
|
self.available_amount[resource_id] = available_amount + amount |
|
|
|
|
|
|
|
resource_mutex.release() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
|
|
|
def register(type_id, amount, is_single_resource = True): |
|
|
|
|
|
|
|
assert(type_id not in resources) |
|
|
|
|
|
|
|
resources[type_id] = Resource(type_id, amount, is_single_resource) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
|
|
|
def get(type_id): |
|
|
|
|
|
|
|
return resources[type_id] |
|
|
|
|
|
|
|
|
|
|
|
class Task(Thread): |
|
|
|
class Task: |
|
|
|
def __init__(self): |
|
|
|
def __init__(self, resource_usage): |
|
|
|
Thread.__init__(self) |
|
|
|
self.__thread = None |
|
|
|
self.task_id = uuid.uuid4() |
|
|
|
self.task_id = str(uuid.uuid4()) |
|
|
|
self.__mutex = Lock() |
|
|
|
self.__mutex = Lock() |
|
|
|
self.__output = StringIO() |
|
|
|
self.__output = StringIO() |
|
|
|
tasks[str(self.task_id)] = self |
|
|
|
self.__resource_usage = list(resource_usage) |
|
|
|
|
|
|
|
self.__resource_usage.append(('__thread', '')) |
|
|
|
|
|
|
|
self.__has_logged_waiting = '' |
|
|
|
|
|
|
|
self.__skip_id = '' |
|
|
|
|
|
|
|
self.__finished_at = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Check all required resources are registered |
|
|
|
|
|
|
|
for required_resource in self.__resource_usage: |
|
|
|
|
|
|
|
resource = Resource.get(required_resource[0]) |
|
|
|
|
|
|
|
single = (required_resource[1] == '') |
|
|
|
|
|
|
|
assert(single == resource.is_single_resource) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
task_management_mutex.acquire() |
|
|
|
|
|
|
|
tasks[self.task_id] = self |
|
|
|
|
|
|
|
task_management_mutex.release() |
|
|
|
|
|
|
|
|
|
|
|
def print(self, str): |
|
|
|
def print(self, str): |
|
|
|
self.__mutex.acquire() |
|
|
|
self.__mutex.acquire() |
|
|
@ -25,13 +92,133 @@ class Task(Thread): |
|
|
|
self.__mutex.release() |
|
|
|
self.__mutex.release() |
|
|
|
return result |
|
|
|
return result |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def start(self, skip_if_another_pending = None): |
|
|
|
|
|
|
|
task_management_mutex.acquire() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
skip_me = False |
|
|
|
|
|
|
|
self.__skip_id = skip_if_another_pending |
|
|
|
|
|
|
|
if skip_if_another_pending is not None: |
|
|
|
|
|
|
|
for id, other_task in pending_tasks.items(): |
|
|
|
|
|
|
|
if other_task.__skip_id == skip_if_another_pending: |
|
|
|
|
|
|
|
skip_me = True |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if skip_me: |
|
|
|
|
|
|
|
print('Task ' + self.task_id + ' skipped') |
|
|
|
|
|
|
|
self.print('Task skipped (another identical task is already pending for execution)\n') |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
pending_tasks[self.task_id] = self |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
task_management_mutex.release() |
|
|
|
|
|
|
|
Task.__update_pending_tasks() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_alive(self): |
|
|
|
|
|
|
|
task_management_mutex.acquire() |
|
|
|
|
|
|
|
alive = self.task_id in pending_tasks |
|
|
|
|
|
|
|
if self.__thread is not None: |
|
|
|
|
|
|
|
alive = self.__thread.is_alive() |
|
|
|
|
|
|
|
task_management_mutex.release() |
|
|
|
|
|
|
|
return alive |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def join(self): |
|
|
|
|
|
|
|
task_management_mutex.acquire() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while self.__thread is None and self.is_alive(): |
|
|
|
|
|
|
|
task_management_mutex.release() |
|
|
|
|
|
|
|
time.sleep(1) |
|
|
|
|
|
|
|
task_management_mutex.acquire() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if self.__thread is not None: |
|
|
|
|
|
|
|
task_management_mutex.release() |
|
|
|
|
|
|
|
self.__thread.join() |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
task_management_mutex.release() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __start_thread(self): |
|
|
|
|
|
|
|
print('Starting task ' + self.task_id) |
|
|
|
|
|
|
|
task_management_mutex.acquire() |
|
|
|
|
|
|
|
assert(self.__thread is None) |
|
|
|
|
|
|
|
self.__thread = Thread(target=self.__run) |
|
|
|
|
|
|
|
task_management_mutex.release() |
|
|
|
|
|
|
|
self.__thread.start() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __run(self): |
|
|
|
|
|
|
|
self.run() |
|
|
|
|
|
|
|
print('Task ' + self.task_id + ' has finished') |
|
|
|
|
|
|
|
self.__finished_at = datetime.now() |
|
|
|
|
|
|
|
Task.__free_resources(self) |
|
|
|
|
|
|
|
Task.__update_pending_tasks() |
|
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
@staticmethod |
|
|
|
def get(task_id): |
|
|
|
def get(task_id): |
|
|
|
return tasks[task_id] |
|
|
|
task_management_mutex.acquire() |
|
|
|
|
|
|
|
result = tasks[task_id] |
|
|
|
|
|
|
|
task_management_mutex.release() |
|
|
|
|
|
|
|
return result |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
|
|
|
def __update_pending_tasks(): |
|
|
|
|
|
|
|
task_management_mutex.acquire() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Clear finished tasks |
|
|
|
|
|
|
|
now = datetime.now() |
|
|
|
|
|
|
|
to_remove = [] |
|
|
|
|
|
|
|
for key, task in tasks.items(): |
|
|
|
|
|
|
|
if task.__finished_at is not None and (now - task.__finished_at).total_seconds() > 60: |
|
|
|
|
|
|
|
to_remove.append(key) |
|
|
|
|
|
|
|
for key in to_remove: |
|
|
|
|
|
|
|
print('Dropping task ' + key) |
|
|
|
|
|
|
|
tasks.pop(key) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for key, task in pending_tasks.items(): |
|
|
|
|
|
|
|
can_start = True |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
all_wait_causes = '' |
|
|
|
|
|
|
|
has_same_wait_cause = False |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resource_mutex.acquire() |
|
|
|
|
|
|
|
for required_resource in task.__resource_usage: |
|
|
|
|
|
|
|
resource = Resource.get(required_resource[0]) |
|
|
|
|
|
|
|
if resource.get_available_amount(required_resource[1]) < 1: |
|
|
|
|
|
|
|
can_start = False |
|
|
|
|
|
|
|
wait_cause = required_resource[0] + (' ('+required_resource[1]+')' if required_resource[1] != '' else '') |
|
|
|
|
|
|
|
if wait_cause == task.__has_logged_waiting: |
|
|
|
|
|
|
|
has_same_wait_cause = True |
|
|
|
|
|
|
|
all_wait_causes = all_wait_causes + (', ' if all_wait_causes != '' else '') + wait_cause |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not can_start: |
|
|
|
|
|
|
|
if not has_same_wait_cause: |
|
|
|
|
|
|
|
task.__has_logged_waiting = wait_cause |
|
|
|
|
|
|
|
task.print('Waiting for resource(s): '+all_wait_causes+'\n') |
|
|
|
|
|
|
|
resource_mutex.release() |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for required_resource in task.__resource_usage: |
|
|
|
|
|
|
|
resource = Resource.get(required_resource[0]) |
|
|
|
|
|
|
|
resource.acquire(required_resource[1], 1) |
|
|
|
|
|
|
|
resource_mutex.release() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pending_tasks.pop(key) |
|
|
|
|
|
|
|
task.__start_thread() |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
task_management_mutex.release() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
|
|
|
def __free_resources(task): |
|
|
|
|
|
|
|
task_management_mutex.acquire() |
|
|
|
|
|
|
|
resource_mutex.acquire() |
|
|
|
|
|
|
|
for required_resource in task.__resource_usage: |
|
|
|
|
|
|
|
resource = Resource.get(required_resource[0]) |
|
|
|
|
|
|
|
resource.release(required_resource[1], 1) |
|
|
|
|
|
|
|
resource_mutex.release() |
|
|
|
|
|
|
|
task_management_mutex.release() |
|
|
|
|
|
|
|
|
|
|
|
class ProcessTask(Task): |
|
|
|
class ProcessTask(Task): |
|
|
|
def __init__(self, commands, cwd = None, hide_passwords = []): |
|
|
|
def __init__(self, commands, cwd = None, hide_passwords = [], resource_usage = []): |
|
|
|
Task.__init__(self) |
|
|
|
resource_usage = list(resource_usage) |
|
|
|
|
|
|
|
resource_usage.append(('__process', '')) |
|
|
|
|
|
|
|
Task.__init__(self, resource_usage) |
|
|
|
self.__commands = commands |
|
|
|
self.__commands = commands |
|
|
|
self.__cwd = cwd |
|
|
|
self.__cwd = cwd |
|
|
|
self.__hide_passwords = hide_passwords |
|
|
|
self.__hide_passwords = hide_passwords |
|
|
|