onec-interactive-runtime-core 0.1.19__py3-none-any.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 (87) hide show
  1. onec_interactive_runtime_core-0.1.19.dist-info/METADATA +12 -0
  2. onec_interactive_runtime_core-0.1.19.dist-info/RECORD +87 -0
  3. onec_interactive_runtime_core-0.1.19.dist-info/WHEEL +4 -0
  4. onec_interactive_runtime_core-0.1.19.dist-info/licenses/COPYRIGHT +9 -0
  5. onec_interactive_runtime_core-0.1.19.dist-info/licenses/LICENSE +232 -0
  6. onec_interactive_runtime_core-0.1.19.dist-info/licenses/THIRD_PARTY_NOTICES.txt +32 -0
  7. onec_runtime/AGENTS.md +7 -0
  8. onec_runtime/__init__.py +3 -0
  9. onec_runtime/artifacts.py +59 -0
  10. onec_runtime/bootstrap.py +310 -0
  11. onec_runtime/breakpoint_workspace.py +249 -0
  12. onec_runtime/bsl/__init__.py +139 -0
  13. onec_runtime/bsl/diagnostics.py +1046 -0
  14. onec_runtime/bsl/full_ast_worker_projection.py +522 -0
  15. onec_runtime/bsl/generated_semantic_parser.py +6322 -0
  16. onec_runtime/bsl/lexer.py +164 -0
  17. onec_runtime/bsl/module_catalog.py +420 -0
  18. onec_runtime/bsl/module_delta.py +1079 -0
  19. onec_runtime/bsl/module_universe.py +1877 -0
  20. onec_runtime/bsl/notebook_cells.py +232 -0
  21. onec_runtime/bsl/notebook_method_globals.py +74 -0
  22. onec_runtime/bsl/notebook_methods.py +175 -0
  23. onec_runtime/bsl/parser_artifact_identity.py +119 -0
  24. onec_runtime/bsl/parser_target.py +388 -0
  25. onec_runtime/bsl/preprocessor.py +16 -0
  26. onec_runtime/bsl/semantic_lowering.py +1539 -0
  27. onec_runtime/bsl/source_maps.py +2411 -0
  28. onec_runtime/bsl/worker_dependency_resolver.py +289 -0
  29. onec_runtime/bsl/worker_preprocessor.py +247 -0
  30. onec_runtime/bsl/worker_projection_model.py +137 -0
  31. onec_runtime/bsl/worker_reload_source_map.py +1697 -0
  32. onec_runtime/capture.py +113 -0
  33. onec_runtime/capture_source.py +453 -0
  34. onec_runtime/compact_table.py +376 -0
  35. onec_runtime/compact_table_backend.py +500 -0
  36. onec_runtime/config.py +198 -0
  37. onec_runtime/configurator_agent.py +520 -0
  38. onec_runtime/controller_worker.py +400 -0
  39. onec_runtime/credentials.py +18 -0
  40. onec_runtime/epf_container.py +196 -0
  41. onec_runtime/errors.py +205 -0
  42. onec_runtime/experiment.py +130 -0
  43. onec_runtime/extension_bundle.py +944 -0
  44. onec_runtime/extension_lifecycle.py +402 -0
  45. onec_runtime/extension_state.py +222 -0
  46. onec_runtime/fault_injection.py +30 -0
  47. onec_runtime/kernel.py +179 -0
  48. onec_runtime/lease.py +123 -0
  49. onec_runtime/observation.py +287 -0
  50. onec_runtime/performance_profile.py +245 -0
  51. onec_runtime/privacy.py +263 -0
  52. onec_runtime/processes.py +265 -0
  53. onec_runtime/prototype_runtime.py +2644 -0
  54. onec_runtime/rdbg/__init__.py +1 -0
  55. onec_runtime/rdbg/models.py +130 -0
  56. onec_runtime/rdbg/reconnect.py +59 -0
  57. onec_runtime/rdbg/session.py +913 -0
  58. onec_runtime/rdbg/transport.py +138 -0
  59. onec_runtime/rdbg/xml_codec.py +980 -0
  60. onec_runtime/recovery.py +106 -0
  61. onec_runtime/recovery_journal.py +79 -0
  62. onec_runtime/resources/extension/OnecInteractiveRuntime.cfe +0 -0
  63. onec_runtime/resources/extension/extension-manifest.json +108 -0
  64. onec_runtime/runtime_api.py +5272 -0
  65. onec_runtime/runtime_contracts.py +196 -0
  66. onec_runtime/server_worker.py +2034 -0
  67. onec_runtime/session.py +2123 -0
  68. onec_runtime/startup_diagnostics.py +45 -0
  69. onec_runtime/stop_routing.py +65 -0
  70. onec_runtime/supervised_processes.py +232 -0
  71. onec_runtime/supervised_runtime.py +512 -0
  72. onec_runtime/supervisor.py +363 -0
  73. onec_runtime/supervisor_model.py +148 -0
  74. onec_runtime/supervisor_protocol.py +77 -0
  75. onec_runtime/table_materialization.py +471 -0
  76. onec_runtime/table_transfer_backend.py +203 -0
  77. onec_runtime/table_value.py +311 -0
  78. onec_runtime/toolchain.py +730 -0
  79. onec_runtime/value_materialization.py +432 -0
  80. onec_runtime/value_transfer_backend.py +199 -0
  81. onec_runtime/worker_breakpoints.py +1431 -0
  82. onec_runtime/worker_epf.py +313 -0
  83. onec_runtime/worker_epf_template.py +53 -0
  84. onec_runtime/worker_stage_protocol.py +516 -0
  85. onec_runtime/worker_universe.py +3772 -0
  86. onec_runtime_build/__init__.py +1 -0
  87. onec_runtime_build/parser_target_development.py +116 -0
