88import traceback
99import threading
1010import queue
11+ import multiprocessing
1112import importlib
1213import time
1314from 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
1931def _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
63141def 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