wandb 0.19.7__py3-none-musllinux_1_2_aarch64.whl → 0.19.8__py3-none-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. wandb/__init__.py +1 -1
  2. wandb/__init__.pyi +32 -2
  3. wandb/bin/gpu_stats +0 -0
  4. wandb/bin/wandb-core +0 -0
  5. wandb/data_types.py +1 -1
  6. wandb/filesync/dir_watcher.py +2 -1
  7. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  8. wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
  9. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  10. wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
  11. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  12. wandb/proto/v5/wandb_telemetry_pb2.py +10 -10
  13. wandb/sdk/artifacts/artifact.py +11 -10
  14. wandb/sdk/backend/backend.py +16 -5
  15. wandb/sdk/interface/interface.py +65 -43
  16. wandb/sdk/interface/interface_queue.py +0 -7
  17. wandb/sdk/interface/interface_relay.py +6 -16
  18. wandb/sdk/interface/interface_shared.py +47 -40
  19. wandb/sdk/interface/interface_sock.py +1 -8
  20. wandb/sdk/interface/router.py +22 -54
  21. wandb/sdk/interface/router_queue.py +11 -10
  22. wandb/sdk/interface/router_relay.py +24 -12
  23. wandb/sdk/interface/router_sock.py +6 -11
  24. wandb/sdk/internal/sender.py +3 -1
  25. wandb/sdk/lib/console_capture.py +172 -0
  26. wandb/sdk/lib/redirect.py +102 -76
  27. wandb/sdk/lib/service_connection.py +37 -17
  28. wandb/sdk/lib/sock_client.py +2 -52
  29. wandb/sdk/mailbox/__init__.py +3 -3
  30. wandb/sdk/mailbox/mailbox.py +31 -17
  31. wandb/sdk/mailbox/mailbox_handle.py +127 -0
  32. wandb/sdk/mailbox/{handles.py → response_handle.py} +34 -66
  33. wandb/sdk/mailbox/wait_with_progress.py +16 -15
  34. wandb/sdk/service/server_sock.py +4 -2
  35. wandb/sdk/service/streams.py +10 -5
  36. wandb/sdk/wandb_init.py +12 -15
  37. wandb/sdk/wandb_run.py +8 -10
  38. wandb/sdk/wandb_settings.py +7 -1
  39. wandb/sdk/wandb_sync.py +1 -7
  40. {wandb-0.19.7.dist-info → wandb-0.19.8.dist-info}/METADATA +1 -1
  41. {wandb-0.19.7.dist-info → wandb-0.19.8.dist-info}/RECORD +44 -44
  42. wandb/sdk/interface/message_future.py +0 -27
  43. wandb/sdk/interface/message_future_poll.py +0 -50
  44. {wandb-0.19.7.dist-info → wandb-0.19.8.dist-info}/WHEEL +0 -0
  45. {wandb-0.19.7.dist-info → wandb-0.19.8.dist-info}/entry_points.txt +0 -0
  46. {wandb-0.19.7.dist-info → wandb-0.19.8.dist-info}/licenses/LICENSE +0 -0
@@ -6,8 +6,10 @@ import string
6
6
  import threading
7
7
 
8
8
  from wandb.proto import wandb_internal_pb2 as pb
9
+ from wandb.proto import wandb_server_pb2 as spb
9
10
 
10
- from . import handles
11
+ from .mailbox_handle import MailboxHandle
12
+ from .response_handle import MailboxResponseHandle
11
13
 
12
14
  _logger = logging.getLogger(__name__)
13
15
 
@@ -19,22 +21,25 @@ class MailboxClosedError(Exception):
19
21
  class Mailbox:
20
22
  """Matches service responses to requests.
21
23
 
22
- The mailbox can set an address on a Record and create a handle for
24
+ The mailbox can set an address on a server request and create a handle for
23
25
  waiting for a response to that record. Responses are delivered by calling
24
26
  `deliver()`. The `close()` method abandons all handles in case the
25
27
  service process becomes unreachable.
26
28
  """
27
29
 
28
30
  def __init__(self) -> None:
29
- self._handles: dict[str, handles.MailboxHandle] = {}
31
+ self._handles: dict[str, MailboxResponseHandle] = {}
30
32
  self._handles_lock = threading.Lock()
