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,45 @@
|
|
|
1
|
+
"""Bounded, allowlisted explanations for native 1C startup logs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def startup_log_hint(log_path: Path) -> str | None:
|
|
9
|
+
"""Classify known 1C failures without copying private log text into errors."""
|
|
10
|
+
try:
|
|
11
|
+
with log_path.open("rb") as stream:
|
|
12
|
+
payload = stream.read(64 * 1024)
|
|
13
|
+
except OSError:
|
|
14
|
+
return None
|
|
15
|
+
if payload.startswith((b"\xff\xfe", b"\xfe\xff")):
|
|
16
|
+
text = payload.decode("utf-16", errors="replace")
|
|
17
|
+
else:
|
|
18
|
+
text = payload.decode("utf-8-sig", errors="replace")
|
|
19
|
+
if "\ufffd" in text:
|
|
20
|
+
text = payload.decode("cp1251", errors="replace")
|
|
21
|
+
lowered = text.casefold()
|
|
22
|
+
if (
|
|
23
|
+
"ошибка программного лицензирования" in lowered
|
|
24
|
+
and "не предусматривает возможность запуска сервера 1с:предприятия" in lowered
|
|
25
|
+
):
|
|
26
|
+
return (
|
|
27
|
+
"используемая лицензия 1С не разрешает запуск сервера "
|
|
28
|
+
"1С:Предприятия; проверьте серверную лицензию"
|
|
29
|
+
)
|
|
30
|
+
if (
|
|
31
|
+
"error locking infobase for configuration" in lowered
|
|
32
|
+
or "ошибка блокировки информационной базы" in lowered
|
|
33
|
+
):
|
|
34
|
+
return "база заблокирована; закройте Конфигуратор для этой базы и повторите запуск"
|
|
35
|
+
if (
|
|
36
|
+
"the infobase user is not authenticated" in lowered
|
|
37
|
+
or "пользователь иб не идентифицирован" in lowered
|
|
38
|
+
):
|
|
39
|
+
return "пользователь ИБ не прошёл аутентификацию; проверьте username и password"
|
|
40
|
+
if (
|
|
41
|
+
"the infobase is not found" in lowered
|
|
42
|
+
or "информационная база не найдена" in lowered
|
|
43
|
+
):
|
|
44
|
+
return "информационная база не найдена; проверьте путь File или адрес и имя Srvr/Ref"
|
|
45
|
+
return None
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
from onec_runtime.rdbg.models import ModuleLocation, StopEvent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StopReason(Enum):
|
|
10
|
+
MAIN_SERVICE = "main_service"
|
|
11
|
+
CAPTURE = "capture"
|
|
12
|
+
USER_BREAKPOINT = "user_breakpoint"
|
|
13
|
+
PAUSE = "pause"
|
|
14
|
+
EXCEPTION = "exception"
|
|
15
|
+
UNKNOWN = "unknown"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class BreakpointRegistry:
|
|
20
|
+
service: ModuleLocation
|
|
21
|
+
captures: tuple[ModuleLocation, ...] = ()
|
|
22
|
+
users: tuple[ModuleLocation, ...] = ()
|
|
23
|
+
|
|
24
|
+
def __post_init__(self) -> None:
|
|
25
|
+
locations = self.full_locations
|
|
26
|
+
if len(set(locations)) != len(locations):
|
|
27
|
+
raise ValueError("Breakpoint registry groups overlap")
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def full_locations(self) -> tuple[ModuleLocation, ...]:
|
|
31
|
+
return (self.service, *self.captures, *self.users)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def evaluation_locations(self) -> tuple[ModuleLocation, ...]:
|
|
35
|
+
return (self.service, *self.users)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True, slots=True)
|
|
39
|
+
class ClassifiedStop:
|
|
40
|
+
event: StopEvent
|
|
41
|
+
reason: StopReason
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def classify_stop(
|
|
45
|
+
event: StopEvent,
|
|
46
|
+
registry: BreakpointRegistry,
|
|
47
|
+
*,
|
|
48
|
+
worker_locations: tuple[ModuleLocation, ...] = (),
|
|
49
|
+
) -> ClassifiedStop:
|
|
50
|
+
if event.runtime_error:
|
|
51
|
+
reason = StopReason.EXCEPTION
|
|
52
|
+
elif event.suspended_by_other is True:
|
|
53
|
+
reason = StopReason.PAUSE
|
|
54
|
+
elif event.stop_by_breakpoint is True:
|
|
55
|
+
if event.location == registry.service:
|
|
56
|
+
reason = StopReason.MAIN_SERVICE
|
|
57
|
+
elif event.location in registry.captures:
|
|
58
|
+
reason = StopReason.CAPTURE
|
|
59
|
+
elif event.location in registry.users or event.location in worker_locations:
|
|
60
|
+
reason = StopReason.USER_BREAKPOINT
|
|
61
|
+
else:
|
|
62
|
+
reason = StopReason.UNKNOWN
|
|
63
|
+
else:
|
|
64
|
+
reason = StopReason.UNKNOWN
|
|
65
|
+
return ClassifiedStop(event, reason)
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from dataclasses import fields
|
|
5
|
+
from enum import Enum
|
|
6
|
+
import multiprocessing
|
|
7
|
+
from multiprocessing.connection import Connection
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from time import monotonic
|
|
10
|
+
from uuid import UUID
|
|
11
|
+
|
|
12
|
+
from onec_runtime.config import RuntimeConfig
|
|
13
|
+
from onec_runtime.errors import (
|
|
14
|
+
CommandTimeout,
|
|
15
|
+
InvalidMessageSequence,
|
|
16
|
+
ProcessStartError,
|
|
17
|
+
)
|
|
18
|
+
from onec_runtime.processes import FileModeProcesses
|
|
19
|
+
from onec_runtime.supervisor_protocol import (
|
|
20
|
+
ControlMessage,
|
|
21
|
+
MessageKind,
|
|
22
|
+
MessageReceiver,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _is_data_only(value: object, seen: set[int] | None = None) -> bool:
|
|
27
|
+
value_type = type(value)
|
|
28
|
+
if value_type in {
|
|
29
|
+
type(None),
|
|
30
|
+
bool,
|
|
31
|
+
int,
|
|
32
|
+
float,
|
|
33
|
+
str,
|
|
34
|
+
bytes,
|
|
35
|
+
type(Path()),
|
|
36
|
+
UUID,
|
|
37
|
+
}:
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
seen = seen if seen is not None else set()
|
|
41
|
+
identity = id(value)
|
|
42
|
+
if identity in seen:
|
|
43
|
+
return False
|
|
44
|
+
seen.add(identity)
|
|
45
|
+
try:
|
|
46
|
+
if value_type is dict:
|
|
47
|
+
return all(
|
|
48
|
+
_is_data_only(key, seen) and _is_data_only(item, seen)
|
|
49
|
+
for key, item in value.items()
|
|
50
|
+
)
|
|
51
|
+
if value_type in {list, tuple, set, frozenset}:
|
|
52
|
+
return all(_is_data_only(item, seen) for item in value)
|
|
53
|
+
if value_type is RuntimeConfig:
|
|
54
|
+
return all(
|
|
55
|
+
_is_data_only(getattr(value, field.name), seen)
|
|
56
|
+
for field in fields(RuntimeConfig)
|
|
57
|
+
)
|
|
58
|
+
return False
|
|
59
|
+
finally:
|
|
60
|
+
seen.remove(identity)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class _ControllerStartupState(Enum):
|
|
64
|
+
WAITING_FOR_DEBUG_READY = "waiting_for_debug_ready"
|
|
65
|
+
READY = "ready"
|
|
66
|
+
FAILED = "failed"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class SupervisedGenerationProcesses:
|
|
70
|
+
"""Parent-owned OS processes and Controller IPC for one generation."""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
config: RuntimeConfig,
|
|
75
|
+
generation_id: int,
|
|
76
|
+
worker_target: Callable[..., None],
|
|
77
|
+
worker_args: tuple[object, ...],
|
|
78
|
+
) -> None:
|
|
79
|
+
if not _is_data_only(worker_args):
|
|
80
|
+
raise TypeError("Worker arguments must contain data-only driver inputs")
|
|
81
|
+
self._config = config
|
|
82
|
+
self._generation_id = generation_id
|
|
83
|
+
self._worker_target = worker_target
|
|
84
|
+
self._worker_args = worker_args
|
|
85
|
+
self._file_processes = FileModeProcesses(config)
|
|
86
|
+
self._context = multiprocessing.get_context("spawn")
|
|
87
|
+
self._parent_connection, self._child_connection = self._context.Pipe()
|
|
88
|
+
self._controller: multiprocessing.Process | None = None
|
|
89
|
+
self._controller_started = False
|
|
90
|
+
self._controller_exit_code: int | None = None
|
|
91
|
+
self._startup_state = _ControllerStartupState.WAITING_FOR_DEBUG_READY
|
|
92
|
+
self._receiver = MessageReceiver(generation_id)
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def controller_pid(self) -> int | None:
|
|
96
|
+
if self._controller is None or not self._controller.is_alive():
|
|
97
|
+
return None
|
|
98
|
+
return self._controller.pid
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def dbgs_pid(self) -> int | None:
|
|
102
|
+
owned = self._file_processes.debug_server
|
|
103
|
+
return owned.pid if owned is not None else None
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def onec_pid(self) -> int | None:
|
|
107
|
+
owned = self._file_processes.debuggee
|
|
108
|
+
return owned.pid if owned is not None else None
|
|
109
|
+
|
|
110
|
+
def start_debug_server(self) -> int:
|
|
111
|
+
return self._file_processes.start_debug_server()
|
|
112
|
+
|
|
113
|
+
def start_controller(self, debug_port: int, run_dir: Path) -> None:
|
|
114
|
+
if self._controller_started:
|
|
115
|
+
raise ProcessStartError("Controller is already owned by this generation")
|
|
116
|
+
process = self._context.Process(
|
|
117
|
+
target=self._worker_target,
|
|
118
|
+
args=(
|
|
119
|
+
self._generation_id,
|
|
120
|
+
self._child_connection,
|
|
121
|
+
debug_port,
|
|
122
|
+
run_dir,
|
|
123
|
+
*self._worker_args,
|
|
124
|
+
),
|
|
125
|
+
name=f"onec-controller-{self._generation_id}",
|
|
126
|
+
)
|
|
127
|
+
self._controller = process
|
|
128
|
+
try:
|
|
129
|
+
process.start()
|
|
130
|
+
self._controller_started = True
|
|
131
|
+
except BaseException:
|
|
132
|
+
self._controller = None
|
|
133
|
+
raise
|
|
134
|
+
finally:
|
|
135
|
+
self._child_connection.close()
|
|
136
|
+
|
|
137
|
+
def start_debuggee(self, debug_port: int) -> None:
|
|
138
|
+
if self._startup_state is not _ControllerStartupState.READY:
|
|
139
|
+
raise InvalidMessageSequence(
|
|
140
|
+
"Controller must publish DEBUG_READY before the debuggee starts"
|
|
141
|
+
)
|
|
142
|
+
self._file_processes.start_debuggee(
|
|
143
|
+
debug_port,
|
|
144
|
+
execute_external=False,
|
|
145
|
+
thick_client=False,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def send(self, message: ControlMessage) -> None:
|
|
149
|
+
self._parent_connection.send(message)
|
|
150
|
+
|
|
151
|
+
def receive(self, timeout_s: float) -> ControlMessage:
|
|
152
|
+
if not self._parent_connection.poll(timeout_s):
|
|
153
|
+
raise CommandTimeout(
|
|
154
|
+
f"Controller did not publish a message within {timeout_s} seconds"
|
|
155
|
+
)
|
|
156
|
+
message = self._parent_connection.recv()
|
|
157
|
+
if not isinstance(message, ControlMessage):
|
|
158
|
+
self._fail_startup()
|
|
159
|
+
raise InvalidMessageSequence("Controller sent a non-protocol message")
|
|
160
|
+
try:
|
|
161
|
+
self._receiver.accept(message)
|
|
162
|
+
except InvalidMessageSequence:
|
|
163
|
+
self._fail_startup()
|
|
164
|
+
raise
|
|
165
|
+
if self._startup_state is _ControllerStartupState.FAILED:
|
|
166
|
+
raise InvalidMessageSequence("Controller startup already failed")
|
|
167
|
+
if self._startup_state is _ControllerStartupState.WAITING_FOR_DEBUG_READY:
|
|
168
|
+
if message.kind is not MessageKind.DEBUG_READY:
|
|
169
|
+
self._startup_state = _ControllerStartupState.FAILED
|
|
170
|
+
raise InvalidMessageSequence(
|
|
171
|
+
"Controller first message must be DEBUG_READY"
|
|
172
|
+
)
|
|
173
|
+
self._startup_state = _ControllerStartupState.READY
|
|
174
|
+
elif message.kind is MessageKind.DEBUG_READY:
|
|
175
|
+
raise InvalidMessageSequence("Controller sent duplicate DEBUG_READY")
|
|
176
|
+
return message
|
|
177
|
+
|
|
178
|
+
def controller_exitcode(self) -> int | None:
|
|
179
|
+
self._reap_controller()
|
|
180
|
+
return (
|
|
181
|
+
self._controller_exit_code
|
|
182
|
+
if self._controller is None
|
|
183
|
+
else self._controller.exitcode
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def terminate_controller(self, timeout_s: float) -> None:
|
|
187
|
+
deadline = monotonic() + max(0.0, timeout_s)
|
|
188
|
+
process = self._controller
|
|
189
|
+
if process is not None:
|
|
190
|
+
if process.is_alive():
|
|
191
|
+
process.terminate()
|
|
192
|
+
process.join(max(0.0, deadline - monotonic()))
|
|
193
|
+
if process.is_alive():
|
|
194
|
+
process.kill()
|
|
195
|
+
process.join(max(0.0, deadline - monotonic()))
|
|
196
|
+
self._reap_controller()
|
|
197
|
+
self._close_connections()
|
|
198
|
+
|
|
199
|
+
def terminate_onec(self, timeout_s: float) -> None:
|
|
200
|
+
self._file_processes.close_debuggee(timeout_s)
|
|
201
|
+
|
|
202
|
+
def terminate_dbgs(self, timeout_s: float) -> None:
|
|
203
|
+
self._file_processes.close_debug_server(timeout_s)
|
|
204
|
+
|
|
205
|
+
def all_stopped(self) -> bool:
|
|
206
|
+
self._reap_controller()
|
|
207
|
+
return (
|
|
208
|
+
self._controller is None
|
|
209
|
+
and self._file_processes.debuggee is None
|
|
210
|
+
and self._file_processes.debug_server is None
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def _reap_controller(self) -> None:
|
|
214
|
+
process = self._controller
|
|
215
|
+
if process is None or process.exitcode is None:
|
|
216
|
+
return
|
|
217
|
+
process.join(timeout=0)
|
|
218
|
+
self._controller_exit_code = process.exitcode
|
|
219
|
+
process.close()
|
|
220
|
+
self._controller = None
|
|
221
|
+
self._close_connections()
|
|
222
|
+
|
|
223
|
+
def _close_connections(self) -> None:
|
|
224
|
+
for connection in (self._parent_connection, self._child_connection):
|
|
225
|
+
try:
|
|
226
|
+
connection.close()
|
|
227
|
+
except OSError:
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
def _fail_startup(self) -> None:
|
|
231
|
+
if self._startup_state is _ControllerStartupState.WAITING_FOR_DEBUG_READY:
|
|
232
|
+
self._startup_state = _ControllerStartupState.FAILED
|