Skip to content

Commit ed07766

Browse files
committed
Merge branch 'main' into cors_files
2 parents d75bf80 + f37967b commit ed07766

File tree

10 files changed

+363
-18
lines changed

10 files changed

+363
-18
lines changed

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ repos:
2121
- id: trailing-whitespace
2222

2323
- repo: https://github.com/python-jsonschema/check-jsonschema
24-
rev: 0.28.4
24+
rev: 0.28.6
2525
hooks:
2626
- id: check-github-workflows
2727

@@ -52,7 +52,7 @@ repos:
5252
- id: rst-inline-touching-normal
5353

5454
- repo: https://github.com/pre-commit/mirrors-mypy
55-
rev: "v1.10.0"
55+
rev: "v1.10.1"
5656
hooks:
5757
- id: mypy
5858
files: jupyter_server
@@ -61,7 +61,7 @@ repos:
6161
["traitlets>=5.13", "jupyter_core>=5.5", "jupyter_client>=8.5"]
6262

6363
- repo: https://github.com/astral-sh/ruff-pre-commit
64-
rev: v0.4.7
64+
rev: v0.5.0
6565
hooks:
6666
- id: ruff
6767
types_or: [python, jupyter]

CHANGELOG.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file.
44

55
<!-- <START NEW CHANGELOG ENTRY> -->
66