31
33
  self._closed = False
32
34
 
33
- def require_response(self, request: pb.Record) -> handles.MailboxHandle:
35
+ def require_response(
36
+ self,
37
+ request: spb.ServerRequest | pb.Record,
38
+ ) -> MailboxHandle[spb.ServerResponse]:
34
39
  """Set a response address on a request.
35
40
 
36
41
  Args:
37
- request: The request on which to set a mailbox slot.
42
+ request: The request on which to set a request ID or mailbox slot.
38
43
  This is mutated. An address must not already be set.
39
44
 
40
45
  Returns:
@@ -45,17 +50,24 @@ class Mailbox:
45
50
  no new responses are expected to be delivered and new handles
46
51
  cannot be created.
47
52
  """
48
- if address := request.control.mailbox_slot:
49
- raise ValueError(f"Request already has an address ({address})")
53
+ if isinstance(request, spb.ServerRequest):
54
+ if address := request.request_id:
55
+ raise ValueError(f"Request already has an address ({address})")
50
56
 
51
- address = self._new_address()
52
- request.control.mailbox_slot = address
57
+ address = self._new_address()
58
+ request.request_id = address
59
+ else:
60
+ if address := request.control.mailbox_slot:
61
+ raise ValueError(f"Request already has an address ({address})")
62
+
63
+ address = self._new_address()
64
+ request.control.mailbox_slot = address
53
65
 
54
66
  with self._handles_lock:
55
67
  if self._closed:
56
68
  raise MailboxClosedError()
57
69
 
58
- handle = handles.MailboxHandle(address)
70
+ handle = MailboxResponseHandle(address)
59
71
  self._handles[address] = handle
60
72
 
61
73
  return handle
@@ -80,18 +92,20 @@ class Mailbox:
80
92
 
81
93
  return address
82
94
 
83
- def deliver(self, result: pb.Result) -> None:
95
+ def deliver(self, response: spb.ServerResponse) -> None:
84
96
  """Deliver a response from the service.
85
97
 
86
98
  If the response address is invalid, this does nothing.
87
99
  It is a no-op if the mailbox has been closed.
88
100
  """
89
- address = result.control.mailbox_slot
101
+ address = response.request_id
90
102
  if not address:
91
- _logger.error(
92
- "Received response with no mailbox slot."
93
- f" Kind: {result.WhichOneof('result_type')}"
94
- )
103
+ kind: str | None = response.WhichOneof("server_response_type")
104
+ if kind == "result_communicate":
105
+ result_type = response.result_communicate.WhichOneof("result_type")
106
+ kind = f"result_communicate.{result_type}"
107
+
108
+ _logger.error(f"Received response with no mailbox slot: {kind}")
95
109
  return
96
110
 
97
111
  with self._handles_lock:
@@ -102,7 +116,7 @@ class Mailbox:
102
116
  # It is not an error if there is no handle for the address:
103
117
  # handles can be abandoned if the result is no longer needed.
104
118
  if handle:
105
- handle.deliver(result)
119
+ handle.deliver(response)
106
120
 
107
121
  def close(self) -> None:
