1 Commits

Author SHA1 Message Date
CakeCN
496c5be90a code-server-like ssh clash 2025-12-13 23:50:45 +08:00
3 changed files with 97 additions and 40 deletions

File diff suppressed because one or more lines are too long

View File

@@ -39,35 +39,56 @@ def read_and_forward_pty_output(fd=None, pid=None, room_id=None,namespace=None):
""" """
max_read_bytes = 1024 * 20 max_read_bytes = 1024 * 20
timeout=0.1 timeout=0.1
while True: try:
socketio.sleep(timeout) while True:
timeout=min(timeout*2, 0.4) socketio.sleep(timeout)
# using flask default web server, or uwsgi production web server timeout=min(timeout*2, 0.4)
# when the child process is terminated, it will not disappear from linux process list # using flask default web server, or uwsgi production web server
# and keep staying as a zombie process until the parent exits. # when the child process is terminated, it will not disappear from linux process list
try: # and keep staying as a zombie process until the parent exits.
child_process = psutil.Process(pid) try:
except psutil.NoSuchProcess as err: child_process = psutil.Process(pid)
return except psutil.NoSuchProcess as err:
if child_process.status() not in ('running', 'sleeping'): # Process already terminated, clean up any zombie
return
if fd:
timeout_sec = 0
(data_ready, _, _) = select.select([fd], [], [], timeout_sec)
if data_ready:
# output = os.read(fd, max_read_bytes).decode('ascii')
timeout=0.1
try: try:
output = os.read(fd, max_read_bytes).decode() os.waitpid(pid, os.WNOHANG)
except Exception as err: except Exception:
output = """ pass
***AQUI WEB TERM ERR*** return
{} if child_process.status() not in ('running', 'sleeping'):
*********************** # Process is terminated or in other state, clean up
""".format(err) try:
# the key for different visitor to get different terminal (instead of mixing up) child_process.wait(timeout=1)
# is to let the background task push pty response to each one's own (default) ROOM! except Exception:
namespace.emit("pty_output", {"output": output}, room=room_id) try:
os.waitpid(pid, os.WNOHANG)
except Exception:
pass
return
if fd:
timeout_sec = 0
(data_ready, _, _) = select.select([fd], [], [], timeout_sec)
if data_ready:
# output = os.read(fd, max_read_bytes).decode('ascii')
timeout=0.1
try:
output = os.read(fd, max_read_bytes).decode()
except Exception as err:
output = """
***AQUI WEB TERM ERR***
{}
***********************
""".format(err)
# the key for different visitor to get different terminal (instead of mixing up)
# is to let the background task push pty response to each one's own (default) ROOM!
namespace.emit("pty_output", {"output": output}, room=room_id)
finally:
# Clean up file descriptor if it's open
if fd:
try:
os.close(fd)
except Exception:
pass
class VSCLikeNameSpace(Namespace): class VSCLikeNameSpace(Namespace):
def on_connect(self): def on_connect(self):
@@ -156,15 +177,40 @@ class VSCLikeNameSpace(Namespace):
set_winsize(fd, data["rows"], data["cols"]) set_winsize(fd, data["rows"], data["cols"])
def on_disconnect(self): def on_disconnect(self):
try: child_pid = session.get('terminal_config', {}).get('child_pid')
child_process = psutil.Process(session.get('terminal_config', {}).get('child_pid')) if child_pid:
except psutil.NoSuchProcess as err: try:
disconnect() child_process = psutil.Process(child_pid)
session['terminal_config'] = TERM_INIT_CONFIG if child_process.status() in ('running', 'sleeping'):
return # if visitor just close the browser tab then left alone the pty here
if child_process.status() in ('running', 'sleeping'): # it should be terminated by the parent process after
# if visitor just close the browser tab then left alone the pty here child_process.terminate()
# it should be terminated by the parent process after # Wait for the process to terminate and collect its exit status
child_process.terminate() child_process.wait(timeout=2)
current_app.logger.debug('user left the pty alone, terminated') current_app.logger.debug('user left the pty alone, terminated and waited')
except psutil.NoSuchProcess as err:
# Process already terminated, try to wait anyway to clean up any zombie
try:
os.waitpid(child_pid, os.WNOHANG)
except Exception:
pass
except psutil.TimeoutExpired:
# If process didn't terminate in time, kill it forcefully
child_process.kill()
try:
child_process.wait(timeout=1)
except Exception:
pass
except Exception as err:
current_app.logger.error(f'Error terminating process: {err}')
finally:
# Close the file descriptor if it's open
fd = session.get('terminal_config', {}).get('fd')
if fd:
try:
os.close(fd)
except Exception:
pass
# Reset session config
session['terminal_config'] = TERM_INIT_CONFIG
current_app.logger.debug('Client disconnected') current_app.logger.debug('Client disconnected')

View File

@@ -61,7 +61,7 @@
</div> </div>
<div id="notificationsContainer"></div> <div id="notificationsContainer"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.js"></script> <script src="static/cdnback/js/monaco-editor/0.34.0/min/vs/loader.js"></script>
<script src="/vsc-like/static/js/code-like-extension.js"></script> <script src="/vsc-like/static/js/code-like-extension.js"></script>
<script src="/vsc-like/static/js/file.js"></script> <script src="/vsc-like/static/js/file.js"></script>
<script src="/vsc-like/static/js/terminal.js"></script> <script src="/vsc-like/static/js/terminal.js"></script>