Skip to content

Commit b49b7a6

Browse files
authored
Merge pull request #1 from Shuuida/correcciones
Correcciones para detector de errores tipográficos en los nodos, botón y función nueva en la terminal de SIGKILL (Ctrl+C), etc.
2 parents 1cad805 + e0c8e95 commit b49b7a6

3 files changed

Lines changed: 349 additions & 89 deletions

File tree

core/executor.py

Lines changed: 135 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,43 @@
88
import traceback
99
import threading
1010
import queue
11+
import multiprocessing
1112
import importlib
1213
import time
1314
from typing import Dict, Any
1415

15-
input_queue = queue.Queue()
16-
gui_queue = queue.Queue()
17-
is_waiting_for_user = False
16+
# Use a Manager so queues and shared state can be passed across the worker process.
17+
if multiprocessing.current_process().name == 'MainProcess':
18+
_multiprocessing_manager = multiprocessing.Manager()
19+
input_queue = _multiprocessing_manager.Queue()
20+
gui_queue = _multiprocessing_manager.Queue()
21+
wait_flag = _multiprocessing_manager.Value('b', False)
22+
else:
23+
_multiprocessing_manager = None
24+
input_queue = None
25+
gui_queue = None
26+
wait_flag = None
27+
28+
current_process = None
29+
current_process_lock = threading.Lock()
1830

1931
def _global_log_worker():
2032
import gevent
2133
while True:
22-
while not gui_queue.empty():
23-
try:
24-
msg = gui_queue.get_nowait()
25-
if msg == "__TRIGGER_INPUT__":
26-
eel.trigger_frontend_input()()
27-
else:
28-
eel.api_realtime_log(msg)()
29-
except Exception:
30-
pass
34+
if gui_queue is not None:
35+
while not gui_queue.empty():
36+
try:
37+
msg = gui_queue.get_nowait()
38+
if msg == "__TRIGGER_INPUT__":
39+
eel.trigger_frontend_input()()
40+
else:
41+
eel.api_realtime_log(msg)()
42+
except Exception:
43+
pass
3144
gevent.sleep(0.05)
3245

33-
eel.spawn(_global_log_worker)
46+
if multiprocessing.current_process().name == 'MainProcess':
47+
eel.spawn(_global_log_worker)
3448

3549
# -------------------------------------
3650
# CONFIGURACIÓN DE SEGURIDAD
@@ -58,81 +72,119 @@ def sanitize_code(code: str) -> str:
5872
code = code.replace(kw, f"# BLOCKED: {kw}")
5973
return code
6074