108
122
  """Indicate no further responses will be delivered.
@@ -0,0 +1,127 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ import sys
5
+ from typing import TYPE_CHECKING, Callable, Generic, TypeVar
6
+
7
+ # Necessary to break an import loop.
8
+ if TYPE_CHECKING:
9
+ from wandb.sdk.interface import interface
10
+
11
+ if sys.version_info >= (3, 12):
12
+ from typing import override
13
+ else:
14
+ from typing_extensions import override
15
+
16
+
17
+ _T = TypeVar("_T")
18
+ _S = TypeVar("_S")
19
+
20
+
21
+ class HandleAbandonedError(Exception):
22
+ """The handle has no response and has been abandoned."""
23
+
24
+
25
+ class MailboxHandle(abc.ABC, Generic[_T]):
26
+ """A thread-safe handle that allows waiting for a response to a request."""
27
+
28
+ def map(self, fn: Callable[[_T], _S]) -> MailboxHandle[_S]:
29
+ """Returns a transformed handle.
30
+
31
+ Methods on the returned handle call methods on this handle, but the
32
+ response type is derived using the given function.
33
+
34
+ Args:
35
+ fn: A function to apply to this handle's result to get the new
36
+ handle's result. The function should be pure and fast.
37
+ """
38
+ return _MailboxMappedHandle(self, fn)
39
+
40
+ @abc.abstractmethod
41
+ def abandon(self) -> None:
42
+ """Abandon the handle, indicating it will not receive a response."""
43
+
44
+ @abc.abstractmethod
45
+ def cancel(self, iface: interface.InterfaceBase) -> None:
46
+ """Cancel the handle, requesting any associated work to not complete.
47
+
48
+ This automatically abandons the handle, as a response is no longer
49
+ guaranteed.
50
+
51
+ Args:
52
+ iface: The interface on which to publish the cancel request.
53
+ """
54
+
55
+ @abc.abstractmethod
56
+ def check(self) -> _T | None:
57
+ """Returns the response if it's ready."""
58
+
59
+ @abc.abstractmethod
60
+ def wait_or(self, *, timeout: float | None) -> _T:
61
+ """Wait for a response or a timeout.
62
+
63
+ Args:
64
+ timeout: A finite number of seconds or None to never time out.
65
+ If less than or equal to zero, times out immediately unless
66
+ the response is available.
67
+
68
+ Returns:
69
+ The response if it arrives before the timeout or has already arrived.
70
+
71
+ Raises:
72
+ TimeoutError: If the timeout is reached.
73
+ HandleAbandonedError: If the handle becomes abandoned.
74
+ """
75
+
76
+ @abc.abstractmethod
77
+ async def wait_async(self, *, timeout: float | None) -> _T:
78
+ """Wait for a response or timeout.
79
+
80
+ This must run in an `asyncio` event loop.
81
+
82
+ Args:
83
+ timeout: A finite number of seconds or None to never time out.
84
+
85
+ Returns:
86
+ The response if it arrives before the timeout or has already arrived.
87
+
88
+ Raises:
89
+ TimeoutError: If the timeout is reached.
90
+ HandleAbandonedError: If the handle becomes abandoned.
91
+ """
92
+
93
+
94
+ class _MailboxMappedHandle(Generic[_S], MailboxHandle[_S]):
95
+ """A mailbox handle whose result is derived from another handle."""
96
+
97
+ def __init__(
98
+ self,
99
+ handle: MailboxHandle[_T],
100
+ fn: Callable[[_T], _S],
101
+ ) -> None:
102
+ self._handle = handle
103
+ self._fn = fn
104
+
105
+ @override
106
+ def abandon(self) -> None:
107
+ self._handle.abandon()
108
+
109
+ @override
110
+ def cancel(self, iface: interface.InterfaceBase) -> None:
111
+ self._handle.cancel(iface)
112
+
113
+ @override
114
+ def check(self) -> _S | None:
115
+ if response := self._handle.check():
116
+ return self._fn(response)
117
+ else:
118
+ return None
119
+
120
+ @override
121
+ def wait_or(self, *, timeout: float | None) -> _S:
122
+ return self._fn(self._handle.wait_or(timeout=timeout))
123
+
124
+ @override
125
+ async def wait_async(self, *, timeout: float | None) -> _S:
126
+ response = await self._handle.wait_async(timeout=timeout)
127
+ return self._fn(response)
@@ -2,22 +2,26 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import math
5
+ import sys
5
6
  import threading
6
7
  from typing import TYPE_CHECKING
7
8
 
8
- from wandb.proto import wandb_internal_pb2 as pb
9
+ from wandb.proto import wandb_server_pb2 as spb
10
+
11
+ from .mailbox_handle import HandleAbandonedError, MailboxHandle
9
12
 
10
13
  # Necessary to break an import loop.
11
14
  if TYPE_CHECKING:
12
15
  from wandb.sdk.interface import interface
13
16
 
14
-
15
- class HandleAbandonedError(Exception):
16
- """The handle has no result and has been abandoned."""
17
+ if sys.version_info >= (3, 12):
18
+ from typing import override
19
+ else:
20
+ from typing_extensions import override
17
21
 
18
22
 
19
- class MailboxHandle:
20
- """A thread-safe handle that allows waiting for a response to a request."""
23
+ class MailboxResponseHandle(MailboxHandle[spb.ServerResponse]):
24
+ """A general handle for any ServerResponse."""
21
25
 