7+
## 2.14.2
8+
9+
([Full Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.14.1...b961d4eb499071c0c60e24f429c20d1e6a908a32))
10+
11+
### Bugs fixed
12+
13+
- Pass session_id during Websocket connect [#1440](https://github.com/jupyter-server/jupyter_server/pull/1440) ([@gogasca](https://github.com/gogasca))
14+
- Do not log environment variables passed to kernels [#1437](https://github.com/jupyter-server/jupyter_server/pull/1437) ([@krassowski](https://github.com/krassowski))
15+
16+
### Maintenance and upkeep improvements
17+
18+
- chore: update pre-commit hooks [#1441](https://github.com/jupyter-server/jupyter_server/pull/1441) ([@pre-commit-ci](https://github.com/pre-commit-ci))
19+
- chore: update pre-commit hooks [#1427](https://github.com/jupyter-server/jupyter_server/pull/1427) ([@pre-commit-ci](https://github.com/pre-commit-ci))
20+
21+
### Documentation improvements
22+
23+
- Update documentation for `cookie_secret` [#1433](https://github.com/jupyter-server/jupyter_server/pull/1433) ([@krassowski](https://github.com/krassowski))
24+
- Add Changelog for 2.14.1 [#1430](https://github.com/jupyter-server/jupyter_server/pull/1430) ([@blink1073](https://github.com/blink1073))
25+
- Update simple extension examples: \_jupyter_server_extension_points [#1426](https://github.com/jupyter-server/jupyter_server/pull/1426) ([@manics](https://github.com/manics))
26+
27+
### Contributors to this release
28+
29+
([GitHub contributors page for this release](https://github.com/jupyter-server/jupyter_server/graphs/contributors?from=2024-05-31&to=2024-07-12&type=c))
30+
31+
[@blink1073](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ablink1073+updated%3A2024-05-31..2024-07-12&type=Issues) | [@gogasca](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Agogasca+updated%3A2024-05-31..2024-07-12&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Akrassowski+updated%3A2024-05-31..2024-07-12&type=Issues) | [@manics](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Amanics+updated%3A2024-05-31..2024-07-12&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Apre-commit-ci+updated%3A2024-05-31..2024-07-12&type=Issues)
32+
33+
<!-- <END NEW CHANGELOG ENTRY> -->
34+
735
## 2.14.1
836

937
([Full Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.14.0...f1379164fa209bc4bfeadf43ab0e7f473b03a0ce))
@@ -27,8 +55,6 @@ All notable changes to this project will be documented in this file.
2755

2856
[@blink1073](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ablink1073+updated%3A2024-04-11..2024-05-31&type=Issues) | [@lresende](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Alresende+updated%3A2024-04-11..2024-05-31&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Apre-commit-ci+updated%3A2024-04-11..2024-05-31&type=Issues)
2957

30-
<!-- <END NEW CHANGELOG ENTRY> -->
31-
3258
## 2.14.0
3359

3460
([Full Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.13.0...074628806d6b2ec3304d60ab5cfba1c326f67730))

jupyter_server/gateway/connections.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ async def connect(self):
4747
url_escape(self.kernel_id),
4848
"channels",
4949
)
50+
if self.session_id:
51+
ws_url += f"?session_id={url_escape(self.session_id)}"
5052
self.log.info(f"Connecting to {ws_url}")
5153
kwargs: dict[str, Any] = {}
5254
kwargs = GatewayClient.instance().load_connection_args(**kwargs)

jupyter_server/serverapp.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,8 +1139,9 @@ def _default_cookie_secret_file(self) -> str:
11391139
b"",
11401140
config=True,
11411141
help="""The random bytes used to secure cookies.
1142-
By default this is a new random number every time you start the server.
1143-
Set it to a value in a config file to enable logins to persist across server sessions.
1142+
By default this is generated on first start of the server and persisted across server
1143+
sessions by writing the cookie secret into the `cookie_secret_file` file.
1144+
When using an executable config file you can override this to be random at each server restart.
11441145
11451146
Note: Cookie secrets should be kept private, do not share config files with
11461147
cookie_secret stored in plaintext (you can read the value from a file).

jupyter_server/services/contents/handlers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ async def get(self, path=""):
140140

141141
hash_str = self.get_query_argument("hash", default="0")
142142
if hash_str not in {"0", "1"}:
143-
raise web.HTTPError(400, f"Content {hash_str!r} is invalid")
143+
raise web.HTTPError(
144+
400, f"Hash argument {hash_str!r} is invalid. It must be '0' or '1'."
145+
)
144146
require_hash = int(hash_str)
145147

146148
if not cm.allow_hidden and await ensure_async(cm.is_hidden(path)):

jupyter_server/services/kernels/kernelmanager.py

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -232,18 +232,25 @@ async def _async_start_kernel( # type:ignore[override]
232232
kwargs["kernel_id"] = kernel_id
233233
kernel_id = await self.pinned_superclass._async_start_kernel(self, **kwargs)
234234
self._kernel_connections[kernel_id] = 0
235-
task = asyncio.create_task(self._finish_kernel_start(kernel_id))
236-
if not getattr(self, "use_pending_kernels", None):
237-
await task
238-
else:
239-
self._pending_kernel_tasks[kernel_id] = task
235+
240236
# add busy/activity markers:
241237
kernel = self.get_kernel(kernel_id)
242238
kernel.execution_state = "starting" # type:ignore[attr-defined]
243239
kernel.reason = "" # type:ignore[attr-defined]
244240
kernel.last_activity = utcnow() # type:ignore[attr-defined]
245241
self.log.info("Kernel started: %s", kernel_id)
246-
self.log.debug("Kernel args: %r", kwargs)
242+
self.log.debug(
243+
"Kernel args (excluding env): %r", {k: v for k, v in kwargs.items() if k != "env"}
244+
)
245+
env = kwargs.get("env", None)
246+
if env and isinstance(env, dict): # type:ignore[unreachable]
247+
self.log.debug("Kernel argument 'env' passed with: %r", list(env.keys())) # type:ignore[unreachable]
248+
249+
task = asyncio.create_task(self._finish_kernel_start(kernel_id))
250+
if not getattr(self, "use_pending_kernels", None):
251+
await task
252+
else:
253+
self._pending_kernel_tasks[kernel_id] = task
247254

248255
# Increase the metric of number of kernels running
249256
# for the relevant kernel type by 1
@@ -532,6 +539,40 @@ def _check_kernel_id(self, kernel_id):
532539
raise web.HTTPError(404, "Kernel does not exist: %s" % kernel_id)
533540

534541
# monitoring activity:
542+
untracked_message_types = List(
543+
trait=Unicode(),
544+
config=True,
545+
default_value=[
546+
"comm_info_request",
547+
"comm_info_reply",
548+
"kernel_info_request",
549+
"kernel_info_reply",
550+
"shutdown_request",
551+
"shutdown_reply",
552+
"interrupt_request",
553+
"interrupt_reply",
554+
"debug_request",
555+
"debug_reply",
556+
"stream",
557+
"display_data",
558+
"update_display_data",
559+
"execute_input",
560+
"execute_result",
561+
"error",
562+
"status",
563+
"clear_output",
564+
"debug_event",
565+
"input_request",
566+
"input_reply",
567+
],
568+
help="""List of kernel message types excluded from user activity tracking.
569+
570+
This should be a superset of the message types sent on any channel other
571+
than the shell channel.""",
572+
)
573+
574+
def track_message_type(self, message_type):
575+
return message_type not in self.untracked_message_types
535576

536577
def start_watching_activity(self, kernel_id):
537578
"""Start watching IOPub messages on a kernel for activity.
@@ -552,15 +593,27 @@ def start_watching_activity(self, kernel_id):
552593

553594
def record_activity(msg_list):
554595
"""Record an IOPub message arriving from a kernel"""
555-
self.last_kernel_activity = kernel.last_activity = utcnow()
556-
557596
idents, fed_msg_list = session.feed_identities(msg_list)
558597
msg = session.deserialize(fed_msg_list, content=False)
559598

560599
msg_type = msg["header"]["msg_type"]
600+
parent_msg_type = msg.get("parent_header", {}).get("msg_type", None)
601+
if (
602+
self.track_message_type(msg_type)
603+
or self.track_message_type(parent_msg_type)
604+
or kernel.execution_state == "busy"
605+
):
606+
self.last_kernel_activity = kernel.last_activity = utcnow()
561607
if msg_type == "status":
562608
msg = session.deserialize(fed_msg_list)
563-
kernel.execution_state = msg["content"]["execution_state"]
609+
execution_state = msg["content"]["execution_state"]
610+
if self.track_message_type(parent_msg_type):
611+
kernel.execution_state = execution_state
612+
elif kernel.execution_state == "starting" and execution_state != "starting":
613+
# We always normalize post-starting execution state to "idle"
614+
# unless we know that the status is in response to one of our
615+
# tracked message types.
616+
kernel.execution_state = "idle"
564617
self.log.debug(
565618
"activity on %s: %s (%s)",
566619
kernel_id,

tests/services/kernels/test_cull.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import asyncio
2+
import datetime
23
import json
34
import os
45
import platform
6+
import uuid
57
import warnings
68

79
import jupyter_client
@@ -94,6 +96,83 @@ async def test_cull_idle(jp_fetch, jp_ws_fetch):
9496
assert culled
9597

9698

99+
@pytest.mark.parametrize(
100+
"jp_server_config",
101+
[
102+
# Test the synchronous case
103+
Config(
104+
{
105+
"ServerApp": {
106+
"kernel_manager_class": "jupyter_server.services.kernels.kernelmanager.MappingKernelManager",
107+
"MappingKernelManager": {
108+
"cull_idle_timeout": CULL_TIMEOUT,
109+
"cull_interval": CULL_INTERVAL,
110+
"cull_connected": True,
111+
},
112+
}
113+
}
114+
),
115+
# Test the async case
116+
Config(
117+
{
118+
"ServerApp": {
119+
"kernel_manager_class": "jupyter_server.services.kernels.kernelmanager.AsyncMappingKernelManager",
120+
"AsyncMappingKernelManager": {
121+
"cull_idle_timeout": CULL_TIMEOUT,
122+
"cull_interval": CULL_INTERVAL,
123+
"cull_connected": True,
124+
},
125+
}
126+
}
127+
),
128+
],
129+
)
130+
async def test_cull_connected(jp_fetch, jp_ws_fetch):
131+
r = await jp_fetch("api", "kernels", method="POST", allow_nonstandard_methods=True)
132+
kernel = json.loads(r.body.decode())
133+
kid = kernel["id"]
134+
135+
# Open a websocket connection.
136+
ws = await jp_ws_fetch("api", "kernels", kid, "channels")
137+
session_id = uuid.uuid1().hex
138+
message_id = uuid.uuid1().hex
139+
await ws.write_message(
140+
json.dumps(
141+
{
142+
"channel": "shell",
143+
"header": {
144+
"date": datetime.datetime.now(tz=datetime.timezone.utc).isoformat(),
145+
"session": session_id,
146+
"msg_id": message_id,
147+
"msg_type": "execute_request",
148+
"username": "",
149+
"version": "5.2",
150+
},
151+
"parent_header": {},
152+
"metadata": {},
153+
"content": {
154+
"code": f"import time\ntime.sleep({CULL_TIMEOUT-1})",
155+
"silent": False,
156+
"allow_stdin": False,
157+
"stop_on_error": True,
158+
},
159+
"buffers": [],
160+
}
161+
)
162+
)
163+
164+
r = await jp_fetch("api", "kernels", kid, method="GET")
165+
model = json.loads(r.body.decode())
166+
assert model["connections"] == 1
167+
culled = await get_cull_status(
168+
kid, jp_fetch
169+
) # connected, but code cell still running. Should not be culled
170+
assert not culled
171+
culled = await get_cull_status(kid, jp_fetch) # still connected, but idle... should be culled
172+
assert culled
173+
ws.close()
174+
175+
97176
async def test_cull_idle_disable(jp_fetch, jp_ws_fetch, jp_kernelspec_with_metadata):
98177
r = await jp_fetch("api", "kernels", method="POST", allow_nonstandard_methods=True)
99178
kernel = json.loads(r.body.decode())

0 commit comments

Comments
 (0)