Skip to content

Commit

Permalink
Work around account switching failing to open the CEF debugger socket (
Browse files Browse the repository at this point in the history
…#668)

* Work around account switching failing to open the CEF debugger socket

this automates lsof and gdb to force close the socket before steam finishes shutting down (from RegisterForShutdownStart)

* lint

* fix LD_LIBRARY_PATH for gdb
  • Loading branch information
AAGaming00 authored Aug 7, 2024
1 parent ddc8073 commit 166c7ea
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 7 deletions.
3 changes: 3 additions & 0 deletions backend/decky_loader/localplatform/localplatform.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ def get_live_reload() -> bool:
def get_keep_systemd_service() -> bool:
return os.getenv("KEEP_SYSTEMD_SERVICE", "0") == "1"

def get_use_cef_close_workaround() -> bool:
return ON_LINUX and os.getenv("USE_CEF_CLOSE_WORKAROUND", "1") == "1"

def get_log_level() -> int:
return {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[
os.getenv("LOG_LEVEL", "INFO")
Expand Down
38 changes: 38 additions & 0 deletions backend/decky_loader/localplatform/localplatformlinux.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from re import compile
from asyncio import Lock
import os, pwd, grp, sys, logging
from subprocess import call, run, DEVNULL, PIPE, STDOUT
from ..enums import UserType
Expand Down Expand Up @@ -227,3 +229,39 @@ def get_unprivileged_user() -> str:
user = 'deck'

return user

# Works around the CEF debugger TCP socket not closing properly when Steam restarts
# Group 1 is PID, group 2 is FD. this also filters for "steamwebhelper" in the process name.
cef_socket_lsof_regex = compile(r"^p(\d+)(?:\s|.)+csteamwebhelper(?:\s|.)+f(\d+)(?:\s|.)+TST=LISTEN")
close_cef_socket_lock = Lock()

async def close_cef_socket():
async with close_cef_socket_lock:
if _get_effective_user_id() != 0:
logger.warn("Can't close CEF socket as Decky isn't running as root.")
return
# Look for anything listening TCP on port 8080
lsof = run(["lsof", "-F", "-iTCP:8080", "-sTCP:LISTEN"], capture_output=True, text=True)
if lsof.returncode != 0 or len(lsof.stdout) < 1:
logger.error(f"lsof call failed in close_cef_socket! return code: {str(lsof.returncode)}")
return

lsof_data = cef_socket_lsof_regex.match(lsof.stdout)

if not lsof_data:
logger.error("lsof regex match failed in close_cef_socket!")
return

pid = lsof_data.group(1)
fd = lsof_data.group(2)

logger.info(f"Closing CEF socket with PID {pid} and FD {fd}")

# Use gdb to inject a close() call for the socket fd into steamwebhelper
gdb_ret = run(["gdb", "--nx", "-p", pid, "--batch", "--eval-command", f"call (int)close({fd})"], env={"LD_LIBRARY_PATH": ""})

if gdb_ret.returncode != 0:
logger.error(f"Failed to close CEF socket with gdb! return code: {str(gdb_ret.returncode)}", exc_info=True)
return

logger.info("CEF socket closed")
5 changes: 4 additions & 1 deletion backend/decky_loader/localplatform/localplatformwin.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,7 @@ def get_unprivileged_user() -> str:
return os.getenv("UNPRIVILEGED_USER", os.getlogin())

async def restart_webhelper() -> bool:
return True # Stubbed
return True # Stubbed

async def close_cef_socket():
return # Stubbed
8 changes: 6 additions & 2 deletions backend/decky_loader/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
if TYPE_CHECKING:
from .main import PluginManager
from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab
from .localplatform.localplatform import ON_WINDOWS
from . import helpers
from .localplatform.localplatform import service_stop, service_start, get_home_path, get_username
from .localplatform.localplatform import ON_WINDOWS, service_stop, service_start, get_home_path, get_username, get_use_cef_close_workaround, close_cef_socket

class FilePickerObj(TypedDict):
file: Path
Expand Down Expand Up @@ -78,6 +77,7 @@ def __init__(self, context: PluginManager) -> None:
context.ws.add_route("utilities/get_tab_id", self.get_tab_id)
context.ws.add_route("utilities/get_user_info", self.get_user_info)
context.ws.add_route("utilities/http_request", self.http_request_legacy)
context.ws.add_route("utilities/close_cef_socket", self.close_cef_socket)
context.ws.add_route("utilities/_call_legacy_utility", self._call_legacy_utility)

context.web_app.add_routes([
Expand Down Expand Up @@ -287,6 +287,10 @@ async def stop_ssh(self):
await service_stop(helpers.SSHD_UNIT)
return True

async def close_cef_socket(self):
if get_use_cef_close_workaround():
await close_cef_socket()

async def filepicker_ls(self,
path: str | None = None,
include_files: bool = True,
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/steamfixes/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// import reloadFix from './reload';
import restartFix from './restart';
// import restartFix from './restart';
import cefSocketFix from './socket';

let fixes: Function[] = [];

export function deinitSteamFixes() {
fixes.forEach((deinit) => deinit());
}

export async function initSteamFixes() {
// fixes.push(await reloadFix());
fixes.push(await restartFix());
fixes.push(cefSocketFix());
// fixes.push(await restartFix());
}
16 changes: 16 additions & 0 deletions frontend/src/steamfixes/socket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Logger from '../logger';

const logger = new Logger('CEFSocketFix');

const closeCEFSocket = DeckyBackend.callable<[], void>('utilities/close_cef_socket');

export default function cefSocketFix() {
const reg = window.SteamClient?.User?.RegisterForShutdownStart(async () => {
logger.log('Closing CEF socket before shutdown');
await closeCEFSocket();
});

if (reg) logger.debug('CEF shutdown handler ready');

return () => reg?.unregister();
}

0 comments on commit 166c7ea

Please sign in to comment.