22
26
  def __init__(self, address: str) -> None:
23
27
  self._address = address
@@ -25,11 +29,11 @@ class MailboxHandle:
25
29
  self._event = threading.Event()
26
30
 
27
31
  self._abandoned = False
28
- self._result: pb.Result | None = None
32
+ self._response: spb.ServerResponse | None = None
29
33
 
30
34
  self._asyncio_events: dict[asyncio.Event, _AsyncioEvent] = dict()
31
35
 
32
- def deliver(self, result: pb.Result) -> None:
36
+ def deliver(self, response: spb.ServerResponse) -> None:
33
37
  """Deliver the response.
34
38
 
35
39
  This may only be called once. It is an error to respond to the same
@@ -39,34 +43,27 @@ class MailboxHandle:
39
43
  if self._abandoned:
40
44
  return
41
45
 
42
- if self._result:
46
+ if self._response:
43
47
  raise ValueError(
44
48
  f"A response has already been delivered to {self._address}."
45
49
  )
46
50
 
47
- self._result = result
51
+ self._response = response
48
52
  self._signal_done()
49
53
 
54
+ @override
50
55
  def cancel(self, iface: interface.InterfaceBase) -> None:
51
- """Cancel the handle, requesting any associated work to not complete.
52
-
53
- This automatically abandons the handle, as a response is no longer
54
- guaranteed.
55
-
56
- Args:
57
- interface: The interface on which to publish the cancel request.
58
- """
59
56
  iface.publish_cancel(self._address)
60
57
  self.abandon()
61
58
 
59
+ @override
62
60
  def abandon(self) -> None:
63
- """Abandon the handle, indicating it will not receive a response."""
64
61
  with self._lock:
65
62
  self._abandoned = True
66
63
  self._signal_done()
67
64
 
68
65
  def _signal_done(self) -> None:
69
- """Indicate that the handle either got a result or became abandoned.
66
+ """Indicate that the handle either got a response or became abandoned.
70
67
 
71
68
  The lock must be held.
72
69
  """
@@ -78,29 +75,13 @@ class MailboxHandle:
78
75
  asyncio_event.set_threadsafe()
79
76
  self._asyncio_events.clear()
80
77
 
81
- def check(self) -> pb.Result | None:
82
- """Returns the result if it's ready."""
78
+ @override
79
+ def check(self) -> spb.ServerResponse | None:
83
80
  with self._lock:
84
- return self._result
85
-
86
- def wait_or(self, *, timeout: float | None) -> pb.Result:
87
- """Wait for a response or a timeout.
88
-
89
- This is called `wait_or` because it replaces a method called `wait`
90
- with different semantics.
91
-
92
- Args:
93
- timeout: A finite number of seconds or None to never time out.
94
- If less than or equal to zero, times out immediately unless
95
- the result is available.
81
+ return self._response
96
82
 
97
- Returns:
98
- The result if it arrives before the timeout or has already arrived.
99
-
100
- Raises:
101
- TimeoutError: If the timeout is reached.
102
- HandleAbandonedError: If the handle becomes abandoned.
103
- """
83
+ @override
84
+ def wait_or(self, *, timeout: float | None) -> spb.ServerResponse:
104
85
  if timeout is not None and not math.isfinite(timeout):
105
86
  raise ValueError("Timeout must be finite or None.")
106
87
 
@@ -110,27 +91,14 @@ class MailboxHandle:
110
91
  )
111
92
 
112
93
  with self._lock:
113
- if self._result:
114
- return self._result
94
+ if self._response:
95
+ return self._response
115
96
 
116
97
  assert self._abandoned
117
98
  raise HandleAbandonedError()
118
99
 
119
- async def wait_async(self, *, timeout: float | None) -> pb.Result:
120
- """Wait for a response or timeout.
121
-
122
- This must run in an `asyncio` event loop.
123
-
124
- Args:
125
- timeout: A finite number of seconds or None to never time out.
126
-
127
- Returns:
128
- The result if it arrives before the timeout or has already arrived.
129
-
130
- Raises:
131
- TimeoutError: If the timeout is reached.
132
- HandleAbandonedError: If the handle becomes abandoned.
133
- """
100
+ @override
101
+ async def wait_async(self, *, timeout: float | None) -> spb.ServerResponse:
134
102
  if timeout is not None and not math.isfinite(timeout):
