robotcode-jsonrpc2 2.4.0__tar.gz → 2.5.0__tar.gz

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.
@@ -334,4 +334,8 @@ bundled/libs
334
334
  results/
335
335
 
336
336
  # kilocode
337
- .kilocode/
337
+ .kilocode/
338
+
339
+ # .agents
340
+ .agents/
341
+ skills-lock.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotcode-jsonrpc2
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: JSONRPC Server for RobotCode
5
5
  Project-URL: Homepage, https://robotcode.io
6
6
  Project-URL: Donate, https://opencollective.com/robotcode
@@ -0,0 +1 @@
1
+ __version__ = "2.5.0"
@@ -1,8 +1,10 @@
1
1
  import abc
2
2
  import asyncio
3
3
  import io
4
+ import logging
4
5
  import sys
5
6
  import threading
7
+ import weakref
6
8
  from concurrent.futures import ThreadPoolExecutor
7
9
  from types import TracebackType
8
10
  from typing import (
@@ -50,6 +52,18 @@ class StdOutTransportAdapter(asyncio.Transport):
50
52
  self.wfile.flush()
51
53
 
52
54
 
55
+ class _JsonRPCServerState:
56
+ __slots__ = ("closed", "in_closing", "logger", "loop", "server", "stdio_stop_event")
57
+
58
+ def __init__(self, loop: asyncio.AbstractEventLoop, logger: logging.Logger) -> None:
59
+ self.logger = logger
60
+ self.loop = loop
61
+ self.server: Optional[asyncio.AbstractServer] = None
62
+ self.stdio_stop_event: Optional[threading.Event] = None
63
+ self.in_closing = False
64
+ self.closed = False
65
+
66
+
53
67
  class JsonRPCServer(Generic[TProtocol], abc.ABC):
54
68
  _logger = LoggingDescriptor()
55
69
 
@@ -65,24 +79,78 @@ class JsonRPCServer(Generic[TProtocol], abc.ABC):
65
79
 
66
80
  self._run_func: Optional[Callable[[], None]] = None
67
81
  self._serve_func: Optional[Callable[[], Coroutine[None, None, None]]] = None
68
- self._server: Optional[asyncio.AbstractServer] = None
69
-
70
- self._stdio_stop_event: Optional[threading.Event] = None
71
-
72
- self._in_closing = False
73
- self._closed = False
74
82
 
75
83
  try:
76
- self.loop = asyncio.get_event_loop()
84
+ loop = asyncio.get_event_loop()
77
85
  except RuntimeError:
78
- self.loop = asyncio.new_event_loop()
79
- asyncio.set_event_loop(self.loop)
86
+ loop = asyncio.new_event_loop()
87
+ asyncio.set_event_loop(loop)
88
+
89
+ self._state = _JsonRPCServerState(
90
+ loop,
91
+ logging.getLogger(f"{type(self).__module__}.{type(self).__qualname__}"),
92
+ )
93
+ self._finalizer = weakref.finalize(self, JsonRPCServer._finalize_resources, self._state)
80
94
 
81
95
  if self.loop is not None:
82
96
  self.loop.slow_callback_duration = 10
83
97
 
84
- def __del__(self) -> None:
85
- self.close()
98
+ @staticmethod
99
+ def _finalize_resources(state: _JsonRPCServerState) -> None:
100
+ if state.closed:
101
+ return
102
+
103
+ if state.stdio_stop_event is not None:
104
+ state.stdio_stop_event.set()
105
+
106
+ if state.server is not None:
107
+ try:
108
+ if not state.loop.is_closed() and state.loop.is_running():
109
+ state.loop.call_soon_threadsafe(state.server.close)
110
+ else:
111
+ state.server.close()
112
+ except BaseException:
113
+ pass
114
+
115
+ state.logger.debug(
116
+ "JsonRPCServer was garbage collected without calling close(); resources were cleaned up best-effort only.",
117
+ )
118
+
119
+ @property
120
+ def loop(self) -> asyncio.AbstractEventLoop:
121
+ return self._state.loop
122
+
123
+ @property
124
+ def _server(self) -> Optional[asyncio.AbstractServer]:
125
+ return self._state.server
126
+
127
+ @_server.setter
128
+ def _server(self, value: Optional[asyncio.AbstractServer]) -> None:
129
+ self._state.server = value
130
+
131
+ @property
132
+ def _stdio_stop_event(self) -> Optional[threading.Event]:
133
+ return self._state.stdio_stop_event
134
+
135
+ @_stdio_stop_event.setter
136
+ def _stdio_stop_event(self, value: Optional[threading.Event]) -> None:
137
+ self._state.stdio_stop_event = value
138
+
139
+ @property
140
+ def _in_closing(self) -> bool:
141
+ return self._state.in_closing
142
+
143
+ @_in_closing.setter
144
+ def _in_closing(self, value: bool) -> None:
145
+ self._state.in_closing = value
146
+
147
+ @property
148
+ def _closed(self) -> bool:
149
+ return self._state.closed
150
+
151
+ @_closed.setter
152
+ def _closed(self, value: bool) -> None:
153
+ self._state.closed = value
86
154
 
87
155
  @_logger.call
88
156
  def start(self) -> None:
@@ -122,6 +190,36 @@ class JsonRPCServer(Generic[TProtocol], abc.ABC):
122
190
  if self._server and self._server.is_serving():
123
191
  self._server.close()
124
192
 
193
+ def _close_on_loop_thread(self) -> None:
194
+ if self.loop.is_running() and getattr(self.loop, "_thread_id", None) != threading.get_ident():
195
+ closed_event = threading.Event()
196
+
197
+ def close_on_loop() -> None:
198
+ try:
199
+ self._close()
200
+ finally:
201
+ closed_event.set()
202
+
203
+ self.loop.call_soon_threadsafe(close_on_loop)
204
+ closed_event.wait()
205
+ return
206
+
207
+ self._close()
208
+
209
+ def _wait_closed_sync(self) -> None:
210
+ if self._server is None or self.loop.is_closed():
211
+ return
212
+
213
+ if self.loop.is_running():
214
+ if getattr(self.loop, "_thread_id", None) == threading.get_ident():
215
+ asyncio.create_task(self._server.wait_closed(), name=f"{type(self).__name__}.wait_closed")
216
+ return
217
+
218
+ asyncio.run_coroutine_threadsafe(self._server.wait_closed(), self.loop).result()
219
+ return
220
+
221
+ self.loop.run_until_complete(self._server.wait_closed())
222
+
125
223
  @_logger.call
126
224
  def close(self) -> None:
127
225
  if self._in_closing or self._closed:
@@ -129,10 +227,12 @@ class JsonRPCServer(Generic[TProtocol], abc.ABC):
129
227
 
130
228
  self._in_closing = True
131
229
  try:
132
- self._close()
230
+ self._close_on_loop_thread()
231
+ self._wait_closed_sync()
133
232
  finally:
134
233
  self._in_closing = False
135
234
  self._closed = True
235
+ self._finalizer.detach()
136
236
 
137
237
  @_logger.call
138
238
  async def close_async(self) -> None:
@@ -147,6 +247,7 @@ class JsonRPCServer(Generic[TProtocol], abc.ABC):
147
247
  finally:
148
248
  self._in_closing = False
149
249
  self._closed = True
250
+ self._finalizer.detach()
150
251
 
151
252
  async def __aenter__(self) -> Self:
152
253
  await self.start_async()
@@ -1 +0,0 @@
1
- __version__ = "2.4.0"