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,1431 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
import json
|
|
6
|
+
from threading import RLock
|
|
7
|
+
from uuid import UUID, uuid4, uuid5
|
|
8
|
+
|
|
9
|
+
from onec_runtime.bsl.source_maps import (
|
|
10
|
+
MappingRelation,
|
|
11
|
+
SourceSpan,
|
|
12
|
+
SourceUnitRef,
|
|
13
|
+
)
|
|
14
|
+
from onec_runtime.bsl.worker_reload_source_map import CompactReloadSourceMap
|
|
15
|
+
from onec_runtime.errors import ProtocolError
|
|
16
|
+
from onec_runtime.rdbg.models import ModuleLocation, StackFrame, StopEvent, TargetId
|
|
17
|
+
from onec_runtime.stop_routing import StopReason
|
|
18
|
+
from onec_runtime.worker_universe import (
|
|
19
|
+
WorkerGenerationDebugView,
|
|
20
|
+
WorkerGenerationHandle,
|
|
21
|
+
WorkerModuleDebugView,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class WorkerBreakpointRejection(StrEnum):
|
|
26
|
+
MODULE_NOT_IN_CANDIDATE = "module_not_in_candidate"
|
|
27
|
+
SOURCE_IDENTITY_MISMATCH = "source_identity_mismatch"
|
|
28
|
+
SOURCE_REVISION_NOT_ADMITTED = "source_revision_not_admitted"
|
|
29
|
+
NO_RETAINED_COMPATIBLE_ARTIFACT = "no_retained_compatible_artifact"
|
|
30
|
+
LINE_OUT_OF_RANGE = "line_out_of_range"
|
|
31
|
+
UNMAPPED_SOURCE_LINE = "unmapped_source_line"
|
|
32
|
+
SYNTHETIC_TARGET = "synthetic_target"
|
|
33
|
+
AMBIGUOUS_LINE_MAPPING = "ambiguous_line_mapping"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class WorkerBreakpointReloadPolicy(StrEnum):
|
|
37
|
+
STRICT = "strict"
|
|
38
|
+
RESET_INCOMPATIBLE = "reset_incompatible"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class WorkerBreakpointResolution(StrEnum):
|
|
42
|
+
PENDING = "pending"
|
|
43
|
+
RESOLVED = "resolved"
|
|
44
|
+
REJECTED = "rejected"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class WorkerBreakpointReloadOutcome(StrEnum):
|
|
48
|
+
COMMITTED = "committed"
|
|
49
|
+
ABORTED = "aborted"
|
|
50
|
+
QUARANTINED = "quarantined"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True, slots=True)
|
|
54
|
+
class WorkerBreakpoint:
|
|
55
|
+
id: UUID
|
|
56
|
+
source_unit: SourceUnitRef
|
|
57
|
+
canonical_module: str
|
|
58
|
+
line: int
|
|
59
|
+
column: int | None = None
|
|
60
|
+
|
|
61
|
+
def __post_init__(self) -> None:
|
|
62
|
+
if (
|
|
63
|
+
type(self.id) is not UUID
|
|
64
|
+
or not isinstance(self.source_unit, SourceUnitRef)
|
|
65
|
+
or type(self.canonical_module) is not str
|
|
66
|
+
or not self.canonical_module
|
|
67
|
+
or self.canonical_module != self.canonical_module.casefold()
|
|
68
|
+
or type(self.line) is not int
|
|
69
|
+
or self.line < 1
|
|
70
|
+
or self.column is not None
|
|
71
|
+
):
|
|
72
|
+
raise ValueError("worker breakpoint is invalid")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True, slots=True)
|
|
76
|
+
class WorkerBreakpointGenerationStatus:
|
|
77
|
+
generation: WorkerGenerationHandle
|
|
78
|
+
resolution: WorkerBreakpointResolution
|
|
79
|
+
reason: WorkerBreakpointRejection | None
|
|
80
|
+
|
|
81
|
+
def __post_init__(self) -> None:
|
|
82
|
+
if (
|
|
83
|
+
not isinstance(self.generation, WorkerGenerationHandle)
|
|
84
|
+
or type(self.resolution) is not WorkerBreakpointResolution
|
|
85
|
+
or not _valid_resolution_reason(self.resolution, self.reason)
|
|
86
|
+
):
|
|
87
|
+
raise ValueError("worker breakpoint generation status is invalid")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True, slots=True)
|
|
91
|
+
class WorkerBreakpointStatus:
|
|
92
|
+
breakpoint: WorkerBreakpoint
|
|
93
|
+
enabled: bool
|
|
94
|
+
resolution: WorkerBreakpointResolution
|
|
95
|
+
reason: WorkerBreakpointRejection | None
|
|
96
|
+
generations: tuple[WorkerBreakpointGenerationStatus, ...]
|
|
97
|
+
installed_binding_count: int
|
|
98
|
+
catalog_version: int
|
|
99
|
+
|
|
100
|
+
def __post_init__(self) -> None:
|
|
101
|
+
if (
|
|
102
|
+
not isinstance(self.breakpoint, WorkerBreakpoint)
|
|
103
|
+
or type(self.enabled) is not bool
|
|
104
|
+
or type(self.resolution) is not WorkerBreakpointResolution
|
|
105
|
+
or not _valid_resolution_reason(self.resolution, self.reason)
|
|
106
|
+
or type(self.generations) is not tuple
|
|
107
|
+
or any(
|
|
108
|
+
not isinstance(item, WorkerBreakpointGenerationStatus)
|
|
109
|
+
for item in self.generations
|
|
110
|
+
)
|
|
111
|
+
or type(self.installed_binding_count) is not int
|
|
112
|
+
or self.installed_binding_count < 0
|
|
113
|
+
or type(self.catalog_version) is not int
|
|
114
|
+
or self.catalog_version < 0
|
|
115
|
+
):
|
|
116
|
+
raise ValueError("worker breakpoint status is invalid")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True, slots=True)
|
|
120
|
+
class WorkerBreakpointReloadRemoval:
|
|
121
|
+
breakpoint_id: UUID
|
|
122
|
+
reason: WorkerBreakpointRejection
|
|
123
|
+
|
|
124
|
+
def __post_init__(self) -> None:
|
|
125
|
+
if (
|
|
126
|
+
type(self.breakpoint_id) is not UUID
|
|
127
|
+
or type(self.reason) is not WorkerBreakpointRejection
|
|
128
|
+
):
|
|
129
|
+
raise ValueError("worker breakpoint reload removal is invalid")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass(frozen=True, slots=True)
|
|
133
|
+
class WorkerBreakpointReloadReport:
|
|
134
|
+
transaction_id: UUID
|
|
135
|
+
candidate_handle: WorkerGenerationHandle
|
|
136
|
+
policy: WorkerBreakpointReloadPolicy
|
|
137
|
+
outcome: WorkerBreakpointReloadOutcome
|
|
138
|
+
planned_removals: tuple[WorkerBreakpointReloadRemoval, ...]
|
|
139
|
+
committed_removals: tuple[UUID, ...]
|
|
140
|
+
catalog_version: int
|
|
141
|
+
|
|
142
|
+
def __post_init__(self) -> None:
|
|
143
|
+
if (
|
|
144
|
+
type(self.transaction_id) is not UUID
|
|
145
|
+
or not isinstance(self.candidate_handle, WorkerGenerationHandle)
|
|
146
|
+
or type(self.policy) is not WorkerBreakpointReloadPolicy
|
|
147
|
+
or type(self.outcome) is not WorkerBreakpointReloadOutcome
|
|
148
|
+
or type(self.planned_removals) is not tuple
|
|
149
|
+
or any(
|
|
150
|
+
type(item) is not WorkerBreakpointReloadRemoval
|
|
151
|
+
for item in self.planned_removals
|
|
152
|
+
)
|
|
153
|
+
or type(self.committed_removals) is not tuple
|
|
154
|
+
or any(type(item) is not UUID for item in self.committed_removals)
|
|
155
|
+
or type(self.catalog_version) is not int
|
|
156
|
+
or self.catalog_version < 0
|
|
157
|
+
or (
|
|
158
|
+
self.outcome is not WorkerBreakpointReloadOutcome.COMMITTED
|
|
159
|
+
and self.committed_removals
|
|
160
|
+
)
|
|
161
|
+
):
|
|
162
|
+
raise ValueError("worker breakpoint reload report is invalid")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class WorkerBreakpointConflict(ProtocolError):
|
|
166
|
+
"""STRICT publication rejected by candidate-proven incompatibilities."""
|
|
167
|
+
|
|
168
|
+
def __init__(
|
|
169
|
+
self,
|
|
170
|
+
rejections: tuple[WorkerBreakpointReloadRemoval, ...],
|
|
171
|
+
) -> None:
|
|
172
|
+
if (
|
|
173
|
+
type(rejections) is not tuple
|
|
174
|
+
or not rejections
|
|
175
|
+
or any(type(item) is not WorkerBreakpointReloadRemoval for item in rejections)
|
|
176
|
+
):
|
|
177
|
+
raise ValueError("worker breakpoint conflict details are invalid")
|
|
178
|
+
self.rejections = rejections
|
|
179
|
+
super().__init__("Worker generation conflicts with enabled logical breakpoints")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass(frozen=True, slots=True, repr=False)
|
|
183
|
+
class WorkerBreakpointCatalogSnapshot:
|
|
184
|
+
catalog_version: int
|
|
185
|
+
statuses: tuple[WorkerBreakpointStatus, ...]
|
|
186
|
+
tombstone_ids: frozenset[UUID]
|
|
187
|
+
|
|
188
|
+
def __post_init__(self) -> None:
|
|
189
|
+
if (
|
|
190
|
+
type(self.catalog_version) is not int
|
|
191
|
+
or self.catalog_version < 0
|
|
192
|
+
or type(self.statuses) is not tuple
|
|
193
|
+
or any(not isinstance(item, WorkerBreakpointStatus) for item in self.statuses)
|
|
194
|
+
or any(item.catalog_version != self.catalog_version for item in self.statuses)
|
|
195
|
+
or len({item.breakpoint.id for item in self.statuses}) != len(self.statuses)
|
|
196
|
+
or type(self.tombstone_ids) is not frozenset
|
|
197
|
+
or any(type(item) is not UUID for item in self.tombstone_ids)
|
|
198
|
+
or any(item.breakpoint.id in self.tombstone_ids for item in self.statuses)
|
|
199
|
+
):
|
|
200
|
+
raise ValueError("worker breakpoint catalog snapshot is invalid")
|
|
201
|
+
|
|
202
|
+
def __repr__(self) -> str:
|
|
203
|
+
return (
|
|
204
|
+
"WorkerBreakpointCatalogSnapshot("
|
|
205
|
+
f"catalog_version={self.catalog_version}, "
|
|
206
|
+
f"statuses={self.statuses!r}, tombstones={len(self.tombstone_ids)})"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass(frozen=True, slots=True, repr=False)
|
|
211
|
+
class WorkerBreakpointPlan:
|
|
212
|
+
owner: object
|
|
213
|
+
proposal_id: UUID
|
|
214
|
+
expected_catalog_version: int
|
|
215
|
+
result_id: UUID
|
|
216
|
+
desired_slots: tuple[ModuleLocation, ...]
|
|
217
|
+
removed_ids: tuple[UUID, ...]
|
|
218
|
+
removal_details: tuple[WorkerBreakpointReloadRemoval, ...]
|
|
219
|
+
next_views: tuple[WorkerGenerationDebugView, ...]
|
|
220
|
+
next_snapshot: WorkerBreakpointCatalogSnapshot
|
|
221
|
+
|
|
222
|
+
def __post_init__(self) -> None:
|
|
223
|
+
if (
|
|
224
|
+
type(self.proposal_id) is not UUID
|
|
225
|
+
or type(self.expected_catalog_version) is not int
|
|
226
|
+
or self.expected_catalog_version < 0
|
|
227
|
+
or type(self.result_id) is not UUID
|
|
228
|
+
or type(self.desired_slots) is not tuple
|
|
229
|
+
or any(type(item) is not ModuleLocation for item in self.desired_slots)
|
|
230
|
+
or type(self.removed_ids) is not tuple
|
|
231
|
+
or any(type(item) is not UUID for item in self.removed_ids)
|
|
232
|
+
or type(self.removal_details) is not tuple
|
|
233
|
+
or any(
|
|
234
|
+
type(item) is not WorkerBreakpointReloadRemoval
|
|
235
|
+
for item in self.removal_details
|
|
236
|
+
)
|
|
237
|
+
or (
|
|
238
|
+
self.removal_details
|
|
239
|
+
and tuple(item.breakpoint_id for item in self.removal_details)
|
|
240
|
+
!= self.removed_ids
|
|
241
|
+
)
|
|
242
|
+
or type(self.next_views) is not tuple
|
|
243
|
+
or any(type(item) is not WorkerGenerationDebugView for item in self.next_views)
|
|
244
|
+
or not isinstance(self.next_snapshot, WorkerBreakpointCatalogSnapshot)
|
|
245
|
+
or self.next_snapshot.catalog_version
|
|
246
|
+
not in (self.expected_catalog_version, self.expected_catalog_version + 1)
|
|
247
|
+
):
|
|
248
|
+
raise ValueError("worker breakpoint plan is invalid")
|
|
249
|
+
|
|
250
|
+
def __repr__(self) -> str:
|
|
251
|
+
return (
|
|
252
|
+
"WorkerBreakpointPlan("
|
|
253
|
+
"owner=<redacted>, "
|
|
254
|
+
f"proposal_id={self.proposal_id!r}, "
|
|
255
|
+
f"expected_catalog_version={self.expected_catalog_version}, "
|
|
256
|
+
f"result_id={self.result_id!r}, "
|
|
257
|
+
f"desired_slots={len(self.desired_slots)}, "
|
|
258
|
+
f"removed_ids={self.removed_ids!r}, "
|
|
259
|
+
f"next_snapshot={self.next_snapshot!r})"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
_PENDING_REASONS = frozenset(
|
|
264
|
+
{
|
|
265
|
+
WorkerBreakpointRejection.SOURCE_REVISION_NOT_ADMITTED,
|
|
266
|
+
WorkerBreakpointRejection.NO_RETAINED_COMPATIBLE_ARTIFACT,
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
_MAP_REJECTION_REASONS = frozenset(
|
|
270
|
+
{
|
|
271
|
+
WorkerBreakpointRejection.LINE_OUT_OF_RANGE,
|
|
272
|
+
WorkerBreakpointRejection.UNMAPPED_SOURCE_LINE,
|
|
273
|
+
WorkerBreakpointRejection.SYNTHETIC_TARGET,
|
|
274
|
+
WorkerBreakpointRejection.AMBIGUOUS_LINE_MAPPING,
|
|
275
|
+
}
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _valid_resolution_reason(
|
|
280
|
+
resolution: WorkerBreakpointResolution,
|
|
281
|
+
reason: WorkerBreakpointRejection | None,
|
|
282
|
+
) -> bool:
|
|
283
|
+
if resolution is WorkerBreakpointResolution.RESOLVED:
|
|
284
|
+
return reason is None
|
|
285
|
+
if type(reason) is not WorkerBreakpointRejection:
|
|
286
|
+
return False
|
|
287
|
+
if resolution is WorkerBreakpointResolution.PENDING:
|
|
288
|
+
return reason in _PENDING_REASONS
|
|
289
|
+
return reason in _MAP_REJECTION_REASONS
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@dataclass(frozen=True, slots=True)
|
|
293
|
+
class WorkerLineMapping:
|
|
294
|
+
generated_line: int | None
|
|
295
|
+
reason: WorkerBreakpointRejection | None
|
|
296
|
+
|
|
297
|
+
def __post_init__(self) -> None:
|
|
298
|
+
if (
|
|
299
|
+
(self.generated_line is None) == (self.reason is None)
|
|
300
|
+
or (
|
|
301
|
+
self.generated_line is not None
|
|
302
|
+
and (
|
|
303
|
+
type(self.generated_line) is not int
|
|
304
|
+
or self.generated_line < 1
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
or (
|
|
308
|
+
self.reason is not None
|
|
309
|
+
and type(self.reason) is not WorkerBreakpointRejection
|
|
310
|
+
)
|
|
311
|
+
):
|
|
312
|
+
raise ValueError("worker line mapping is invalid")
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@dataclass(frozen=True, slots=True)
|
|
316
|
+
class WorkerSourceLocation:
|
|
317
|
+
source_unit: SourceUnitRef
|
|
318
|
+
canonical_module: str
|
|
319
|
+
line: int
|
|
320
|
+
column: int | None
|
|
321
|
+
|
|
322
|
+
def __post_init__(self) -> None:
|
|
323
|
+
if (
|
|
324
|
+
not isinstance(self.source_unit, SourceUnitRef)
|
|
325
|
+
or type(self.canonical_module) is not str
|
|
326
|
+
or not self.canonical_module
|
|
327
|
+
or self.canonical_module != self.canonical_module.casefold()
|
|
328
|
+
or type(self.line) is not int
|
|
329
|
+
or self.line < 1
|
|
330
|
+
or self.column is not None
|
|
331
|
+
):
|
|
332
|
+
raise ValueError("worker source location is invalid")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class WorkerFrameProvenance:
|
|
336
|
+
"""Opaque proof tying a mapped frame to one target-bound physical frame."""
|
|
337
|
+
|
|
338
|
+
__slots__ = ("__owner", "__raw_frame", "__target_incarnation_id")
|
|
339
|
+
|
|
340
|
+
def __init__(
|
|
341
|
+
self,
|
|
342
|
+
owner: object,
|
|
343
|
+
raw_frame: StackFrame,
|
|
344
|
+
target_incarnation_id: UUID,
|
|
345
|
+
) -> None:
|
|
346
|
+
if (
|
|
347
|
+
owner is not _WORKER_FRAME_PROVENANCE_OWNER
|
|
348
|
+
or type(raw_frame) is not StackFrame
|
|
349
|
+
or type(target_incarnation_id) is not UUID
|
|
350
|
+
):
|
|
351
|
+
raise TypeError("Worker frame provenance is private")
|
|
352
|
+
self.__owner = owner
|
|
353
|
+
self.__raw_frame = raw_frame
|
|
354
|
+
self.__target_incarnation_id = target_incarnation_id
|
|
355
|
+
|
|
356
|
+
def __repr__(self) -> str:
|
|
357
|
+
return "WorkerFrameProvenance(<redacted>)"
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
_WORKER_FRAME_PROVENANCE_OWNER = object()
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@dataclass(frozen=True, slots=True, repr=False)
|
|
364
|
+
class WorkerMappedFrame:
|
|
365
|
+
level: int
|
|
366
|
+
source: WorkerSourceLocation
|
|
367
|
+
generation: WorkerGenerationHandle
|
|
368
|
+
artifact_sha256: str
|
|
369
|
+
source_map_sha256: str
|
|
370
|
+
provenance: WorkerFrameProvenance
|
|
371
|
+
|
|
372
|
+
def __post_init__(self) -> None:
|
|
373
|
+
if (
|
|
374
|
+
type(self.level) is not int
|
|
375
|
+
or self.level < 0
|
|
376
|
+
or type(self.source) is not WorkerSourceLocation
|
|
377
|
+
or not isinstance(self.generation, WorkerGenerationHandle)
|
|
378
|
+
or type(self.artifact_sha256) is not str
|
|
379
|
+
or len(self.artifact_sha256) != 64
|
|
380
|
+
or type(self.source_map_sha256) is not str
|
|
381
|
+
or len(self.source_map_sha256) != 64
|
|
382
|
+
or type(self.provenance) is not WorkerFrameProvenance
|
|
383
|
+
):
|
|
384
|
+
raise ValueError("mapped Worker frame is invalid")
|
|
385
|
+
|
|
386
|
+
def __repr__(self) -> str:
|
|
387
|
+
return (
|
|
388
|
+
"WorkerMappedFrame("
|
|
389
|
+
f"level={self.level}, source={self.source!r}, "
|
|
390
|
+
f"generation={self.generation!r}, "
|
|
391
|
+
f"artifact_sha256={self.artifact_sha256!r}, "
|
|
392
|
+
f"source_map_sha256={self.source_map_sha256!r}, "
|
|
393
|
+
"provenance=<redacted>)"
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@dataclass(frozen=True, slots=True)
|
|
398
|
+
class NativeDebugFrame:
|
|
399
|
+
level: int
|
|
400
|
+
location: ModuleLocation
|
|
401
|
+
|
|
402
|
+
def __post_init__(self) -> None:
|
|
403
|
+
if (
|
|
404
|
+
type(self.level) is not int
|
|
405
|
+
or self.level < 0
|
|
406
|
+
or type(self.location) is not ModuleLocation
|
|
407
|
+
):
|
|
408
|
+
raise ValueError("native debug frame is invalid")
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@dataclass(frozen=True, slots=True, repr=False)
|
|
412
|
+
class WorkerBreakpointBinding:
|
|
413
|
+
breakpoint_id: UUID
|
|
414
|
+
generation: WorkerGenerationHandle
|
|
415
|
+
location: ModuleLocation
|
|
416
|
+
|
|
417
|
+
def __post_init__(self) -> None:
|
|
418
|
+
if (
|
|
419
|
+
type(self.breakpoint_id) is not UUID
|
|
420
|
+
or not isinstance(self.generation, WorkerGenerationHandle)
|
|
421
|
+
or type(self.location) is not ModuleLocation
|
|
422
|
+
):
|
|
423
|
+
raise ValueError("Worker breakpoint binding is invalid")
|
|
424
|
+
|
|
425
|
+
def __repr__(self) -> str:
|
|
426
|
+
return (
|
|
427
|
+
"WorkerBreakpointBinding("
|
|
428
|
+
f"breakpoint_id={self.breakpoint_id!r}, "
|
|
429
|
+
f"generation={self.generation!r}, location=<redacted>)"
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
@dataclass(frozen=True, slots=True, repr=False)
|
|
434
|
+
class RuntimeDebugStop:
|
|
435
|
+
reason: StopReason
|
|
436
|
+
origin: str
|
|
437
|
+
operation_id: int
|
|
438
|
+
location: WorkerSourceLocation | ModuleLocation
|
|
439
|
+
frames: tuple[WorkerMappedFrame | NativeDebugFrame, ...]
|
|
440
|
+
breakpoint_ids: tuple[UUID, ...]
|
|
441
|
+
|
|
442
|
+
def __post_init__(self) -> None:
|
|
443
|
+
if (
|
|
444
|
+
type(self.reason) is not StopReason
|
|
445
|
+
or self.origin not in {"main", "capture_evaluation"}
|
|
446
|
+
or type(self.operation_id) is not int
|
|
447
|
+
or self.operation_id < 0
|
|
448
|
+
or type(self.location) not in {WorkerSourceLocation, ModuleLocation}
|
|
449
|
+
or type(self.frames) is not tuple
|
|
450
|
+
or any(
|
|
451
|
+
type(frame) not in {WorkerMappedFrame, NativeDebugFrame}
|
|
452
|
+
for frame in self.frames
|
|
453
|
+
)
|
|
454
|
+
or type(self.breakpoint_ids) is not tuple
|
|
455
|
+
or any(type(item) is not UUID for item in self.breakpoint_ids)
|
|
456
|
+
or len(set(self.breakpoint_ids)) != len(self.breakpoint_ids)
|
|
457
|
+
):
|
|
458
|
+
raise ValueError("runtime debug stop is invalid")
|
|
459
|
+
|
|
460
|
+
def __repr__(self) -> str:
|
|
461
|
+
return (
|
|
462
|
+
"RuntimeDebugStop("
|
|
463
|
+
f"reason={self.reason!r}, origin={self.origin!r}, "
|
|
464
|
+
f"operation_id={self.operation_id}, location={self.location!r}, "
|
|
465
|
+
f"frames={self.frames!r}, breakpoint_ids={self.breakpoint_ids!r})"
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _overlaps(left: SourceSpan, right: SourceSpan) -> bool:
|
|
470
|
+
if left.start == left.end:
|
|
471
|
+
return right.start <= left.start <= right.end
|
|
472
|
+
if right.start == right.end:
|
|
473
|
+
return left.start <= right.start <= left.end
|
|
474
|
+
return left.start < right.end and right.start < left.end
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _generated_line_offsets(view: WorkerModuleDebugView, line: int) -> range:
|
|
478
|
+
try:
|
|
479
|
+
span = view.generated_line_index.line_range(line)
|
|
480
|
+
except ValueError as error:
|
|
481
|
+
raise ProtocolError("Worker generated line is outside the artifact") from error
|
|
482
|
+
if span.start == span.end:
|
|
483
|
+
return range(span.start, span.start + 1)
|
|
484
|
+
return range(span.start, span.end)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def map_generated_line(
|
|
488
|
+
view: WorkerModuleDebugView,
|
|
489
|
+
line: int,
|
|
490
|
+
) -> WorkerSourceLocation | None:
|
|
491
|
+
if type(view) is not WorkerModuleDebugView:
|
|
492
|
+
raise TypeError("worker module debug view is required")
|
|
493
|
+
if type(line) is not int or line < 1:
|
|
494
|
+
raise ValueError("generated line must be positive")
|
|
495
|
+
if view.visible_context is None:
|
|
496
|
+
raise ProtocolError("Worker visible source context is unavailable")
|
|
497
|
+
|
|
498
|
+
exact: set[tuple[SourceUnitRef, int, int]] = set()
|
|
499
|
+
saw_synthetic = False
|
|
500
|
+
saw_unproven = False
|
|
501
|
+
for offset in _generated_line_offsets(view, line):
|
|
502
|
+
try:
|
|
503
|
+
mapped = view.mapped_source.source_map.map_offset(offset)
|
|
504
|
+
except ValueError:
|
|
505
|
+
continue
|
|
506
|
+
if mapped.relation is MappingRelation.EXACT:
|
|
507
|
+
if mapped.unit is None or mapped.origin_span is None:
|
|
508
|
+
raise ProtocolError("Worker exact mapping has no source identity")
|
|
509
|
+
coordinates = view.visible_context.line_column(
|
|
510
|
+
mapped.unit,
|
|
511
|
+
mapped.origin_span.start,
|
|
512
|
+
)
|
|
513
|
+
if coordinates is None:
|
|
514
|
+
raise ProtocolError("Worker exact mapping provenance is unavailable")
|
|
515
|
+
exact.add((mapped.unit, coordinates[0], coordinates[1]))
|
|
516
|
+
elif mapped.relation is MappingRelation.SYNTHETIC:
|
|
517
|
+
saw_synthetic = True
|
|
518
|
+
else:
|
|
519
|
+
saw_unproven = True
|
|
520
|
+
|
|
521
|
+
source_lines = {(unit, source_line) for unit, source_line, _column in exact}
|
|
522
|
+
if len(source_lines) > 1:
|
|
523
|
+
raise ProtocolError("Worker generated line mapping is ambiguous")
|
|
524
|
+
if source_lines:
|
|
525
|
+
unit, source_line = next(iter(source_lines))
|
|
526
|
+
return WorkerSourceLocation(
|
|
527
|
+
unit,
|
|
528
|
+
view.canonical_module,
|
|
529
|
+
source_line,
|
|
530
|
+
None,
|
|
531
|
+
)
|
|
532
|
+
if saw_synthetic and not saw_unproven:
|
|
533
|
+
return None
|
|
534
|
+
raise ProtocolError("Worker generated line mapping is not exact")
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def resolve_source_line(
|
|
538
|
+
view: WorkerModuleDebugView,
|
|
539
|
+
source_unit: SourceUnitRef,
|
|
540
|
+
line: int,
|
|
541
|
+
) -> WorkerLineMapping:
|
|
542
|
+
if type(view) is not WorkerModuleDebugView:
|
|
543
|
+
raise TypeError("worker module debug view is required")
|
|
544
|
+
if not isinstance(source_unit, SourceUnitRef):
|
|
545
|
+
raise TypeError("worker source unit is required")
|
|
546
|
+
if source_unit not in view.source_units:
|
|
547
|
+
raise ProtocolError("Worker source identity does not match the debug view")
|
|
548
|
+
if type(line) is not int or line < 1:
|
|
549
|
+
return WorkerLineMapping(None, WorkerBreakpointRejection.LINE_OUT_OF_RANGE)
|
|
550
|
+
if view.visible_context is None:
|
|
551
|
+
raise ProtocolError("Worker visible source context is unavailable")
|
|
552
|
+
requested = view.visible_context.line_range(source_unit, line)
|
|
553
|
+
if requested is None:
|
|
554
|
+
return WorkerLineMapping(None, WorkerBreakpointRejection.LINE_OUT_OF_RANGE)
|
|
555
|
+
|
|
556
|
+
exact_lines: set[int] = set()
|
|
557
|
+
synthetic_lines: set[int] = set()
|
|
558
|
+
source_map = view.mapped_source.source_map
|
|
559
|
+
if isinstance(source_map, CompactReloadSourceMap):
|
|
560
|
+
spans = source_map.mapped_spans_for_visible_span(source_unit, requested)
|
|
561
|
+
for relation, span in spans:
|
|
562
|
+
if span.start == span.end:
|
|
563
|
+
lines = (view.generated_line_index.offset_to_line_column(span.start)[0],)
|
|
564
|
+
else:
|
|
565
|
+
first = view.generated_line_index.offset_to_line_column(span.start)[0]
|
|
566
|
+
last = view.generated_line_index.offset_to_line_column(span.end - 1)[0]
|
|
567
|
+
lines = range(first, last + 1)
|
|
568
|
+
if relation is MappingRelation.EXACT:
|
|
569
|
+
exact_lines.update(lines)
|
|
570
|
+
elif relation is MappingRelation.SYNTHETIC:
|
|
571
|
+
synthetic_lines.update(lines)
|
|
572
|
+
else:
|
|
573
|
+
for offset in range(len(view.mapped_source.text) + 1):
|
|
574
|
+
try:
|
|
575
|
+
mapped = source_map.map_offset(offset)
|
|
576
|
+
except ValueError:
|
|
577
|
+
continue
|
|
578
|
+
generated_line = view.generated_line_index.offset_to_line_column(offset)[0]
|
|
579
|
+
if (
|
|
580
|
+
mapped.relation is MappingRelation.EXACT
|
|
581
|
+
and mapped.unit == source_unit
|
|
582
|
+
and mapped.origin_span is not None
|
|
583
|
+
and _overlaps(mapped.origin_span, requested)
|
|
584
|
+
):
|
|
585
|
+
exact_lines.add(generated_line)
|
|
586
|
+
elif (
|
|
587
|
+
mapped.relation is MappingRelation.SYNTHETIC
|
|
588
|
+
and mapped.anchor_unit == source_unit
|
|
589
|
+
and mapped.anchor_span is not None
|
|
590
|
+
and _overlaps(mapped.anchor_span, requested)
|
|
591
|
+
):
|
|
592
|
+
synthetic_lines.add(generated_line)
|
|
593
|
+
|
|
594
|
+
if not exact_lines:
|
|
595
|
+
return WorkerLineMapping(
|
|
596
|
+
None,
|
|
597
|
+
(
|
|
598
|
+
WorkerBreakpointRejection.SYNTHETIC_TARGET
|
|
599
|
+
if synthetic_lines
|
|
600
|
+
else WorkerBreakpointRejection.UNMAPPED_SOURCE_LINE
|
|
601
|
+
),
|
|
602
|
+
)
|
|
603
|
+
if len(exact_lines) != 1 or exact_lines & synthetic_lines:
|
|
604
|
+
return WorkerLineMapping(
|
|
605
|
+
None,
|
|
606
|
+
WorkerBreakpointRejection.AMBIGUOUS_LINE_MAPPING,
|
|
607
|
+
)
|
|
608
|
+
generated_line = next(iter(exact_lines))
|
|
609
|
+
roundtrip = map_generated_line(view, generated_line)
|
|
610
|
+
if roundtrip is None or (
|
|
611
|
+
roundtrip.source_unit != source_unit or roundtrip.line != line
|
|
612
|
+
):
|
|
613
|
+
return WorkerLineMapping(
|
|
614
|
+
None,
|
|
615
|
+
WorkerBreakpointRejection.AMBIGUOUS_LINE_MAPPING,
|
|
616
|
+
)
|
|
617
|
+
return WorkerLineMapping(generated_line, None)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def _breakpoint_identity(
|
|
621
|
+
source_unit: SourceUnitRef,
|
|
622
|
+
canonical_module: str,
|
|
623
|
+
line: int,
|
|
624
|
+
column: int | None,
|
|
625
|
+
) -> str:
|
|
626
|
+
return json.dumps(
|
|
627
|
+
{
|
|
628
|
+
"canonical_module": canonical_module,
|
|
629
|
+
"column": column,
|
|
630
|
+
"line": line,
|
|
631
|
+
"source_kind": source_unit.kind.value,
|
|
632
|
+
"source_revision": source_unit.revision,
|
|
633
|
+
"source_sha256": source_unit.source_sha256.lower(),
|
|
634
|
+
"source_unit_id": source_unit.unit_id,
|
|
635
|
+
},
|
|
636
|
+
ensure_ascii=False,
|
|
637
|
+
sort_keys=True,
|
|
638
|
+
separators=(",", ":"),
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def _module_location_key(location: ModuleLocation) -> tuple[object, ...]:
|
|
643
|
+
return (
|
|
644
|
+
location.module_type,
|
|
645
|
+
location.url,
|
|
646
|
+
location.object_id.hex,
|
|
647
|
+
location.property_id.hex,
|
|
648
|
+
location.line,
|
|
649
|
+
location.extension_name,
|
|
650
|
+
location.ext_id,
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
class WorkerBreakpointCoordinator:
|
|
655
|
+
"""Own immutable logical Worker breakpoint catalog proposals.
|
|
656
|
+
|
|
657
|
+
This layer resolves admitted source provenance only. It intentionally does
|
|
658
|
+
not contact RDBG and therefore cannot claim any installed physical binding.
|
|
659
|
+
"""
|
|
660
|
+
|
|
661
|
+
__slots__ = (
|
|
662
|
+
"_committed_proposals",
|
|
663
|
+
"_installed_slots",
|
|
664
|
+
"_lock",
|
|
665
|
+
"_owner",
|
|
666
|
+
"_quarantined_plans",
|
|
667
|
+
"_session_id",
|
|
668
|
+
"_snapshot",
|
|
669
|
+
"_views",
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
def __init__(self, *, session_id: UUID) -> None:
|
|
673
|
+
if type(session_id) is not UUID:
|
|
674
|
+
raise TypeError("worker breakpoint session id must be a UUID")
|
|
675
|
+
self._session_id = session_id
|
|
676
|
+
self._owner = object()
|
|
677
|
+
self._lock = RLock()
|
|
678
|
+
self._views: tuple[WorkerGenerationDebugView, ...] = ()
|
|
679
|
+
self._installed_slots: frozenset[ModuleLocation] = frozenset()
|
|
680
|
+
self._committed_proposals: set[UUID] = set()
|
|
681
|
+
self._quarantined_plans: list[WorkerBreakpointPlan] = []
|
|
682
|
+
self._snapshot = WorkerBreakpointCatalogSnapshot(0, (), frozenset())
|
|
683
|
+
|
|
684
|
+
def set_views(self, views: tuple[WorkerGenerationDebugView, ...]) -> None:
|
|
685
|
+
if (
|
|
686
|
+
type(views) is not tuple
|
|
687
|
+
or any(type(view) is not WorkerGenerationDebugView for view in views)
|
|
688
|
+
or len({id(view.handle) for view in views}) != len(views)
|
|
689
|
+
):
|
|
690
|
+
raise TypeError("worker generation debug views are invalid")
|
|
691
|
+
with self._lock:
|
|
692
|
+
if views == self._views:
|
|
693
|
+
return
|
|
694
|
+
self._views = views
|
|
695
|
+
definitions = self._definitions(self._snapshot)
|
|
696
|
+
version = self._snapshot.catalog_version + 1
|
|
697
|
+
self._snapshot, _slots = self._build_snapshot(
|
|
698
|
+
definitions,
|
|
699
|
+
self._snapshot.tombstone_ids,
|
|
700
|
+
version,
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
def prepare_add(
|
|
704
|
+
self,
|
|
705
|
+
source_unit: SourceUnitRef,
|
|
706
|
+
canonical_module: str,
|
|
707
|
+
line: int,
|
|
708
|
+
*,
|
|
709
|
+
enabled: bool,
|
|
710
|
+
column: int | None,
|
|
711
|
+
) -> WorkerBreakpointPlan:
|
|
712
|
+
self._validate_request(
|
|
713
|
+
source_unit,
|
|
714
|
+
canonical_module,
|
|
715
|
+
line,
|
|
716
|
+
enabled=enabled,
|
|
717
|
+
column=column,
|
|
718
|
+
)
|
|
719
|
+
canonical_module = canonical_module.casefold()
|
|
720
|
+
with self._lock:
|
|
721
|
+
identity = _breakpoint_identity(
|
|
722
|
+
source_unit,
|
|
723
|
+
canonical_module,
|
|
724
|
+
line,
|
|
725
|
+
column,
|
|
726
|
+
)
|
|
727
|
+
breakpoint_id = uuid5(self._session_id, identity)
|
|
728
|
+
for status in self._snapshot.statuses:
|
|
729
|
+
if status.breakpoint.id == breakpoint_id:
|
|
730
|
+
if (
|
|
731
|
+
status.breakpoint.source_unit != source_unit
|
|
732
|
+
or status.breakpoint.canonical_module != canonical_module
|
|
733
|
+
or status.breakpoint.line != line
|
|
734
|
+
or status.breakpoint.column != column
|
|
735
|
+
):
|
|
736
|
+
raise ProtocolError("Worker breakpoint identity collision")
|
|
737
|
+
return self._plan(
|
|
738
|
+
result_id=breakpoint_id,
|
|
739
|
+
snapshot=self._snapshot,
|
|
740
|
+
removed_ids=(),
|
|
741
|
+
)
|
|
742
|
+
if breakpoint_id in self._snapshot.tombstone_ids:
|
|
743
|
+
raise ProtocolError("Removed Worker breakpoint identity cannot be reused")
|
|
744
|
+
breakpoint = WorkerBreakpoint(
|
|
745
|
+
breakpoint_id,
|
|
746
|
+
source_unit,
|
|
747
|
+
canonical_module,
|
|
748
|
+
line,
|
|
749
|
+
column,
|
|
750
|
+
)
|
|
751
|
+
definitions = self._definitions(self._snapshot) + ((breakpoint, enabled),)
|
|
752
|
+
next_snapshot, slots = self._build_snapshot(
|
|
753
|
+
definitions,
|
|
754
|
+
self._snapshot.tombstone_ids,
|
|
755
|
+
self._snapshot.catalog_version + 1,
|
|
756
|
+
)
|
|
757
|
+
return self._plan(
|
|
758
|
+
result_id=breakpoint_id,
|
|
759
|
+
snapshot=next_snapshot,
|
|
760
|
+
removed_ids=(),
|
|
761
|
+
desired_slots=slots,
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
def prepare_remove(self, breakpoint_id: UUID) -> WorkerBreakpointPlan:
|
|
765
|
+
self._validate_breakpoint_id(breakpoint_id)
|
|
766
|
+
with self._lock:
|
|
767
|
+
if breakpoint_id in self._snapshot.tombstone_ids:
|
|
768
|
+
return self._plan(
|
|
769
|
+
result_id=breakpoint_id,
|
|
770
|
+
snapshot=self._snapshot,
|
|
771
|
+
removed_ids=(),
|
|
772
|
+
)
|
|
773
|
+
definitions = tuple(
|
|
774
|
+
item
|
|
775
|
+
for item in self._definitions(self._snapshot)
|
|
776
|
+
if item[0].id != breakpoint_id
|
|
777
|
+
)
|
|
778
|
+
if len(definitions) == len(self._snapshot.statuses):
|
|
779
|
+
raise ProtocolError("Worker breakpoint id is unknown to this session")
|
|
780
|
+
tombstones = self._snapshot.tombstone_ids | {breakpoint_id}
|
|
781
|
+
next_snapshot, slots = self._build_snapshot(
|
|
782
|
+
definitions,
|
|
783
|
+
frozenset(tombstones),
|
|
784
|
+
self._snapshot.catalog_version + 1,
|
|
785
|
+
)
|
|
786
|
+
return self._plan(
|
|
787
|
+
result_id=breakpoint_id,
|
|
788
|
+
snapshot=next_snapshot,
|
|
789
|
+
removed_ids=(breakpoint_id,),
|
|
790
|
+
desired_slots=slots,
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
def prepare_enabled(
|
|
794
|
+
self,
|
|
795
|
+
breakpoint_id: UUID,
|
|
796
|
+
enabled: bool,
|
|
797
|
+
) -> WorkerBreakpointPlan:
|
|
798
|
+
self._validate_breakpoint_id(breakpoint_id)
|
|
799
|
+
if type(enabled) is not bool:
|
|
800
|
+
raise TypeError("worker breakpoint enabled must be bool")
|
|
801
|
+
with self._lock:
|
|
802
|
+
if breakpoint_id in self._snapshot.tombstone_ids:
|
|
803
|
+
raise ProtocolError("Worker breakpoint has been removed")
|
|
804
|
+
definitions = list(self._definitions(self._snapshot))
|
|
805
|
+
for index, (breakpoint, current_enabled) in enumerate(definitions):
|
|
806
|
+
if breakpoint.id != breakpoint_id:
|
|
807
|
+
continue
|
|
808
|
+
if current_enabled is enabled:
|
|
809
|
+
return self._plan(
|
|
810
|
+
result_id=breakpoint_id,
|
|
811
|
+
snapshot=self._snapshot,
|
|
812
|
+
removed_ids=(),
|
|
813
|
+
)
|
|
814
|
+
definitions[index] = (breakpoint, enabled)
|
|
815
|
+
next_snapshot, slots = self._build_snapshot(
|
|
816
|
+
tuple(definitions),
|
|
817
|
+
self._snapshot.tombstone_ids,
|
|
818
|
+
self._snapshot.catalog_version + 1,
|
|
819
|
+
)
|
|
820
|
+
return self._plan(
|
|
821
|
+
result_id=breakpoint_id,
|
|
822
|
+
snapshot=next_snapshot,
|
|
823
|
+
removed_ids=(),
|
|
824
|
+
desired_slots=slots,
|
|
825
|
+
)
|
|
826
|
+
raise ProtocolError("Worker breakpoint id is unknown to this session")
|
|
827
|
+
|
|
828
|
+
def prepare_generation(
|
|
829
|
+
self,
|
|
830
|
+
candidate_view: WorkerGenerationDebugView,
|
|
831
|
+
policy: WorkerBreakpointReloadPolicy,
|
|
832
|
+
) -> WorkerBreakpointPlan:
|
|
833
|
+
if type(candidate_view) is not WorkerGenerationDebugView:
|
|
834
|
+
raise TypeError("worker candidate debug view is required")
|
|
835
|
+
if type(policy) is not WorkerBreakpointReloadPolicy:
|
|
836
|
+
raise TypeError("worker breakpoint reload policy is required")
|
|
837
|
+
with self._lock:
|
|
838
|
+
if any(view.handle is candidate_view.handle for view in self._views):
|
|
839
|
+
raise ProtocolError("Worker candidate debug view is already retained")
|
|
840
|
+
incompatibilities = tuple(
|
|
841
|
+
detail
|
|
842
|
+
for status in self._snapshot.statuses
|
|
843
|
+
if (
|
|
844
|
+
detail := self._candidate_incompatibility(
|
|
845
|
+
status.breakpoint,
|
|
846
|
+
candidate_view,
|
|
847
|
+
)
|
|
848
|
+
)
|
|
849
|
+
is not None
|
|
850
|
+
)
|
|
851
|
+
if policy is WorkerBreakpointReloadPolicy.STRICT:
|
|
852
|
+
enabled_ids = {
|
|
853
|
+
status.breakpoint.id
|
|
854
|
+
for status in self._snapshot.statuses
|
|
855
|
+
if status.enabled
|
|
856
|
+
}
|
|
857
|
+
conflicts = tuple(
|
|
858
|
+
detail
|
|
859
|
+
for detail in incompatibilities
|
|
860
|
+
if detail.breakpoint_id in enabled_ids
|
|
861
|
+
)
|
|
862
|
+
if conflicts:
|
|
863
|
+
raise WorkerBreakpointConflict(conflicts)
|
|
864
|
+
removals: tuple[WorkerBreakpointReloadRemoval, ...] = ()
|
|
865
|
+
else:
|
|
866
|
+
removals = incompatibilities
|
|
867
|
+
removed_ids = tuple(item.breakpoint_id for item in removals)
|
|
868
|
+
removed = frozenset(removed_ids)
|
|
869
|
+
definitions = tuple(
|
|
870
|
+
item
|
|
871
|
+
for item in self._definitions(self._snapshot)
|
|
872
|
+
if item[0].id not in removed
|
|
873
|
+
)
|
|
874
|
+
next_views = self._views + (candidate_view,)
|
|
875
|
+
next_tombstones = self._snapshot.tombstone_ids | removed
|
|
876
|
+
next_snapshot, desired_slots = self._build_snapshot(
|
|
877
|
+
definitions,
|
|
878
|
+
frozenset(next_tombstones),
|
|
879
|
+
self._snapshot.catalog_version + 1,
|
|
880
|
+
views=next_views,
|
|
881
|
+
assume_desired_installed=True,
|
|
882
|
+
)
|
|
883
|
+
return self._plan(
|
|
884
|
+
result_id=uuid4(),
|
|
885
|
+
snapshot=next_snapshot,
|
|
886
|
+
removed_ids=removed_ids,
|
|
887
|
+
removal_details=removals,
|
|
888
|
+
desired_slots=desired_slots,
|
|
889
|
+
next_views=next_views,
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
def prepare_release(
|
|
893
|
+
self,
|
|
894
|
+
remaining_views: tuple[WorkerGenerationDebugView, ...],
|
|
895
|
+
) -> WorkerBreakpointPlan:
|
|
896
|
+
if (
|
|
897
|
+
type(remaining_views) is not tuple
|
|
898
|
+
or any(
|
|
899
|
+
type(view) is not WorkerGenerationDebugView
|
|
900
|
+
for view in remaining_views
|
|
901
|
+
)
|
|
902
|
+
or any(
|
|
903
|
+
all(view is not retained for retained in self._views)
|
|
904
|
+
for view in remaining_views
|
|
905
|
+
)
|
|
906
|
+
):
|
|
907
|
+
raise TypeError("remaining Worker debug views are invalid")
|
|
908
|
+
with self._lock:
|
|
909
|
+
next_snapshot, desired_slots = self._build_snapshot(
|
|
910
|
+
self._definitions(self._snapshot),
|
|
911
|
+
self._snapshot.tombstone_ids,
|
|
912
|
+
self._snapshot.catalog_version + 1,
|
|
913
|
+
views=remaining_views,
|
|
914
|
+
assume_desired_installed=True,
|
|
915
|
+
)
|
|
916
|
+
return self._plan(
|
|
917
|
+
result_id=uuid4(),
|
|
918
|
+
snapshot=next_snapshot,
|
|
919
|
+
removed_ids=(),
|
|
920
|
+
desired_slots=desired_slots,
|
|
921
|
+
next_views=remaining_views,
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
def quarantine(self, plan: WorkerBreakpointPlan) -> None:
|
|
925
|
+
if type(plan) is not WorkerBreakpointPlan:
|
|
926
|
+
raise TypeError("worker breakpoint plan is required")
|
|
927
|
+
with self._lock:
|
|
928
|
+
if (
|
|
929
|
+
plan.owner is not self._owner
|
|
930
|
+
or plan.expected_catalog_version != self._snapshot.catalog_version
|
|
931
|
+
or plan.proposal_id in self._committed_proposals
|
|
932
|
+
):
|
|
933
|
+
raise ProtocolError("Worker breakpoint proposal is stale")
|
|
934
|
+
self._quarantined_plans.append(plan)
|
|
935
|
+
|
|
936
|
+
def commit(
|
|
937
|
+
self,
|
|
938
|
+
plan: WorkerBreakpointPlan,
|
|
939
|
+
*,
|
|
940
|
+
workspace_confirmed: bool = False,
|
|
941
|
+
) -> None:
|
|
942
|
+
if type(plan) is not WorkerBreakpointPlan:
|
|
943
|
+
raise TypeError("worker breakpoint plan is required")
|
|
944
|
+
if type(workspace_confirmed) is not bool:
|
|
945
|
+
raise TypeError("workspace confirmation must be bool")
|
|
946
|
+
with self._lock:
|
|
947
|
+
if (
|
|
948
|
+
plan.owner is not self._owner
|
|
949
|
+
or plan.expected_catalog_version != self._snapshot.catalog_version
|
|
950
|
+
or plan.proposal_id in self._committed_proposals
|
|
951
|
+
):
|
|
952
|
+
raise ProtocolError("Worker breakpoint proposal is stale")
|
|
953
|
+
snapshot = plan.next_snapshot
|
|
954
|
+
if workspace_confirmed:
|
|
955
|
+
snapshot, desired_slots = self._build_snapshot(
|
|
956
|
+
self._definitions(plan.next_snapshot),
|
|
957
|
+
plan.next_snapshot.tombstone_ids,
|
|
958
|
+
plan.next_snapshot.catalog_version,
|
|
959
|
+
views=plan.next_views,
|
|
960
|
+
installed_slots=frozenset(plan.desired_slots),
|
|
961
|
+
)
|
|
962
|
+
if desired_slots != plan.desired_slots:
|
|
963
|
+
raise ProtocolError(
|
|
964
|
+
"Worker breakpoint proposal changed during commit"
|
|
965
|
+
)
|
|
966
|
+
self._snapshot = snapshot
|
|
967
|
+
self._views = plan.next_views
|
|
968
|
+
if workspace_confirmed:
|
|
969
|
+
self._installed_slots = frozenset(plan.desired_slots)
|
|
970
|
+
self._committed_proposals.add(plan.proposal_id)
|
|
971
|
+
|
|
972
|
+
def status(self, breakpoint_id: UUID) -> WorkerBreakpointStatus:
|
|
973
|
+
self._validate_breakpoint_id(breakpoint_id)
|
|
974
|
+
with self._lock:
|
|
975
|
+
for status in self._snapshot.statuses:
|
|
976
|
+
if status.breakpoint.id == breakpoint_id:
|
|
977
|
+
return status
|
|
978
|
+
raise ProtocolError("Worker breakpoint id is unknown to this session")
|
|
979
|
+
|
|
980
|
+
def list_statuses(self) -> tuple[WorkerBreakpointStatus, ...]:
|
|
981
|
+
with self._lock:
|
|
982
|
+
return self._snapshot.statuses
|
|
983
|
+
|
|
984
|
+
def snapshot(self) -> WorkerBreakpointCatalogSnapshot:
|
|
985
|
+
with self._lock:
|
|
986
|
+
return self._snapshot
|
|
987
|
+
|
|
988
|
+
def bindings_for_view(
|
|
989
|
+
self,
|
|
990
|
+
view: WorkerGenerationDebugView,
|
|
991
|
+
) -> tuple[WorkerBreakpointBinding, ...]:
|
|
992
|
+
if type(view) is not WorkerGenerationDebugView:
|
|
993
|
+
raise TypeError("worker generation debug view is required")
|
|
994
|
+
with self._lock:
|
|
995
|
+
if all(view is not retained for retained in self._views):
|
|
996
|
+
raise ProtocolError("Worker generation debug view is not retained")
|
|
997
|
+
bindings: list[WorkerBreakpointBinding] = []
|
|
998
|
+
for status in self._snapshot.statuses:
|
|
999
|
+
if not status.enabled:
|
|
1000
|
+
continue
|
|
1001
|
+
breakpoint = status.breakpoint
|
|
1002
|
+
modules = tuple(
|
|
1003
|
+
module
|
|
1004
|
+
for module in view.modules
|
|
1005
|
+
if module.canonical_module == breakpoint.canonical_module
|
|
1006
|
+
and breakpoint.source_unit in module.source_units
|
|
1007
|
+
)
|
|
1008
|
+
if not modules:
|
|
1009
|
+
continue
|
|
1010
|
+
if len(modules) != 1:
|
|
1011
|
+
raise ProtocolError(
|
|
1012
|
+
"Worker generation contains duplicate module views"
|
|
1013
|
+
)
|
|
1014
|
+
mapping = resolve_source_line(
|
|
1015
|
+
modules[0],
|
|
1016
|
+
breakpoint.source_unit,
|
|
1017
|
+
breakpoint.line,
|
|
1018
|
+
)
|
|
1019
|
+
if mapping.generated_line is None:
|
|
1020
|
+
continue
|
|
1021
|
+
bindings.append(
|
|
1022
|
+
WorkerBreakpointBinding(
|
|
1023
|
+
breakpoint.id,
|
|
1024
|
+
view.handle,
|
|
1025
|
+
modules[0].registration.module_location(
|
|
1026
|
+
mapping.generated_line
|
|
1027
|
+
),
|
|
1028
|
+
)
|
|
1029
|
+
)
|
|
1030
|
+
return tuple(
|
|
1031
|
+
sorted(
|
|
1032
|
+
bindings,
|
|
1033
|
+
key=lambda item: (
|
|
1034
|
+
_module_location_key(item.location),
|
|
1035
|
+
item.breakpoint_id.hex,
|
|
1036
|
+
),
|
|
1037
|
+
)
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
@staticmethod
|
|
1041
|
+
def _validate_request(
|
|
1042
|
+
source_unit: SourceUnitRef,
|
|
1043
|
+
canonical_module: str,
|
|
1044
|
+
line: int,
|
|
1045
|
+
*,
|
|
1046
|
+
enabled: bool,
|
|
1047
|
+
column: int | None,
|
|
1048
|
+
) -> None:
|
|
1049
|
+
if not isinstance(source_unit, SourceUnitRef):
|
|
1050
|
+
raise TypeError("worker breakpoint source unit is required")
|
|
1051
|
+
if type(canonical_module) is not str or not canonical_module:
|
|
1052
|
+
raise ValueError("worker breakpoint module is required")
|
|
1053
|
+
if type(line) is not int or line < 1:
|
|
1054
|
+
raise ValueError("worker breakpoint line must be positive")
|
|
1055
|
+
if type(enabled) is not bool:
|
|
1056
|
+
raise TypeError("worker breakpoint enabled must be bool")
|
|
1057
|
+
if column is not None:
|
|
1058
|
+
raise ValueError("worker breakpoint columns are not supported")
|
|
1059
|
+
|
|
1060
|
+
@staticmethod
|
|
1061
|
+
def _validate_breakpoint_id(breakpoint_id: UUID) -> None:
|
|
1062
|
+
if type(breakpoint_id) is not UUID:
|
|
1063
|
+
raise TypeError("worker breakpoint id must be a UUID")
|
|
1064
|
+
|
|
1065
|
+
@staticmethod
|
|
1066
|
+
def _definitions(
|
|
1067
|
+
snapshot: WorkerBreakpointCatalogSnapshot,
|
|
1068
|
+
) -> tuple[tuple[WorkerBreakpoint, bool], ...]:
|
|
1069
|
+
return tuple((status.breakpoint, status.enabled) for status in snapshot.statuses)
|
|
1070
|
+
|
|
1071
|
+
def _plan(
|
|
1072
|
+
self,
|
|
1073
|
+
*,
|
|
1074
|
+
result_id: UUID,
|
|
1075
|
+
snapshot: WorkerBreakpointCatalogSnapshot,
|
|
1076
|
+
removed_ids: tuple[UUID, ...],
|
|
1077
|
+
removal_details: tuple[WorkerBreakpointReloadRemoval, ...] = (),
|
|
1078
|
+
desired_slots: tuple[ModuleLocation, ...] | None = None,
|
|
1079
|
+
next_views: tuple[WorkerGenerationDebugView, ...] | None = None,
|
|
1080
|
+
) -> WorkerBreakpointPlan:
|
|
1081
|
+
if next_views is None:
|
|
1082
|
+
next_views = self._views
|
|
1083
|
+
if desired_slots is None:
|
|
1084
|
+
_ignored, desired_slots = self._build_snapshot(
|
|
1085
|
+
self._definitions(snapshot),
|
|
1086
|
+
snapshot.tombstone_ids,
|
|
1087
|
+
snapshot.catalog_version,
|
|
1088
|
+
)
|
|
1089
|
+
return WorkerBreakpointPlan(
|
|
1090
|
+
self._owner,
|
|
1091
|
+
uuid4(),
|
|
1092
|
+
self._snapshot.catalog_version,
|
|
1093
|
+
result_id,
|
|
1094
|
+
desired_slots,
|
|
1095
|
+
removed_ids,
|
|
1096
|
+
removal_details,
|
|
1097
|
+
next_views,
|
|
1098
|
+
snapshot,
|
|
1099
|
+
)
|
|
1100
|
+
|
|
1101
|
+
def _build_snapshot(
|
|
1102
|
+
self,
|
|
1103
|
+
definitions: tuple[tuple[WorkerBreakpoint, bool], ...],
|
|
1104
|
+
tombstones: frozenset[UUID],
|
|
1105
|
+
version: int,
|
|
1106
|
+
*,
|
|
1107
|
+
views: tuple[WorkerGenerationDebugView, ...] | None = None,
|
|
1108
|
+
installed_slots: frozenset[ModuleLocation] | None = None,
|
|
1109
|
+
assume_desired_installed: bool = False,
|
|
1110
|
+
) -> tuple[WorkerBreakpointCatalogSnapshot, tuple[ModuleLocation, ...]]:
|
|
1111
|
+
statuses: list[WorkerBreakpointStatus] = []
|
|
1112
|
+
desired: set[ModuleLocation] = set()
|
|
1113
|
+
active_views = self._views if views is None else views
|
|
1114
|
+
active_installed_slots = (
|
|
1115
|
+
self._installed_slots if installed_slots is None else installed_slots
|
|
1116
|
+
)
|
|
1117
|
+
for breakpoint, enabled in definitions:
|
|
1118
|
+
status, slots = self._resolve_breakpoint(
|
|
1119
|
+
breakpoint,
|
|
1120
|
+
enabled=enabled,
|
|
1121
|
+
version=version,
|
|
1122
|
+
views=active_views,
|
|
1123
|
+
installed_slots=active_installed_slots,
|
|
1124
|
+
assume_desired_installed=assume_desired_installed,
|
|
1125
|
+
)
|
|
1126
|
+
statuses.append(status)
|
|
1127
|
+
if enabled:
|
|
1128
|
+
desired.update(slots)
|
|
1129
|
+
desired_slots = tuple(sorted(desired, key=_module_location_key))
|
|
1130
|
+
return (
|
|
1131
|
+
WorkerBreakpointCatalogSnapshot(version, tuple(statuses), tombstones),
|
|
1132
|
+
desired_slots,
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
def _resolve_breakpoint(
|
|
1136
|
+
self,
|
|
1137
|
+
breakpoint: WorkerBreakpoint,
|
|
1138
|
+
*,
|
|
1139
|
+
enabled: bool,
|
|
1140
|
+
version: int,
|
|
1141
|
+
views: tuple[WorkerGenerationDebugView, ...],
|
|
1142
|
+
installed_slots: frozenset[ModuleLocation],
|
|
1143
|
+
assume_desired_installed: bool,
|
|
1144
|
+
) -> tuple[WorkerBreakpointStatus, tuple[ModuleLocation, ...]]:
|
|
1145
|
+
generation_statuses: list[WorkerBreakpointGenerationStatus] = []
|
|
1146
|
+
resolved_slots: set[ModuleLocation] = set()
|
|
1147
|
+
exact_rejections: list[WorkerBreakpointRejection] = []
|
|
1148
|
+
saw_module = False
|
|
1149
|
+
for generation_view in views:
|
|
1150
|
+
module_views = tuple(
|
|
1151
|
+
module
|
|
1152
|
+
for module in generation_view.modules
|
|
1153
|
+
if module.canonical_module == breakpoint.canonical_module
|
|
1154
|
+
)
|
|
1155
|
+
if len(module_views) > 1:
|
|
1156
|
+
raise ProtocolError("Worker generation contains duplicate module views")
|
|
1157
|
+
if not module_views:
|
|
1158
|
+
generation_statuses.append(
|
|
1159
|
+
WorkerBreakpointGenerationStatus(
|
|
1160
|
+
generation_view.handle,
|
|
1161
|
+
WorkerBreakpointResolution.PENDING,
|
|
1162
|
+
WorkerBreakpointRejection.NO_RETAINED_COMPATIBLE_ARTIFACT,
|
|
1163
|
+
)
|
|
1164
|
+
)
|
|
1165
|
+
continue
|
|
1166
|
+
saw_module = True
|
|
1167
|
+
module = module_views[0]
|
|
1168
|
+
if breakpoint.source_unit not in module.source_units:
|
|
1169
|
+
generation_statuses.append(
|
|
1170
|
+
WorkerBreakpointGenerationStatus(
|
|
1171
|
+
generation_view.handle,
|
|
1172
|
+
WorkerBreakpointResolution.PENDING,
|
|
1173
|
+
WorkerBreakpointRejection.SOURCE_REVISION_NOT_ADMITTED,
|
|
1174
|
+
)
|
|
1175
|
+
)
|
|
1176
|
+
continue
|
|
1177
|
+
mapping = resolve_source_line(
|
|
1178
|
+
module,
|
|
1179
|
+
breakpoint.source_unit,
|
|
1180
|
+
breakpoint.line,
|
|
1181
|
+
)
|
|
1182
|
+
if mapping.reason is not None:
|
|
1183
|
+
exact_rejections.append(mapping.reason)
|
|
1184
|
+
generation_statuses.append(
|
|
1185
|
+
WorkerBreakpointGenerationStatus(
|
|
1186
|
+
generation_view.handle,
|
|
1187
|
+
WorkerBreakpointResolution.REJECTED,
|
|
1188
|
+
mapping.reason,
|
|
1189
|
+
)
|
|
1190
|
+
)
|
|
1191
|
+
continue
|
|
1192
|
+
if mapping.generated_line is None:
|
|
1193
|
+
raise ProtocolError("Worker breakpoint resolution lost generated line")
|
|
1194
|
+
slot = module.registration.module_location(mapping.generated_line)
|
|
1195
|
+
resolved_slots.add(slot)
|
|
1196
|
+
generation_statuses.append(
|
|
1197
|
+
WorkerBreakpointGenerationStatus(
|
|
1198
|
+
generation_view.handle,
|
|
1199
|
+
WorkerBreakpointResolution.RESOLVED,
|
|
1200
|
+
None,
|
|
1201
|
+
)
|
|
1202
|
+
)
|
|
1203
|
+
|
|
1204
|
+
if resolved_slots:
|
|
1205
|
+
resolution = WorkerBreakpointResolution.RESOLVED
|
|
1206
|
+
reason = None
|
|
1207
|
+
elif exact_rejections:
|
|
1208
|
+
resolution = WorkerBreakpointResolution.REJECTED
|
|
1209
|
+
reason = (
|
|
1210
|
+
exact_rejections[0]
|
|
1211
|
+
if len(set(exact_rejections)) == 1
|
|
1212
|
+
else WorkerBreakpointRejection.AMBIGUOUS_LINE_MAPPING
|
|
1213
|
+
)
|
|
1214
|
+
else:
|
|
1215
|
+
resolution = WorkerBreakpointResolution.PENDING
|
|
1216
|
+
reason = (
|
|
1217
|
+
WorkerBreakpointRejection.SOURCE_REVISION_NOT_ADMITTED
|
|
1218
|
+
if saw_module
|
|
1219
|
+
else WorkerBreakpointRejection.NO_RETAINED_COMPATIBLE_ARTIFACT
|
|
1220
|
+
)
|
|
1221
|
+
installed_count = (
|
|
1222
|
+
(
|
|
1223
|
+
len(resolved_slots)
|
|
1224
|
+
if assume_desired_installed
|
|
1225
|
+
else len(resolved_slots & installed_slots)
|
|
1226
|
+
)
|
|
1227
|
+
if enabled and resolution is WorkerBreakpointResolution.RESOLVED
|
|
1228
|
+
else 0
|
|
1229
|
+
)
|
|
1230
|
+
status = WorkerBreakpointStatus(
|
|
1231
|
+
breakpoint,
|
|
1232
|
+
enabled,
|
|
1233
|
+
resolution,
|
|
1234
|
+
reason,
|
|
1235
|
+
tuple(generation_statuses),
|
|
1236
|
+
installed_count,
|
|
1237
|
+
version,
|
|
1238
|
+
)
|
|
1239
|
+
return status, tuple(sorted(resolved_slots, key=_module_location_key))
|
|
1240
|
+
|
|
1241
|
+
@staticmethod
|
|
1242
|
+
def _candidate_incompatibility(
|
|
1243
|
+
breakpoint: WorkerBreakpoint,
|
|
1244
|
+
candidate_view: WorkerGenerationDebugView,
|
|
1245
|
+
) -> WorkerBreakpointReloadRemoval | None:
|
|
1246
|
+
modules = tuple(
|
|
1247
|
+
module
|
|
1248
|
+
for module in candidate_view.modules
|
|
1249
|
+
if module.canonical_module == breakpoint.canonical_module
|
|
1250
|
+
)
|
|
1251
|
+
if len(modules) > 1:
|
|
1252
|
+
raise ProtocolError("Worker generation contains duplicate module views")
|
|
1253
|
+
if not modules:
|
|
1254
|
+
reason = WorkerBreakpointRejection.MODULE_NOT_IN_CANDIDATE
|
|
1255
|
+
elif breakpoint.source_unit not in modules[0].source_units:
|
|
1256
|
+
reason = WorkerBreakpointRejection.SOURCE_IDENTITY_MISMATCH
|
|
1257
|
+
else:
|
|
1258
|
+
mapping = resolve_source_line(
|
|
1259
|
+
modules[0],
|
|
1260
|
+
breakpoint.source_unit,
|
|
1261
|
+
breakpoint.line,
|
|
1262
|
+
)
|
|
1263
|
+
reason = mapping.reason
|
|
1264
|
+
return (
|
|
1265
|
+
None
|
|
1266
|
+
if reason is None
|
|
1267
|
+
else WorkerBreakpointReloadRemoval(breakpoint.id, reason)
|
|
1268
|
+
)
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
def require_exact_worker_module_or_native(
|
|
1272
|
+
location: ModuleLocation,
|
|
1273
|
+
view: WorkerGenerationDebugView,
|
|
1274
|
+
) -> WorkerModuleDebugView | None:
|
|
1275
|
+
if type(location) is not ModuleLocation:
|
|
1276
|
+
raise TypeError("debug module location is required")
|
|
1277
|
+
if type(view) is not WorkerGenerationDebugView:
|
|
1278
|
+
raise TypeError("worker generation debug view is required")
|
|
1279
|
+
if type(location.line) is not int or location.line < 1:
|
|
1280
|
+
raise ProtocolError("Debug frame line is invalid")
|
|
1281
|
+
expected_locations = tuple(
|
|
1282
|
+
(module, module.registration.module_location(location.line))
|
|
1283
|
+
for module in view.modules
|
|
1284
|
+
)
|
|
1285
|
+
exact = tuple(
|
|
1286
|
+
module
|
|
1287
|
+
for module, expected in expected_locations
|
|
1288
|
+
if location == expected
|
|
1289
|
+
)
|
|
1290
|
+
if len(exact) == 1:
|
|
1291
|
+
return exact[0]
|
|
1292
|
+
if len(exact) > 1:
|
|
1293
|
+
raise ProtocolError("Worker physical module identity is ambiguous")
|
|
1294
|
+
for _module, expected in expected_locations:
|
|
1295
|
+
same_worker_shape = (
|
|
1296
|
+
location.module_type == expected.module_type
|
|
1297
|
+
and location.object_id == expected.object_id
|
|
1298
|
+
and location.property_id == expected.property_id
|
|
1299
|
+
and location.extension_name == expected.extension_name
|
|
1300
|
+
and location.ext_id == expected.ext_id
|
|
1301
|
+
)
|
|
1302
|
+
if location.url == expected.url or same_worker_shape:
|
|
1303
|
+
raise ProtocolError("Worker physical module identity is stale or forged")
|
|
1304
|
+
return None
|
|
1305
|
+
|
|
1306
|
+
|
|
1307
|
+
def mint_worker_frame(
|
|
1308
|
+
level: int,
|
|
1309
|
+
source: WorkerSourceLocation,
|
|
1310
|
+
view: WorkerGenerationDebugView,
|
|
1311
|
+
module: WorkerModuleDebugView,
|
|
1312
|
+
raw_frame: StackFrame,
|
|
1313
|
+
) -> WorkerMappedFrame:
|
|
1314
|
+
if (
|
|
1315
|
+
type(level) is not int
|
|
1316
|
+
or level < 0
|
|
1317
|
+
or type(source) is not WorkerSourceLocation
|
|
1318
|
+
or type(view) is not WorkerGenerationDebugView
|
|
1319
|
+
or type(module) is not WorkerModuleDebugView
|
|
1320
|
+
or type(raw_frame) is not StackFrame
|
|
1321
|
+
or all(module is not candidate for candidate in view.modules)
|
|
1322
|
+
or raw_frame.level != level
|
|
1323
|
+
):
|
|
1324
|
+
raise ProtocolError("Worker frame provenance is inconsistent")
|
|
1325
|
+
return WorkerMappedFrame(
|
|
1326
|
+
level,
|
|
1327
|
+
source,
|
|
1328
|
+
view.handle,
|
|
1329
|
+
module.artifact_sha256,
|
|
1330
|
+
module.source_map_sha256,
|
|
1331
|
+
WorkerFrameProvenance(
|
|
1332
|
+
_WORKER_FRAME_PROVENANCE_OWNER,
|
|
1333
|
+
raw_frame,
|
|
1334
|
+
module.registration.target_incarnation_id,
|
|
1335
|
+
),
|
|
1336
|
+
)
|
|
1337
|
+
|
|
1338
|
+
|
|
1339
|
+
def _stop_stack_frames(event: StopEvent) -> tuple[StackFrame, ...]:
|
|
1340
|
+
if type(event) is not StopEvent or type(event.target_id) is not TargetId:
|
|
1341
|
+
raise TypeError("RDBG stop event is required")
|
|
1342
|
+
if event.stack_frames:
|
|
1343
|
+
frames = event.stack_frames
|
|
1344
|
+
levels = tuple(frame.level for frame in frames)
|
|
1345
|
+
if (
|
|
1346
|
+
any(type(frame) is not StackFrame for frame in frames)
|
|
1347
|
+
or any(frame.target_id != event.target_id for frame in frames)
|
|
1348
|
+
or any(type(level) is not int or level < 0 for level in levels)
|
|
1349
|
+
or len(set(levels)) != len(levels)
|
|
1350
|
+
):
|
|
1351
|
+
raise ProtocolError("Worker debug stack is incoherent")
|
|
1352
|
+
frames = tuple(sorted(frames, key=lambda frame: frame.level))
|
|
1353
|
+
if frames[0].level != 0 or frames[0].location != event.location:
|
|
1354
|
+
raise ProtocolError("Worker debug stack top does not match the stop")
|
|
1355
|
+
if event.stack and tuple(frame.location for frame in frames) != event.stack:
|
|
1356
|
+
raise ProtocolError("Worker debug stack representations disagree")
|
|
1357
|
+
return frames
|
|
1358
|
+
locations = event.stack or (event.location,)
|
|
1359
|
+
if locations[0] != event.location:
|
|
1360
|
+
raise ProtocolError("Worker debug stack top does not match the stop")
|
|
1361
|
+
return tuple(
|
|
1362
|
+
StackFrame(event.target_id, level, location)
|
|
1363
|
+
for level, location in enumerate(locations)
|
|
1364
|
+
)
|
|
1365
|
+
|
|
1366
|
+
|
|
1367
|
+
def map_worker_stop(
|
|
1368
|
+
event: StopEvent,
|
|
1369
|
+
*,
|
|
1370
|
+
operation_id: int,
|
|
1371
|
+
view: WorkerGenerationDebugView,
|
|
1372
|
+
origin: str,
|
|
1373
|
+
bindings: tuple[WorkerBreakpointBinding, ...],
|
|
1374
|
+
reason: StopReason = StopReason.USER_BREAKPOINT,
|
|
1375
|
+
) -> RuntimeDebugStop:
|
|
1376
|
+
if type(operation_id) is not int or operation_id < 0:
|
|
1377
|
+
raise ValueError("debug stop operation id is invalid")
|
|
1378
|
+
if type(view) is not WorkerGenerationDebugView:
|
|
1379
|
+
raise TypeError("worker generation debug view is required")
|
|
1380
|
+
if origin not in {"main", "capture_evaluation"}:
|
|
1381
|
+
raise ValueError("debug stop origin is invalid")
|
|
1382
|
+
if (
|
|
1383
|
+
type(bindings) is not tuple
|
|
1384
|
+
or any(type(binding) is not WorkerBreakpointBinding for binding in bindings)
|
|
1385
|
+
or any(binding.generation is not view.handle for binding in bindings)
|
|
1386
|
+
):
|
|
1387
|
+
raise ProtocolError("Worker breakpoint bindings do not match generation")
|
|
1388
|
+
if type(reason) is not StopReason:
|
|
1389
|
+
raise TypeError("debug stop reason is required")
|
|
1390
|
+
|
|
1391
|
+
top_module = require_exact_worker_module_or_native(event.location, view)
|
|
1392
|
+
if top_module is None:
|
|
1393
|
+
public_location: WorkerSourceLocation | ModuleLocation = event.location
|
|
1394
|
+
else:
|
|
1395
|
+
mapped_top = map_generated_line(top_module, event.location.line)
|
|
1396
|
+
if mapped_top is None:
|
|
1397
|
+
raise ProtocolError("Worker stopped in synthetic generated source")
|
|
1398
|
+
public_location = mapped_top
|
|
1399
|
+
|
|
1400
|
+
visible_frames: list[WorkerMappedFrame | NativeDebugFrame] = []
|
|
1401
|
+
for raw_frame in _stop_stack_frames(event):
|
|
1402
|
+
module = require_exact_worker_module_or_native(raw_frame.location, view)
|
|
1403
|
+
if module is None:
|
|
1404
|
+
visible_frames.append(
|
|
1405
|
+
NativeDebugFrame(raw_frame.level, raw_frame.location)
|
|
1406
|
+
)
|
|
1407
|
+
continue
|
|
1408
|
+
source = map_generated_line(module, raw_frame.location.line)
|
|
1409
|
+
if source is None:
|
|
1410
|
+
continue
|
|
1411
|
+
visible_frames.append(
|
|
1412
|
+
mint_worker_frame(raw_frame.level, source, view, module, raw_frame)
|
|
1413
|
+
)
|
|
1414
|
+
breakpoint_ids = tuple(
|
|
1415
|
+
sorted(
|
|
1416
|
+
{
|
|
1417
|
+
binding.breakpoint_id
|
|
1418
|
+
for binding in bindings
|
|
1419
|
+
if binding.location == event.location
|
|
1420
|
+
},
|
|
1421
|
+
key=lambda item: item.hex,
|
|
1422
|
+
)
|
|
1423
|
+
)
|
|
1424
|
+
return RuntimeDebugStop(
|
|
1425
|
+
reason,
|
|
1426
|
+
origin,
|
|
1427
|
+
operation_id,
|
|
1428
|
+
public_location,
|
|
1429
|
+
tuple(visible_frames),
|
|
1430
|
+
breakpoint_ids,
|
|
1431
|
+
)
|