135
103
  raise ValueError("Timeout must be finite or None.")
136
104
 
@@ -142,8 +110,8 @@ class MailboxHandle:
142
110
 
143
111
  except (asyncio.TimeoutError, TimeoutError) as e:
144
112
  with self._lock:
145
- if self._result:
146
- return self._result
113
+ if self._response:
114
+ return self._response
147
115
  elif self._abandoned:
148
116
  raise HandleAbandonedError()
149
117
  else:
@@ -153,8 +121,8 @@ class MailboxHandle:
153
121
 
154
122
  else:
155
123
  with self._lock:
156
- if self._result:
157
- return self._result
124
+ if self._response:
125
+ return self._response
158
126
 
159
127
  assert self._abandoned
160
128
  raise HandleAbandonedError()
@@ -167,20 +135,20 @@ class MailboxHandle:
167
135
  loop: asyncio.AbstractEventLoop,
168
136
  event: asyncio.Event,
169
137
  ) -> None:
170
- """Add an event to signal when a result is received.
138
+ """Add an event to signal when a response is received.
171
139
 
172
- If a result already exists, this notifies the event loop immediately.
140
+ If a response already exists, this notifies the event loop immediately.
173
141
  """
174
142
  asyncio_event = _AsyncioEvent(loop, event)
175
143
 
176
144
  with self._lock:
177
- if self._result or self._abandoned:
145
+ if self._response or self._abandoned:
178
146
  asyncio_event.set_threadsafe()
179
147
  else:
180
148
  self._asyncio_events[event] = asyncio_event
181
149
 
182
150
  def _forget_asyncio_event(self, event: asyncio.Event) -> None:
183
- """Cancel signalling an event when a result is received."""
151
+ """Cancel signalling an event when a response is received."""
184
152
  with self._lock:
185
153
  self._asyncio_events.pop(event, None)
186
154
 
@@ -1,21 +1,22 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import time
4
- from typing import Any, Callable, Coroutine, List, cast
4
+ from typing import Any, Callable, Coroutine, List, TypeVar, cast
5
5
 
6
- from wandb.proto import wandb_internal_pb2 as pb
7
6
  from wandb.sdk.lib import asyncio_compat
8
7
 
9
- from . import handles
8
+ from .mailbox_handle import MailboxHandle
9
+
10
+ _T = TypeVar("_T")
10
11
 
11
12
 
12
13
  def wait_with_progress(
13
- handle: handles.MailboxHandle,
14
+ handle: MailboxHandle[_T],
14
15
  *,
15
16
  timeout: float | None,
16
17
  progress_after: float,
17
18
  display_progress: Callable[[], Coroutine[Any, Any, None]],
18
- ) -> pb.Result:
19
+ ) -> _T:
19
20
  """Wait for a handle, possibly displaying progress to the user.
20
21
 
21
22
  Equivalent to passing a single handle to `wait_all_with_progress`.
@@ -29,12 +30,12 @@ def wait_with_progress(
29
30
 
30
31
 
31
32
  def wait_all_with_progress(
32
- handle_list: list[handles.MailboxHandle],
33
+ handle_list: list[MailboxHandle[_T]],
33
34
  *,
34
35
  timeout: float | None,
35
36
  progress_after: float,
36
37
  display_progress: Callable[[], Coroutine[Any, Any, None]],
37
- ) -> list[pb.Result]:
38
+ ) -> list[_T]:
38
39
  """Wait for multiple handles, possibly displaying progress to the user.
39
40
 
40
41
  Args:
@@ -70,7 +71,7 @@ def wait_all_with_progress(
70
71
  except TimeoutError:
71
72
  pass
72
73
 
73
- async def progress_loop_with_timeout() -> list[pb.Result]:
74
+ async def progress_loop_with_timeout() -> list[_T]:
74
75
  with asyncio_compat.cancel_on_exit(display_progress()):
75
76
  if timeout is not None:
76
77
  elapsed_time = time.monotonic() - start_time
@@ -87,10 +88,10 @@ def wait_all_with_progress(
87
88
 
88
89
 
89
90
  def _wait_handles(
90
- handle_list: list[handles.MailboxHandle],
91
+ handle_list: list[MailboxHandle[_T]],
91
92
  *,
92
93
  timeout: float,
93
- ) -> list[pb.Result]:
94
+ ) -> list[_T]:
94
95
  """Wait for multiple mailbox handles.