@@ -0,0 +1,265 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ import os
6
+ import subprocess
7
+ from time import monotonic, sleep
8
+ from typing import BinaryIO
9
+ from uuid import uuid4
10
+
11
+ import psutil
12
+
13
+ from onec_runtime.config import RuntimeConfig
14
+ from onec_runtime.errors import ProcessStartError, TargetLost
15
+
16
+
17
+ @dataclass
18
+ class OwnedProcess:
19
+ process: subprocess.Popen[bytes] = field(repr=False)
20
+ stdout_path: Path
21
+ stderr_path: Path
22
+ _streams: tuple[BinaryIO, BinaryIO] = field(repr=False)
23
+
24
+ @property
25
+ def pid(self) -> int:
26
+ return self.process.pid
27
+
28
+ def ensure_running(self) -> None:
29
+ returncode = self.process.poll()
30
+ if returncode is not None:
31
+ raise TargetLost(
32
+ f"Owned process {self.process.pid} exited with code {returncode}; "
33
+ f"see {self.stderr_path}"
34
+ )
35
+
36
+ def close(self, timeout_s: float = 10.0, *, graceful_timeout_s: float = 0.0) -> None:
37
+ deadline = monotonic() + max(0.0, timeout_s)
38
+ try:
39
+ if graceful_timeout_s > 0 and self.process.poll() is None:
40
+ try:
41
+ self.process.wait(timeout=min(graceful_timeout_s, max(0.0, deadline - monotonic())))
42
+ except subprocess.TimeoutExpired:
43
+ pass
44
+ if self.process.poll() is None:
45
+ self.process.terminate()
46
+ try:
47
+ self.process.wait(timeout=max(0.0, deadline - monotonic()))
48
+ except subprocess.TimeoutExpired:
49
+ self.process.kill()
50
+ try:
51
+ self.process.wait(timeout=max(0.0, deadline - monotonic()))
52
+ except subprocess.TimeoutExpired:
53
+ raise TargetLost(
54
+ f"Owned process {self.pid} did not exit before the cleanup deadline"
55
+ ) from None
56
+ finally:
57
+ for stream in self._streams:
58
+ stream.close()
59
+
60
+
61
+ def read_debug_server_notification(path: Path) -> tuple[str, int]:
62
+ payload = path.read_bytes()
63
+ encoding = "utf-16" if payload.startswith((b"\xff\xfe", b"\xfe\xff")) else "utf-8-sig"
64
+ value = payload.decode(encoding).strip()
65
+ host, port_text = value.rsplit(":", 1)
66
+ port = int(port_text)
67
+ if not host or not 0 < port <= 65535:
68
+ raise ValueError(f"Invalid debug server notification: {value!r}")
69
+ return host, port
70
+
71
+
72
+ def debug_server_command(config: RuntimeConfig, notify_path: Path) -> list[str]:
73
+ return [
74
+ str(config.debug_server_exe),
75
+ f"--addr={config.debug_host}",
76
+ f"--portRange={config.debug_port_from}:{config.debug_port_to}",
77
+ f"--ownerPID={os.getpid()}",
78
+ f"--notify={notify_path}",
79
+ ]
80
+
81
+
82
+ def debuggee_command(
83
+ config: RuntimeConfig,
84
+ debug_port: int,
85
+ *,
86
+ execute_external: bool = True,
87
+ thick_client: bool = False,
88
+ startup_parameter: str | None = None,
89
+ ) -> list[str]:
90
+ command = [
91
+ str(config.designer_exe if thick_client else config.client_exe),
92
+ "ENTERPRISE",
93
+ "/Lru",
94
+ *config.infobase_arguments,
95
+ "/TCOMP",
96
+ "-SDC",
97
+ "/DisableStartupDialogs",
98
+ "/DisableStartupMessages",
99
+ "/Out",
100
+ str(config.logs_dir / "1c-messages.log"),
101
+ "/TechnicalSpecialistMode",
102
+ "/DEBUG",
103
+ "-http",
104
+ "-attach",
105
+ "/DEBUGGERURL",
106
+ f"http://{config.debug_host}:{debug_port}",
107
+ "/O",
108
+ "Normal",
109
+ "/WA-",
110
+ "/N",
111
+ config.username,
112
+ "/P",
113
+ config.password,
114
+ ]
115
+ if execute_external:
116
+ infobase_argument_end = command.index(config.infobase_arguments[0]) + 2
117
+ command[infobase_argument_end:infobase_argument_end] = [
118
+ "/Execute",
119
+ str(config.kernel_epf),
120
+ ]
121
+ if startup_parameter is not None:
122
+ command.extend(("/C", startup_parameter))
123
+ return command
124
+
125
+
126
+ class FileModeProcesses:
127
+ """Own the client and, only for a file infobase, its private debugger."""
128
+ def __init__(self, config: RuntimeConfig) -> None:
129
+ self.config = config
130
+ self.debug_server: OwnedProcess | None = None
131
+ self.debuggee: OwnedProcess | None = None
132
+
133
+ def _spawn(
134
+ self,
135
+ command: list[str],
136
+ stem: str,
137
+ *,
138
+ hide_window: bool = False,
139
+ ) -> OwnedProcess:
140
+ self.config.logs_dir.mkdir(parents=True, exist_ok=True)
141
+ stdout_path = self.config.logs_dir / f"{stem}.stdout.log"
142
+ stderr_path = self.config.logs_dir / f"{stem}.stderr.log"
143
+ stdout = stdout_path.open("wb")
144
+ stderr = stderr_path.open("wb")
145
+ startupinfo = None
146
+ if hide_window and os.name == "nt":
147
+ startupinfo = subprocess.STARTUPINFO()
148
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
149
+ startupinfo.wShowWindow = subprocess.SW_HIDE
150
+ try:
151
+ process = subprocess.Popen(
152
+ command,
153
+ shell=False,
154
+ stdin=subprocess.DEVNULL,
155
+ stdout=stdout,
156
+ stderr=stderr,
157
+ startupinfo=startupinfo,
158
+ )
159
+ except OSError as error:
160
+ stdout.close()
161
+ stderr.close()
162
+ raise ProcessStartError(
163
+ f"Unable to start owned process (OS error {error.errno}); see {stderr_path}"
164
+ ) from None
165
+ return OwnedProcess(process, stdout_path, stderr_path, (stdout, stderr))
166
+
167
+ def start_debug_server(self, timeout_s: float = 30.0) -> int:
168
+ if self.config.is_server_infobase:
169
+ return self.config.debug_port
170
+ if self.debug_server is not None:
171
+ raise ProcessStartError("Debug server is already owned by this probe")
172
+ notify_path = self.config.logs_dir / f"dbgs-notify-{uuid4()}.txt"
173
+ self.debug_server = self._spawn(debug_server_command(self.config, notify_path), "dbgs")
174
+ deadline = monotonic() + timeout_s
175
+ while monotonic() < deadline:
176
+ self.debug_server.ensure_running()
177
+ try:
178
+ _, port = read_debug_server_notification(notify_path)
179
+ notify_path.unlink(missing_ok=True)
180
+ return port
181
+ except (FileNotFoundError, PermissionError, UnicodeDecodeError, ValueError):
182
+ pass
183
+ sleep(0.05)
184
+ raise ProcessStartError(f"dbgs.exe did not publish a port within {timeout_s} seconds")
185
+
186
+ def start_debuggee(
187
+ self,
188
+ debug_port: int,
189
+ *,
190
+ execute_external: bool = True,
191
+ thick_client: bool = False,
192
+ startup_parameter: str | None = None,
193
+ ) -> OwnedProcess:
194
+ if self.debuggee is not None:
195
+ raise ProcessStartError("1C debuggee is already owned by this probe")
196
+ if (
197
+ not self.config.is_server_infobase
198
+ and not (self.config.infobase_dir / "1Cv8.1CD").is_file()
199
+ ):
200
+ raise ProcessStartError("The dedicated file infobase does not exist")
201
+ if execute_external and not self.config.kernel_epf.is_file():
202
+ raise ProcessStartError("Kernel.epf has not been built")
203
+ (self.config.logs_dir / "1c-messages.log").unlink(missing_ok=True)
204
+ self.debuggee = self._spawn(
205
+ debuggee_command(
206
+ self.config,
207
+ debug_port,
208
+ execute_external=execute_external,
209
+ thick_client=thick_client,
210
+ startup_parameter=startup_parameter,
211
+ ),
212
+ "1cv8c",
213
+ hide_window=True,
214
+ )
215
+ return self.debuggee
216
+
217
+ def ensure_running(self) -> None:
218
+ if self.debuggee is None:
219
+ raise TargetLost("The owned 1C client process is missing")
220
+ if not self.config.is_server_infobase:
221
+ if self.debug_server is None:
222
+ raise TargetLost("The owned file-mode process pair is incomplete")
223
+ self.debug_server.ensure_running()
224
+ self.debuggee.ensure_running()
225
+
226
+ def sample_memory(self) -> dict[str, int | None]:
227
+ result: dict[str, int | None] = {"python": None, "dbgs": None, "onec": None}
228
+ pids = {
229
+ "python": os.getpid(),
230
+ "dbgs": self.debug_server.pid if self.debug_server else None,
231
+ "onec": self.debuggee.pid if self.debuggee else None,
232
+ }
233
+ for name, pid in pids.items():
234
+ if pid is None:
235
+ continue
236
+ try:
237
+ result[name] = psutil.Process(pid).memory_info().rss
238
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
239
+ result[name] = None
240
+ return result
241
+
242
+ def close(self, *, graceful_client_timeout_s: float = 0.0) -> None:
243
+ self.close_debuggee(graceful_timeout_s=graceful_client_timeout_s)
244
+ self.close_debug_server()
245
+
246
+ def close_debuggee(self, timeout_s: float = 10.0, *, graceful_timeout_s: float = 0.0) -> None:
247
+ if self.debuggee is None:
248
+ return
249
+ if graceful_timeout_s > 0:
250
+ self.debuggee.close(timeout_s, graceful_timeout_s=graceful_timeout_s)
251
+ else:
252
+ self.debuggee.close(timeout_s)
253
+ self.debuggee = None
254
+
255
+ def close_debug_server(self, timeout_s: float = 10.0) -> None:
256
+ if self.debug_server is None:
257
+ return
258
+ self.debug_server.close(timeout_s)
259
+ self.debug_server = None
260
+
261
+ def __enter__(self) -> "FileModeProcesses":
262
+ return self
263
+
264
+ def __exit__(self, *_: object) -> None:
265
+ self.close()