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.
- onec_interactive_runtime_core-0.1.19.dist-info/METADATA +12 -0
- onec_interactive_runtime_core-0.1.19.dist-info/RECORD +87 -0
- onec_interactive_runtime_core-0.1.19.dist-info/WHEEL +4 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/COPYRIGHT +9 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/LICENSE +232 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/THIRD_PARTY_NOTICES.txt +32 -0
- onec_runtime/AGENTS.md +7 -0
- onec_runtime/__init__.py +3 -0
- onec_runtime/artifacts.py +59 -0
- onec_runtime/bootstrap.py +310 -0
- onec_runtime/breakpoint_workspace.py +249 -0
- onec_runtime/bsl/__init__.py +139 -0
- onec_runtime/bsl/diagnostics.py +1046 -0
- onec_runtime/bsl/full_ast_worker_projection.py +522 -0
- onec_runtime/bsl/generated_semantic_parser.py +6322 -0
- onec_runtime/bsl/lexer.py +164 -0
- onec_runtime/bsl/module_catalog.py +420 -0
- onec_runtime/bsl/module_delta.py +1079 -0
- onec_runtime/bsl/module_universe.py +1877 -0
- onec_runtime/bsl/notebook_cells.py +232 -0
- onec_runtime/bsl/notebook_method_globals.py +74 -0
- onec_runtime/bsl/notebook_methods.py +175 -0
- onec_runtime/bsl/parser_artifact_identity.py +119 -0
- onec_runtime/bsl/parser_target.py +388 -0
- onec_runtime/bsl/preprocessor.py +16 -0
- onec_runtime/bsl/semantic_lowering.py +1539 -0
- onec_runtime/bsl/source_maps.py +2411 -0
- onec_runtime/bsl/worker_dependency_resolver.py +289 -0
- onec_runtime/bsl/worker_preprocessor.py +247 -0
- onec_runtime/bsl/worker_projection_model.py +137 -0
- onec_runtime/bsl/worker_reload_source_map.py +1697 -0
- onec_runtime/capture.py +113 -0
- onec_runtime/capture_source.py +453 -0
- onec_runtime/compact_table.py +376 -0
- onec_runtime/compact_table_backend.py +500 -0
- onec_runtime/config.py +198 -0
- onec_runtime/configurator_agent.py +520 -0
- onec_runtime/controller_worker.py +400 -0
- onec_runtime/credentials.py +18 -0
- onec_runtime/epf_container.py +196 -0
- onec_runtime/errors.py +205 -0
- onec_runtime/experiment.py +130 -0
- onec_runtime/extension_bundle.py +944 -0
- onec_runtime/extension_lifecycle.py +402 -0
- onec_runtime/extension_state.py +222 -0
- onec_runtime/fault_injection.py +30 -0
- onec_runtime/kernel.py +179 -0
- onec_runtime/lease.py +123 -0
- onec_runtime/observation.py +287 -0
- onec_runtime/performance_profile.py +245 -0
- onec_runtime/privacy.py +263 -0
- onec_runtime/processes.py +265 -0
- onec_runtime/prototype_runtime.py +2644 -0
- onec_runtime/rdbg/__init__.py +1 -0
- onec_runtime/rdbg/models.py +130 -0
- onec_runtime/rdbg/reconnect.py +59 -0
- onec_runtime/rdbg/session.py +913 -0
- onec_runtime/rdbg/transport.py +138 -0
- onec_runtime/rdbg/xml_codec.py +980 -0
- onec_runtime/recovery.py +106 -0
- onec_runtime/recovery_journal.py +79 -0
- onec_runtime/resources/extension/OnecInteractiveRuntime.cfe +0 -0
- onec_runtime/resources/extension/extension-manifest.json +108 -0
- onec_runtime/runtime_api.py +5272 -0
- onec_runtime/runtime_contracts.py +196 -0
- onec_runtime/server_worker.py +2034 -0
- onec_runtime/session.py +2123 -0
- onec_runtime/startup_diagnostics.py +45 -0
- onec_runtime/stop_routing.py +65 -0
- onec_runtime/supervised_processes.py +232 -0
- onec_runtime/supervised_runtime.py +512 -0
- onec_runtime/supervisor.py +363 -0
- onec_runtime/supervisor_model.py +148 -0
- onec_runtime/supervisor_protocol.py +77 -0
- onec_runtime/table_materialization.py +471 -0
- onec_runtime/table_transfer_backend.py +203 -0
- onec_runtime/table_value.py +311 -0
- onec_runtime/toolchain.py +730 -0
- onec_runtime/value_materialization.py +432 -0
- onec_runtime/value_transfer_backend.py +199 -0
- onec_runtime/worker_breakpoints.py +1431 -0
- onec_runtime/worker_epf.py +313 -0
- onec_runtime/worker_epf_template.py +53 -0
- onec_runtime/worker_stage_protocol.py +516 -0
- onec_runtime/worker_universe.py +3772 -0
- onec_runtime_build/__init__.py +1 -0
- 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()
|