Skip to content

Commit 3930b21

Browse files
Rework manager based on PR comments
Do not instantiate manager in the file anymore. This will be done during extension loading Rework monitoring for proxy apps natively using asyncio. This will be added as callback to ServerApp IO loop during extension loading Manager spits out logs in debug mode when there is proxy app is added/removed from it. Added uni_socket to server proxy app dataclass Remove unnecessary async methods Properly handle the get_server_proxy_{app,proc} methods when server proxy is not found in the manager. We return a default ServerProxyApp tuple in this case with empty values
1 parent 7513419 commit 3930b21

File tree

3 files changed

+79
-55
lines changed

3 files changed

+79
-55
lines changed

jupyter_server_proxy/config.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
from importlib.metadata import entry_points
1212

1313
from jupyter_server.utils import url_path_join as ujoin
14-
from traitlets import Dict, List, Tuple, Union, default, observe
14+
from traitlets import Dict, List, Tuple, Union, Int, default, observe
1515
from traitlets.config import Configurable
1616

17+
from jupyter_server.utils import url_path_join as ujoin
18+
1719
from .handlers import AddSlashHandler, NamedLocalProxyHandler, SuperviseAndProxyHandler
1820

1921
try:
@@ -102,15 +104,15 @@ def get_entrypoint_server_processes(serverproxy_config):
102104
return sps
103105

104106

105-
def make_handlers(base_url, server_processes):
107+
def make_handlers(base_url, manager, server_processes):
106108
"""
107109
Get tornado handlers for registered server_processes
108110
"""
109111
handlers = []
110112
for sp in server_processes:
111113
if sp.command:
112114
handler = _make_supervisedproxy_handler(sp)
113-
kwargs = dict(state={})
115+
kwargs = dict(state={}, manager=manager)
114116
else:
115117
if not (sp.port or isinstance(sp.unix_socket, str)):
116118
warn(
@@ -341,3 +343,13 @@ def _host_whitelist_deprecated(self, change):
341343
)
342344
)
343345
self.host_allowlist = change.new
346+
347+
monitor_interval = Int(
348+
10,
349+
help="""
350+
Proxy polling interval in seconds. The server proxy manager will keep
351+
polling the status of running servers with a frequency set by this
352+
interval.
353+
""",
354+
config=True
355+
)

jupyter_server_proxy/manager.py

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,57 @@
11
"""Manager for jupyter server proxy"""
22

3-
from collections import namedtuple
4-
5-
from jupyter_server.utils import url_path_join as ujoin
6-
from tornado.ioloop import PeriodicCallback
7-
8-
from .utils import check_pid
9-
10-
ServerProxy = namedtuple("ServerProxy", ["name", "url", "cmd", "port", "managed"])
11-
ServerProxyProc = namedtuple("ServerProxyProc", ["name", "proc"])
3+
import asyncio
124

5+
from collections import namedtuple
136

14-
async def monitor_server_proxy_procs():
15-
"""Perodically monitor the server proxy processes. If user terminates
16-
the process outside of jupyter-server-proxy, we should be able to
17-
capture that and remove proxy app from manager"""
18-
# Get current active apps
19-
procs = manager._list_server_proxy_procs()
7+
from traitlets import List, Int
8+
from traitlets.config import LoggingConfigurable
209

21-
# Check if all pids are alive
22-
for proc in procs:
23-
exists = check_pid(proc.proc.pid)
24-
if not exists:
25-
await manager.del_server_proxy_app(proc.name)
10+
from jupyter_server.utils import url_path_join as ujoin
2611

2712

28-
class ServerProxyAppManager:
13+
ServerProxy = namedtuple(
14+
"ServerProxy",
15+
[
16+
"name",
17+
"url",
18+
"cmd",
19+
"port",
20+
"managed",
21+
"unix_socket"
22+
],
23+
defaults=[""] * 6
24+
)
25+
ServerProxyProc = namedtuple(
26+
"ServerProxyProc",
27+
[
28+
"name",
29+
"proc"
30+
],
31+
defaults=[""] * 2
32+
)
33+
34+
35+
class ServerProxyAppManager(LoggingConfigurable):
2936
"""
3037
A class for listing and stopping server proxies that are started
3138
by jupyter server proxy.
3239
"""
3340

34-
def __init__(self):
35-
"""Initialize the server proxy manager"""
36-
# List of server proxy apps
37-
self.server_proxy_apps = []
41+
server_proxy_apps = List(
42+
help="List of server proxy apps"
43+
)
3844

