From e6d0f6ceb6d31a4a0be544126984ea067faedd55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Pinz=C3=B3n?= Date: Tue, 23 Jun 2020 03:47:17 -0500 Subject: [PATCH] v1.0.1 process interrupt working fast and nicely --- _cpcp/downloader.py | 2 +- _cpcp/utils/_process.py | 65 ++++++++++++++++++++++++++++------------- cpcp.py | 24 +++++++-------- cpcp/cache/version.json | 5 +++- 4 files changed, 59 insertions(+), 37 deletions(-) diff --git a/_cpcp/downloader.py b/_cpcp/downloader.py index 00f2b57..78d9858 100644 --- a/_cpcp/downloader.py +++ b/_cpcp/downloader.py @@ -20,7 +20,7 @@ def download(self, UI, problem, platform): tool = platform.name else: tool = 'fallback' - UI.print(f'\nUsing {tool} downloader...') + UI.print(f'\nUsing {tool} downloader... (ctrl+c to interrupt)') UI.print('-'*15+'\n') plugin = self.plugins[tool] try: diff --git a/_cpcp/utils/_process.py b/_cpcp/utils/_process.py index 7c54c52..3e4432c 100644 --- a/_cpcp/utils/_process.py +++ b/_cpcp/utils/_process.py @@ -6,6 +6,7 @@ from threading import Thread, Timer from queue import Queue, Empty import sys, time, io, asyncio, threading +from ._interrupt import terminate_thread ON_POSIX = 'posix' in sys.builtin_module_names @@ -74,6 +75,8 @@ def run(self, input=None, timeout=None, check=False, kwargs.pop('self') self.kwargs.update(kwargs) self._start() + interrupt = Thread(target=self._kill, + args=['KeyboardInterrupt']) try: #self._sync.wait() was here before but #the loop is needed for quick handling of @@ -81,15 +84,18 @@ def run(self, input=None, timeout=None, check=False, while not self._sync.is_set(): time.sleep(1e-4) except KeyboardInterrupt: - self._stop = True - self._process.kill() - self._error = 'KeyboardInterrupt' + interrupt.start() except Exception as e: self._error = str(e) - self._sync.wait() - if check and self.error: - kw = dict(returncode=self.returncode, cmd=self.kwargs.args) - raise CalledProcessError(**kw) + while 1: + try: + self._sync.wait() + if check and self.error: + kw = dict(returncode=self.returncode, cmd=self.kwargs.args) + raise CalledProcessError(**kw) + break + except KeyboardInterrupt: + pass return self async def async_run(self, input=None, timeout=None, check=False, @@ -139,7 +145,8 @@ def _start(self): self._threads['waiter'] = Thread(target=self._waiter) if self._timeout: - self._threads['timer'] = Timer(self._timeout, self._terminate) + self._threads['timer'] = Timer(self._timeout, + self._kill, args=['TimeoutExpired']) config = { 'out': { @@ -211,6 +218,14 @@ def _start(self): t.start() return + def _silent_interrupt(func): + def wrapper(self, *args, **kwargs): + try: func(self, *args, **kwargs) + except KeyboardInterrupt: pass + return + wrapper.__name__=func.__name__ + return wrapper + def _waiter(self): try: while not self._stop: @@ -249,13 +264,28 @@ def get_value(buffer): if self._async: self._async.set() return - def _terminate(self): - self._threads['timer'].cancel() - self._process.kill() - self._stop = True - self.error = 'TimeoutExpired' + def kill(self): + self._kill('KilledByUser') + while not self._done: + time.sleep(1e-3) return + def _kill(self, error): + if self.is_active(): + self._error = error + if 'timer' in self._threads: + self._threads['timer'].cancel() + self._stop = True + self._process.kill() + for k,t in self._threads.items(): + if k!='waiter' and k!='timer': + terminate_thread(t, KeyboardInterrupt) + for k,t in self._threads.items(): + if k!='waiter' and k!='timer': + t.join() + + + @_silent_interrupt def _non_blocking_reader(self, istream, queues, max_size): #https://stackoverflow.com/a/4896288/3671939 for line in iter(istream.readline, b''): @@ -268,6 +298,7 @@ def _non_blocking_reader(self, istream, queues, max_size): for q in queues: q.put(line) return istream.close() + @_silent_interrupt def _live_handler(self, queue, ostream, flush, wait): waiting = False waiting_start = None @@ -301,14 +332,6 @@ def _parse_time(self, x, ifNone): def is_active(self): return self._done==False - def kill(self): - if self.is_active(): - self._stop = True - self._error = 'KilledByUser' - self._process.kill() - while not self._done: - time.sleep(1e-3) - class CustomOStream(io.BufferedWriter): def __init__(self, write_function, flush_function=None): diff --git a/cpcp.py b/cpcp.py index b05f698..d6f72bb 100644 --- a/cpcp.py +++ b/cpcp.py @@ -52,10 +52,6 @@ def main(self, UI): self.UI = UI self.lib = ProblemLibrary() self.UI.print(f'\nRoot: {os.getcwd()}') - t = Thread(target=version_main, args=[ - self.UI, META]) - t.setDaemon(True) - t.start() self.change_problem(escape=False) self.limits = Dict(time_limit=10, @@ -231,13 +227,13 @@ def f(shared, self, args, kwargs): t = Thread(target=f, args=(shared,self,args,kwargs)) self.UI._interrupt.clear() t.start() - while not shared['done'] and not self.UI._interrupt.is_set(): + while not shared['done']: time.sleep(1e-3) - if not shared['done']: - self.UI.print('Terminating...') - terminate_thread(t, KeyboardInterrupt) - t.join() - self.UI.print('Terinated') + if self.UI._interrupt.is_set(): + self.UI.print('Keyboard interruption...') + terminate_thread(t, KeyboardInterrupt) + self.UI._interrupt.clear() + t.join() return shared['ret'] wrapper.__name__=func.__name__ return wrapper @@ -422,12 +418,9 @@ def xdg_open(self, path, touch=False): return exists - def main(): - global META - META = Dict() - META.version = 'v0.0.7' + META.version = 'v1.1.0' META.source = os.path.realpath(__file__) META.srcdir = os.path.dirname(os.path.realpath(__file__)) META.exetag = f'CPCP-{platform.system()}-{platform.machine()}' @@ -464,6 +457,9 @@ def main(): t = Thread(target=cpcp.main, args=[UI]) t.setDaemon(True) t.start() + t = Thread(target=version_main, args=[UI, META]) + t.setDaemon(True) + t.start() flx.run() sys.exit(0) return diff --git a/cpcp/cache/version.json b/cpcp/cache/version.json index 9e26dfe..3342391 100644 --- a/cpcp/cache/version.json +++ b/cpcp/cache/version.json @@ -1 +1,4 @@ -{} \ No newline at end of file +{ + "v0.0.7": "/home/carlos/Documents/cpcp/CPCP", + "v1.0.0": "/home/carlos/Documents/cpcp/CPCP" +} \ No newline at end of file