75+
def _execution_target(result_queue, input_q, gui_q, wait_flag_obj, code):
76+
try:
77+
safe_code = sanitize_code(code)
78+
79+
def custom_print(*args, sep=' ', end='\n', file=None, flush=False):
80+
text = sep.join(map(str, args))
81+
gui_q.put(str(text))
82+
83+
def interactive_input(prompt=""):
84+
if prompt:
85+
gui_q.put(str(prompt))
86+
gui_q.put("__TRIGGER_INPUT__")
87+
wait_flag_obj.value = True
88+
user_response = input_q.get()
89+
wait_flag_obj.value = False
90+
gui_q.put(f"❯ {user_response}")
91+
return str(user_response)
92+
93+
safe_builtins_map = {
94+
'print': custom_print,
95+
'input': interactive_input,
96+
'range': range, 'len': len, 'int': int, 'float': float, 'str': str,
97+
'list': list, 'dict': dict, 'set': set, 'tuple': tuple, 'bool': bool,
98+
'abs': abs, 'min': min, 'max': max, 'sum': sum, 'round': round,
99+
'zip': zip, 'map': map, 'filter': filter, 'sorted': sorted, 'enumerate': enumerate,
100+
'Exception': Exception, 'ValueError': ValueError, 'TypeError': TypeError,
101+
'__build_class__': __build_class__,
102+
'object': object, 'super': super, 'classmethod': classmethod,
103+
'staticmethod': staticmethod, 'property': property, 'type': type,
104+
'isinstance': isinstance, '__import__': safe_import
105+
}
106+
107+
env = {
108+
'__builtins__': safe_builtins_map,
109+
'__name__': '__main__'
110+
}
111+
112+
exec(safe_code, env)
113+
result_queue.put({'success': True, 'output': '', 'error': ''})
114+
115+
except SyntaxError as se:
116+
result_queue.put({'success': False, 'output': '', 'error': f"Error de Sintaxis: {se}"})
117+
except ImportError as ie:
118+
result_queue.put({'success': False, 'output': '', 'error': f"Error de Importación: {ie}"})
119+
except Exception:
120+
result_queue.put({'success': False, 'output': '', 'error': traceback.format_exc()})
121+
122+
123+
def kill_execution() -> bool:
124+
global current_process
125+
if current_process is None:
126+
return False
127+
128+
with current_process_lock:
129+
if current_process is not None and current_process.is_alive():
130+
current_process.terminate()
131+
current_process.join(timeout=1)
132+
if wait_flag is not None:
133+
wait_flag.value = False
134+
current_process = None
135+
return True
136+
137+
return False
138+
61139
# ---------------------------------------------
62140
# EJECUTOR PRINCIPAL
63141
def execute_user_code(code: str, timeout: int = 5) -> Dict[str, Any]:
64-
global is_waiting_for_user
142+
global current_process
65143
result = {'success': False, 'output': '', 'error': ''}
66-
67-
# Limpiar entradas zombis
68-
while not input_queue.empty(): input_queue.get_nowait()
69-
70-
def target(q):
71-
global is_waiting_for_user
144+
145+
if input_queue is None or gui_queue is None or wait_flag is None or _multiprocessing_manager is None:
146+
return {'success': False, 'output': '', 'error': 'El entorno de ejecución no está inicializado correctamente.'}
147+
148+
if current_process is not None and current_process.is_alive():
149+
return {'success': False, 'output': '', 'error': 'Ejecutor ocupado. Espere a que termine la ejecución actual.'}
150+
151+
while not input_queue.empty():
72152
try:
73-
safe_code = sanitize_code(code)
74-
75-
def custom_print(*args, sep=' ', end='\n', file=None, flush=False):
76-
text = sep.join(map(str, args))
77-
gui_queue.put(str(text))
78-
79-
def interactive_input(prompt=""):
80-
global is_waiting_for_user
81-
if prompt: gui_queue.put(str(prompt))
82-
gui_queue.put("__TRIGGER_INPUT__")
83-
is_waiting_for_user = True
84-
user_response = input_queue.get()
85-
is_waiting_for_user = False
86-
gui_queue.put(f"❯ {user_response}")
87-
return str(user_response)
88-
89-
safe_builtins_map = {
90-
'print': custom_print,
91-
'input': interactive_input,
92-
'range': range, 'len': len, 'int': int, 'float': float, 'str': str,
93-
'list': list, 'dict': dict, 'set': set, 'tuple': tuple, 'bool': bool,
94-
'abs': abs, 'min': min, 'max': max, 'sum': sum, 'round': round,
95-
'zip': zip, 'map': map, 'filter': filter, 'sorted': sorted, 'enumerate': enumerate,
96-
'Exception': Exception, 'ValueError': ValueError, 'TypeError': TypeError,
97-
'__build_class__': __build_class__,
98-
'object': object, 'super': super, 'classmethod': classmethod,
99-
'staticmethod': staticmethod, 'property': property, 'type': type,
100-
'isinstance': isinstance, '__import__': safe_import
101-
}
102-
103-
# __name__ se pasa a nivel de globals para que no haya falsos saltos
104-
env = {
105-
'__builtins__': safe_builtins_map,
106-
'__name__': '__main__'
107-
}
108-
109-
exec(safe_code, env)
110-
q.put({'success': True, 'output': '', 'error': ''})
111-
112-
except SyntaxError as se:
113-
q.put({'success': False, 'output': '', 'error': f"Error de Sintaxis: {se}"})
114-
except ImportError as ie:
115-
q.put({'success': False, 'output': '', 'error': f"Error de Importación: {ie}"})
116-
except Exception:
117-
q.put({'success': False, 'output': '', 'error': traceback.format_exc()})
118-
119-
q = queue.Queue()
120-
thread = threading.Thread(target=target, args=(q,))
121-
thread.daemon = True
122-
thread.start()
123-
153+
input_queue.get_nowait()
154+
except queue.Empty:
155+
break
156+
157+
result_queue = _multiprocessing_manager.Queue()
158+
process = multiprocessing.Process(
159+
target=_execution_target,
160+
args=(result_queue, input_queue, gui_queue, wait_flag, code)
161+
)
162+
163+
with current_process_lock:
164+
current_process = process
165+
process.start()
166+
124167
time_elapsed = 0.0
125-
while thread.is_alive() and time_elapsed < timeout:
126-
eel.sleep(0.05)
127-
if not is_waiting_for_user:
168+
while process.is_alive() and time_elapsed < timeout:
169+
eel.sleep(0.05)
170+
if not wait_flag.value:
128171
time_elapsed += 0.05
129-
130-
if thread.is_alive():
131-
result['error'] = "Tiempo excedido (Timeout). ¿El hilo principal se colgó?"
132-
else:
133-
if not q.empty():
134-
result = q.get()
135-
else:
136-
result['error'] = "Error desconocido de procesamiento."
137-
172+
173+
if process.is_alive():
174+
process.terminate()
175+
process.join(timeout=1)
176+
with current_process_lock:
177+
current_process = None
178+
wait_flag.value = False
179+
result['error'] = 'Tiempo excedido (Timeout). ¿El proceso se colgó?'
180+
return result
181+
182+
with current_process_lock:
183+
current_process = None
184+
185+
try:
186+
result = result_queue.get_nowait()
187+
except queue.Empty:
188+
result = {'success': False, 'output': '', 'error': 'Error desconocido de procesamiento.'}
189+
138190
return result

0 commit comments

Comments
 (0)