39-
# List of server proxy app proc objects. For internal use only
40-
self._server_proxy_procs = []
45+
_server_proxy_procs = List(
46+
help="List of server proxy app proc objects"
47+
)
4148

42-
# Total number of currently running proxy apps
43-
self.num_active_server_proxy_apps = 0
49+
num_active_server_proxy_apps = Int(
50+
0,
51+
help="Total number of currently running proxy apps"
52+
)
4453

45-
async def add_server_proxy_app(self, name, base_url, cmd, port, proc):
54+
def add_server_proxy_app(self, name, base_url, cmd, port, proc, unix_socket):
4655
"""Add a launched proxy server to list"""
4756
self.num_active_server_proxy_apps += 1
4857

@@ -54,6 +63,7 @@ async def add_server_proxy_app(self, name, base_url, cmd, port, proc):
5463
cmd=" ".join(cmd),
5564
port=port,
5665
managed=True if proc else False,
66+
unix_socket=unix_socket if unix_socket is not None else ''
5767
)
5868
)
5969

@@ -65,24 +75,25 @@ async def add_server_proxy_app(self, name, base_url, cmd, port, proc):
6575
proc=proc,
6676
)
6777
)
78+
self.log.debug("Server proxy %s added to server proxy manager" % name)
6879

69-
async def del_server_proxy_app(self, name):
80+
def del_server_proxy_app(self, name):
7081
"""Remove a launched proxy server from list"""
7182
self.server_proxy_apps = [
7283
app for app in self.server_proxy_apps if app.name != name
7384
]
7485
self._server_proxy_procs = [
7586
app for app in self._server_proxy_procs if app.name != name
7687
]
77-
self.num_active_server_proxy_apps -= 1
88+
self.num_active_server_proxy_apps = len(self.server_proxy_apps)
7889

7990
def get_server_proxy_app(self, name):
8091
"""Get a given server proxy app"""
81-
return next((app for app in self.server_proxy_apps if app.name == name), {})
92+
return next((app for app in self.server_proxy_apps if app.name == name), ServerProxy())
8293

8394
def _get_server_proxy_proc(self, name):
8495
"""Get a given server proxy app"""
85-
return next((app for app in self._server_proxy_procs if app.name == name), {})
96+
return next((app for app in self._server_proxy_procs if app.name == name), ServerProxyProc())
8697

8798
def list_server_proxy_apps(self):
8899
"""List all active server proxy apps"""
@@ -107,21 +118,32 @@ async def terminate_server_proxy_app(self, name):
107118
await app.proc.terminate()
108119

109120
# Remove proxy app from list
110-
await self.del_server_proxy_app(name)
121+
self.del_server_proxy_app(name)
122+
123+
self.log.debug("Server proxy %s removed from server proxy manager" % name)
111124

112125
return True
113126
except (KeyError, AttributeError):
127+
self.log.warning("Server proxy %s not found in server proxy manager" % name)
114128
return None
115129

116130
async def terminate_all(self):
117131
"""Close all server proxy and cleanup"""
118132
for app in self.server_proxy_apps:
119133
await self.terminate_server_proxy_app(app)
120134

121-
122-
# Create a default manager to keep track of server proxy apps.
123-
manager = ServerProxyAppManager()
124-
125-
# Create a Periodic call back function to check the status of processes
126-
pc = PeriodicCallback(monitor_server_proxy_procs, 1e4)
127-
pc.start()
135+
async def monitor(self, monitor_interval):
136+
while True:
137+
procs = self._list_server_proxy_procs()
138+
139+
# Check if processes are running
140+
for proc in procs:
141+
running = proc.proc.running
142+
if not running:
143+
self.log.warning(
144+
"Server proxy %s is not running anymore. "
145+
"Removing from server proxy manager" % proc.name
146+
)
147+
self.del_server_proxy_app(proc.name)
148+
149+
await asyncio.sleep(monitor_interval)

jupyter_server_proxy/utils.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,3 @@ def validate(self, obj, value):
5151
return value
5252
else:
5353
self.error(obj, value)
54-
55-
56-
def check_pid(pid):
57-
"""Check For the existence of a unix pid"""
58-
try:
59-
os.kill(pid, 0)
60-
except OSError:
61-
return False
62-
else:
63-
return True

0 commit comments

Comments
 (0)