95
96
 
96
97
  Returns:
@@ -100,7 +101,7 @@ def _wait_handles(
100
101
  TimeoutError: If the overall timeout expires.
101
102
  HandleAbandonedError: If any handle becomes abandoned.
102
103
  """
103
- results: list[pb.Result] = []
104
+ results: list[_T] = []
104
105
 
105
106
  start_time = time.monotonic()
106
107
  for handle in handle_list:
@@ -112,15 +113,15 @@ def _wait_handles(
112
113
 
113
114
 
114
115
  async def _wait_handles_async(
115
- handle_list: list[handles.MailboxHandle],
116
+ handle_list: list[MailboxHandle[_T]],
116
117
  *,
117
118
  timeout: float | None,
118
- ) -> list[pb.Result]:
119
+ ) -> list[_T]:
119
120
  """Asynchronously wait for multiple mailbox handles.
120
121
 
121
122
  Just like _wait_handles.
122
123
  """
123
- results: list[pb.Result | None] = [None for _ in handle_list]
124
+ results: list[_T | None] = [None for _ in handle_list]
124
125
 
125
126
  async def wait_single(index: int) -> None:
126
127
  handle = handle_list[index]
@@ -131,4 +132,4 @@ async def _wait_handles_async(
131
132
  task_group.start_soon(wait_single(index))
132
133
 
133
134
  # NOTE: `list` is not subscriptable until Python 3.10, so we use List.
134
- return cast(List[pb.Result], results)
135
+ return cast(List[_T], results)
@@ -44,7 +44,10 @@ class SockServerInterfaceReaderThread(threading.Thread):
44
44
  _stopped: "Event"
45
45
 
46
46
  def __init__(
47
- self, clients: ClientDict, iface: "InterfaceRelay", stopped: "Event"
47
+ self,
48
+ clients: ClientDict,
49
+ iface: "InterfaceRelay",
50
+ stopped: "Event",
48
51
  ) -> None:
49
52
  self._iface = iface
50
53
  self._clients = clients
@@ -53,7 +56,6 @@ class SockServerInterfaceReaderThread(threading.Thread):
53
56
  self._stopped = stopped
54
57
 
55
58
  def run(self) -> None:
56
- assert self._iface.relay_q
57
59
  while not self._stopped.is_set():
58
60
  try:
59
61
  result = self._iface.relay_q.get(timeout=1)
@@ -21,16 +21,14 @@ import psutil
21
21
  import wandb
22
22
  import wandb.util
23
23
  from wandb.proto import wandb_internal_pb2 as pb
24
+ from wandb.sdk.interface.interface_relay import InterfaceRelay
25
+ from wandb.sdk.interface.router_relay import MessageRelayRouter
24
26
  from wandb.sdk.internal.settings_static import SettingsStatic
25
27
  from wandb.sdk.lib import asyncio_compat, progress
26
28
  from wandb.sdk.lib import printer as printerlib
27
29
  from wandb.sdk.mailbox import Mailbox, MailboxHandle, wait_all_with_progress
28
30
  from wandb.sdk.wandb_run import Run
29
31
 
30
- from ..interface.interface_relay import InterfaceRelay
31
-
32
- # from wandb.sdk.wandb_settings import Settings
33
-
34
32
 
35
33
  class StreamThread(threading.Thread):
36
34
  """Class to running internal process as a thread."""
@@ -62,6 +60,12 @@ class StreamRecord:
62
60
  self._record_q = queue.Queue()
63
61
  self._result_q = queue.Queue()
64
62
  self._relay_q = queue.Queue()
63
+ self._router = MessageRelayRouter(
64
+ request_queue=self._record_q,
65
+ response_queue=self._result_q,
66
+ relay_queue=self._relay_q,
67
+ mailbox=self._mailbox,
68
+ )
65
69
  self._iface = InterfaceRelay(
66
70
  record_q=self._record_q,
67
71
  result_q=self._result_q,
@@ -80,6 +84,7 @@ class StreamRecord:
80
84
 
81
85
  def join(self) -> None:
82
86
  self._iface.join()
87
+ self._router.join()
83
88
  if self._thread:
84
89
  self._thread.join()
85
90
 
@@ -290,7 +295,7 @@ class StreamMux:
290
295
 
291
296
  # fixme: for now we have a single printer for all streams,
292
297
  # and jupyter is disabled if at least single stream's setting set `_jupyter` to false
293
- exit_handles: list[MailboxHandle] = []
298
+ exit_handles: list[MailboxHandle[pb.Result]] = []
294
299
 
295
300
  # only finish started streams, non started streams failed early
296
301
  started_streams: dict[str, StreamRecord] = {}
wandb/sdk/wandb_init.py CHANGED
@@ -42,7 +42,7 @@ from . import wandb_login, wandb_setup
42
42
  from .backend.backend import Backend
43
43
  from .lib import SummaryDisabled, filesystem, module, paths, printer, telemetry
44
44
  from .lib.deprecate import Deprecated, deprecate
45
- from .mailbox import Mailbox, wait_with_progress
45
+ from .mailbox import wait_with_progress
46
46
  from .wandb_helper import parse_config
47
47
  from .wandb_run import Run, TeardownHook, TeardownStage
48
48
  from .wandb_settings import Settings
@@ -682,11 +682,11 @@ class _WandbInit:
682
682
  drun._Run__metadata = wandb.sdk.wandb_metadata.Metadata()
683
683
 
684
684
  # methods
685
- drun.log = lambda data, *_, **__: drun.summary.update(data) # type: ignore
686
- drun.finish = lambda *_, **__: module.unset_globals() # type: ignore
687
- drun.join = drun.finish # type: ignore
688
- drun.define_metric = lambda *_, **__: wandb.sdk.wandb_metric.Metric("dummy") # type: ignore
689
- drun.save = lambda *_, **__: False # type: ignore
685
+ drun.log = lambda data, *_, **__: drun.summary.update(data) # type: ignore[method-assign]
686
+ drun.finish = lambda *_, **__: module.unset_globals() # type: ignore[method-assign]
687
+ drun.join = drun.finish # type: ignore[method-assign]
688
+ drun.define_metric = lambda *_, **__: wandb.sdk.wandb_metric.Metric("dummy") # type: ignore[method-assign]
689
+ drun.save = lambda *_, **__: False # type: ignore[method-assign]
690
690
  for symbol in (
691
691
  "alert",
692
692
  "finish_artifact",
@@ -733,7 +733,7 @@ class _WandbInit:
733
733
  def __call__(self, *args: Any, **kwargs: Any) -> _ChainableNoOp:
734
734
  return _ChainableNoOp()
735
735
 
736
- drun.log_artifact = _ChainableNoOpField()
736
+ drun.log_artifact = _ChainableNoOpField() # type: ignore[method-assign]
737
737
  # attributes
738
738
  drun._start_time = time.time()
739
739
  drun._starting_step = 0
@@ -793,12 +793,7 @@ class _WandbInit:
793
793
  else:
794
794
  service = None
795
795
 
796
- mailbox = Mailbox()
797
- backend = Backend(
798
- settings=settings,
799
- service=service,
800
- mailbox=mailbox,
801
- )
796
+ backend = Backend(settings=settings, service=service)
802
797
  backend.ensure_launched()
803
798
  self._logger.info("backend started and connected")
804
799
 
@@ -1085,8 +1080,7 @@ def _attach(
1085
1080
  )
1086
1081
 
1087
1082
  # TODO: consolidate this codepath with wandb.init()
1088
- mailbox = Mailbox()
1089
- backend = Backend(settings=settings, service=service, mailbox=mailbox)
1083
+ backend = Backend(settings=settings, service=service)
1090
1084
  backend.ensure_launched()
1091
1085
  logger.info("attach backend started and connected")
1092
1086
 
@@ -1468,6 +1462,9 @@ def init( # noqa: C901
1468
1462
  _monkeypatch_tensorboard()
1469
1463
  init_telemetry.feature.tensorboard_sync = True
1470
1464
 
1465
+ if run_settings.x_server_side_derived_summary:
1466
+ init_telemetry.feature.server_side_derived_summary = True
1467
+
1471
1468
  return wi.init(run_settings, run_config)
1472
1469
 
1473
1470
  except KeyboardInterrupt as e: