-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy_runtime.py
More file actions
108 lines (96 loc) · 3.45 KB
/
Copy pathproxy_runtime.py
File metadata and controls
108 lines (96 loc) · 3.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
"""Thread-owned asyncio runtime for the local HTTP proxy."""
import asyncio
import logging
import threading
from proxy import run_proxy
logger = logging.getLogger("magic-proxy.runtime")
class ProxyRuntime:
"""Own one generation of loop/task/thread and stop it before replacement."""
def __init__(self, stats):
self._stats = stats
self._lock = threading.Lock()
self._generation = 0
self._thread = None
self._loop = None
self._task = None
self._stop_event = None
self._error = ""
@property
def running(self):
with self._lock:
return bool(self._thread and self._thread.is_alive() and not self._stop_event.is_set())
@property
def error(self):
with self._lock:
return self._error
def start(self, config):
self.stop()
with self._lock:
self._generation += 1
generation = self._generation
stop_event = threading.Event()
self._stop_event = stop_event
self._error = ""
def worker():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
task = None
try:
if stop_event.is_set():
return
control = {}
task = loop.create_task(run_proxy(dict(config), self._stats, control))
with self._lock:
if generation != self._generation:
task.cancel()
else:
self._loop = loop
self._task = task
if stop_event.is_set():
task.cancel()
loop.run_until_complete(task)
except asyncio.CancelledError:
pass
except Exception as exc:
logger.exception("HTTP proxy generation %d stopped", generation)
with self._lock:
if generation == self._generation:
self._error = str(exc)
finally:
pending = asyncio.all_tasks(loop)
for pending_task in pending:
pending_task.cancel()
if pending:
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
loop.close()
with self._lock:
if generation == self._generation:
self._loop = None
self._task = None
self._thread = None
thread = threading.Thread(
target=worker, name=f"MagicProxyHTTP-{generation}", daemon=True,
)
with self._lock:
self._thread = thread
thread.start()
return True
def stop(self, timeout=5):
with self._lock:
thread = self._thread
loop = self._loop
task = self._task
stop_event = self._stop_event
if stop_event:
stop_event.set()
if loop and task and loop.is_running():
try:
loop.call_soon_threadsafe(task.cancel)
except RuntimeError:
pass
if thread and thread is not threading.current_thread():
thread.join(timeout=timeout)
alive = bool(thread and thread.is_alive())
if alive:
logger.error("HTTP proxy thread did not stop within %.1fs", timeout)
return not alive