From 5cb97f351e832a64a8d0c2fb22e4b452a77d477e Mon Sep 17 00:00:00 2001 From: vczh Date: Sun, 2 Aug 2026 00:54:37 -0700 Subject: [PATCH] Run CDB directly --- .github/Guidelines/Coding.md | 4 + .github/Guidelines/Debugging.md | 42 ++++---- .github/Guidelines/Running-GacUI.md | 8 +- .github/KnowledgeBase/Index_VlppReflection.md | 11 +++ ..._RemoteProtocolRendererAndSerialization.md | 6 +- ...InterProcessNetworkProtocolsAndChannels.md | 18 +++- ...KB_VlppReflection_AttributeRegistration.md | 2 + ...KB_VlppReflection_GenerateMetaonlyTypes.md | 95 +++++++++++++++++++ .github/KnowledgeBase/Learning.md | 32 ++++--- .../manual/coding-agent/context.md | 2 +- .../manual/coding-agent/installing/windows.md | 9 +- .../KnowledgeBase/manual/coding-agent/jobs.md | 2 +- .../gacui/coding-agent/automation-service.md | 67 ++++++------- .../manual/gacui/modes/remote_client.md | 10 +- .../manual/vlppos/using-inter-process.md | 4 +- .github/Scripts/copilotDebug_Init.txt | 4 - .github/Scripts/copilotDebug_RunCommand.ps1 | 14 --- .github/Scripts/copilotDebug_Start.ps1 | 31 ------ .github/Scripts/copilotShared.ps1 | 15 --- .github/copilot-instructions.md | 8 +- .github/prompts/investigate.prompt.md | 2 +- 21 files changed, 222 insertions(+), 164 deletions(-) create mode 100644 .github/KnowledgeBase/KB_VlppReflection_GenerateMetaonlyTypes.md delete mode 100644 .github/Scripts/copilotDebug_Init.txt delete mode 100644 .github/Scripts/copilotDebug_RunCommand.ps1 delete mode 100644 .github/Scripts/copilotDebug_Start.ps1 diff --git a/.github/Guidelines/Coding.md b/.github/Guidelines/Coding.md index d8d983fc..b4386c59 100644 --- a/.github/Guidelines/Coding.md +++ b/.github/Guidelines/Coding.md @@ -157,6 +157,10 @@ object, causing missing of a complete picture. - Such finalization includes but not limited to: - Free an object or memory (`Ptr` is a good example). - Release a lock (`SPIN_LOCK` and other similar macros are good examples). +- If an exception is surely going to crash the app and nothing will recover from it: + - It usually means that under the situation, ensuring finalization to execute might bring no benefits. + - In this case, just call finalization as if there will be no exception, no RAII or try-catch is needed here. + - Such scenario is very usual in test apps. In shared library it is less likely to happen. ## Keep C++ Code Cross Platform diff --git a/.github/Guidelines/Debugging.md b/.github/Guidelines/Debugging.md index 135b8218..b9d2ec53 100644 --- a/.github/Guidelines/Debugging.md +++ b/.github/Guidelines/Debugging.md @@ -7,43 +7,42 @@ ## Windows Specific Debugging can be useful when you lack necessary information. -This section offers a set of PowerShell scripts that work with CDB (Microsoft's Console Debugger). CDB accepts exactly the same commands as WinDBG. ### Start a Debugger Read `REPO-ROOT/Project.md` to understand the solution folder and the unit test project name you are working with. Additional information can be found in the first line of `REPO-ROOT/.github/Scripts/Execute.log`. -Execute the following PowerShell commands: +Choose the executable for the intended configuration and read its arguments from the matching `*.vcxproj.user` file. -``` +Run CDB directly from `SOLUTION-ROOT` in a PTY-backed tool session: + +```powershell cd SOLUTION-ROOT -start powershell {& REPO-ROOT\.github\Scripts\copilotDebug_Start.ps1 -Executable PROJECT-NAME} +& $env:CDBPATH -lines "ABSOLUTE-PATH-TO-EXECUTABLE" ARGUMENTS ``` -If the debugger is already started, this script will fail because the pipe name is occupied. That probably means the last time you forgot to stop the debugger. You can kill `cdb` and the process being debugged, before starting a new debugger. +Keep the session ID returned by the tool. The process is paused at the initial breakpoint, giving you a chance to configure source mode and set breakpoints. Send these commands as newline-terminated stdin, one round at a time, and wait for output between rounds: -The `start powershell {}` is necessary; otherwise the script will block the execution forever, causing you to wait infinitely. -The script will finish immediately, leaving a debugger running in the background. You can send commands to the debugger. -The process being debugged is paused at the beginning; you are given a chance to set breakpoints. -After you are ready, send the `g` command to start running. +```text +l+s;l+t +g +``` + +Do not leave unbounded debugger output flowing through the terminal. If output repeats continuously, interrupt execution immediately and configure the relevant exception or output filter before continuing. ### Stop a Debugger -- Kill `cdb` process first, if any. - - The cdb path is stored in `$env:CDBPATH`. -- Kill the binary process that is blocked. +- If the debugged process is running or stuck, send Ctrl-C (`\u0003`). +- Send `q` to terminate the debugged process and CDB. +- If CDB does not respond, terminate the exact CDB process first and then the exact debugged process. -### Sending Commands to Debugger +### Sending Commands to CDB -``` -& REPO-ROOT\.github\Scripts\copilotDebug_RunCommand.ps1 -Command "Commands" -``` - -The effect of commands lasts across multiple `copilotDebug_RunCommand.ps1` calls. For example, after you execute `.frame X`, you do not need to repeat it to use `dx` under the same call stack frame in later calls, as `.frame X` is already effective. +Send commands to the same PTY-backed session. The effect of commands lasts for the whole session. For example, after you execute `.frame X`, you do not need to repeat it to use `dx` under the same call stack frame later. Multiple commands can be executed in sequence, separated by ";". -The debugger is configured to use source mode, which means you can see source files and line numbers in the call stack, and step in/out/over work line by line. +The `-lines` option and the `l+s;l+t` commands enable source mode, which means you can see source files and line numbers in the call stack, and step in/out/over work line by line. CDB accepts exactly the same commands as WinDBG, and here are some recommended commands: - **g**: continue until hitting a breakpoint or crashing. - **k**n: print current call stack. @@ -57,8 +56,8 @@ CDB accepts exactly the same commands as WinDBG, and here are some recommended c - **t**: step in, aka execute the current line, if any function is called, goes into the function. - **pt**: step out, aka run until the end of the current function. -An `.natvis` file is automatically provided with the debugger, -it formats some primitive types defined in the `Vlpp` project, +Load the installed `vlpp.natvis` file with `.nvload` when structured visualization is needed. +It formats some primitive types defined in the `Vlpp` project, including `WString` and other string types, `Nullable`, `Variant`, container types, etc. The formatting applies to the **dx** command, when you want to see raw data instead of formatted printing, @@ -70,7 +69,6 @@ You can also use `dv -rX` to expand "X" levels of fields. The default option is - Only use **dv** without any parameters. - DO NOT use **dt**. -- DO NOT use **q**, **qd**, **qq**, **qqd** etc. to stop the debugger. ## Linux/macOS Specific diff --git a/.github/Guidelines/Running-GacUI.md b/.github/Guidelines/Running-GacUI.md index 23c9bd81..026ad87a 100644 --- a/.github/Guidelines/Running-GacUI.md +++ b/.github/Guidelines/Running-GacUI.md @@ -13,8 +13,6 @@ When the model `gpt-5.3-codex-spark` is available: ## Windows Specific -- You are strongly recommended to attach a debugger when running any GacUI application. -- Because some runtime exceptions are silently consumed by Windows causing the application not to crash, covering issues if no debugger is attached. - GacUI applications could end up in dead loop or dead locks, so DO NOT JUST wait for the process to exit. - When it is crashed, sometimes (but not always) a native dialog would show and block the process. - Native dialogs could be proactivately called from a GacUI application, even when `FakeDialogService` is not used. @@ -25,6 +23,12 @@ When the model `gpt-5.3-codex-spark` is available: This is a very useful way for coding agent to debug GacUI applications. Computer use via UI Automation may not work when the computer screen is locked. +Automation is composed explicitly by each application: +- Construct the concrete automation service that matches the active controller: `WindowsAutomationService` for an ordinary Windows app, `WindowsAutomationServiceHosted` for hosted mode, `WindowsAutomationServiceRenderer` for a Windows remote renderer, `RemoteProtocolAutomationService` for a remote core, or the platform renderer service on Linux/macOS. +- Substitute that service with `GetNativeServiceSubstitution()->Substitute` before starting an endpoint. +- Start either the Windows HTTP endpoint or the MiniHTTP endpoint, run the application, then stop the endpoint, call `Stop` on the service, and unsubstitute it in that order. +- Endpoint selection changes the transport only. Windows HTTP and MiniHTTP expose the same `Controls`, `Dom`, and `IO` contract described below. + When `StartWindowsHttpAutomationService` is used during startup up a GacUI application: - It listens to `http://localhost:/Automation//...`. - GET `.../Controls`, for GacUI applications, exposing all visible windows and popups. diff --git a/.github/KnowledgeBase/Index_VlppReflection.md b/.github/KnowledgeBase/Index_VlppReflection.md index 833837da..aee36cb8 100644 --- a/.github/KnowledgeBase/Index_VlppReflection.md +++ b/.github/KnowledgeBase/Index_VlppReflection.md @@ -28,6 +28,17 @@ Runtime type information retrieval and manipulation through the reflection syste [API Explanation](./KB_VlppReflection_TypeMetadata.md) +#### Metaonly Metadata Generation and Loading + +Generate binary reflection metadata as either an independently loadable file or a layer that depends on types registered from previous files. + +- Use `CollectRegisteredTypes` after loading a base type manager to capture descriptors supplied by previous layers +- Pass an empty exclusion list to `GenerateMetaonlyTypes` for a self-contained file +- Pass a captured descriptor list to omit foreign metadata records while preserving references to those types +- Load and activate every dependency before reading a dependent file with `LoadMetaonlyTypes` + +[API Explanation](./KB_VlppReflection_GenerateMetaonlyTypes.md) + #### Type Registration Structure Organized approach for registering types with proper file organization and macro usage. diff --git a/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md b/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md index 863647c2..2d94103a 100644 --- a/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md +++ b/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md @@ -117,7 +117,7 @@ Protocol types are code-generated from `Protocol/*.txt` files into `GuiRemotePro `GuiRemoteProtocolAsyncJsonChannel` is the core-side async wrapper around an `IJsonChannel`. It queues outgoing packages, queues incoming events for `ProcessRemoteEvents()`, stores incoming responses by request id, and blocks `BatchWrite(disconnected)` until the current `PendingRequestGroup` is satisfied or disconnected. `connectionCounter` and `connectionClientId` protect pending requests when channel events arrive after disconnect/reconnect boundaries. -`GuiRemoteProtocolAsyncJsonChannelRenderer` is the renderer-side async wrapper. It queues received packages and schedules `ProcessRemoteMessages()` through an `IGuiRemoteProtocolAsyncRendererInvoker`. Before `SetInvokeInMainThread(...)` is called by renderer `GuiMain`, packages are cached. After the invoker is installed, they are drained on the renderer UI thread. `Initialize(reader)` requires a non-null reader; `Detach()` explicitly clears it, increments `messageVersion`, and drops queued work. The version check prevents callbacks queued before `Detach()` from running after detachment or a later reader installation. +`GuiRemoteProtocolAsyncJsonChannelRenderer` is the renderer-side async wrapper. It queues received packages and main-thread tasks, then schedules `ProcessPendingMessages()` through an `IGuiRemoteProtocolAsyncRendererInvoker`. Before `SetInvokeInMainThread(...)` is called by renderer `GuiMain`, work is cached. After the invoker is installed, it is drained in FIFO order on the renderer UI thread. `Initialize(reader)` requires a non-null reader; `Detach()` explicitly clears it, increments `messageVersion`, and drops queued work. The version check prevents callbacks queued before `Detach()` from running after detachment or a later reader installation. ## Demo Project Pair @@ -146,10 +146,10 @@ Located at `Test/GacUISrc/RemotingTest_Rendering_Win32/`. Accepts `/Pipe` or `/H 2. Creates `GuiRemoteProtocolAsyncJsonChannelRenderer` over the client's protocol channel. 3. Creates `GuiRemoteRendererSingle` and `GuiRemoteProtocolRendererChannel(&asyncRendererChannel, &remoteRenderer)`. 4. Waits for the server, then calls `SetupRawWindowsDirect2DRenderer()` to run the native window event loop. -5. In `GuiMain()`, creates the native window, registers it with `GuiRemoteRendererSingle`, installs `GuiMainAsyncRendererInvoker` through `asyncChannel->SetInvokeInMainThread(&invoker)`, and runs the window service. +5. In `GuiMain()`, creates the native window, registers it with `GuiRemoteRendererSingle`, creates a retained `Ptr`, installs it through `asyncChannel->SetInvokeInMainThread(invoker)`, drains startup work with `ProcessPendingMessages()`, and runs the window service. 6. On exit, clears the invoker, unregisters the main window, stops the network connection, and clears stack-owned renderer/channel pointers. -`RemotingTestChannelClient` records only the first fatal error and queues a native Yes/No prompt asking whether to close the renderer. A core read error uses the core-error title. A fatal local transport error uses the renderer-transport title and first calls `GuiRemoteRendererSingle::RequestCoreForceExitByFatalError()`. Choosing Yes calls `ForceExitByFatelError()`; choosing No calls `RetainByFatalError(message)`, keeps the native renderer window open with a `[STOPPED]` title and fatal overlay, and exposes the error through the renderer automation service. On disconnect, the client calls `GuiRemoteProtocolAsyncJsonChannelRenderer::Detach()` and forces renderer exit only when no fatal error has already been claimed. +`RemotingTestChannelClient` queues both protocol packages and terminal actions through the async renderer's ordered main-thread FIFO. A Core-authored `!Error` arrives through `OnReadError`, claims the first fatal error, and opens the native Yes/No prompt. Choosing Yes calls `ForceExitByFatelError()`; choosing No calls `RetainByFatalError(message)`, keeps the native renderer window open with a `[STOPPED]` title and fatal overlay, and exposes the error through renderer automation. A fatal local channel error has different UI semantics: after VlppOS's `IChannelClient` promotes a post-connection protocol error, `OnLocalError(..., true)` queues the ordinary disconnected transition directly, without showing a fatal prompt and without waiting for `OnDisconnected`. `OnDisconnected` queues the same idempotent transition when it is delivered. FIFO ordering lets an earlier `ControllerConnectionStopped` or Core `!Error` win before detach. ### Protocol Stack Direction diff --git a/.github/KnowledgeBase/KB_VlppOS_InterProcessNetworkProtocolsAndChannels.md b/.github/KnowledgeBase/KB_VlppOS_InterProcessNetworkProtocolsAndChannels.md index 556d3bee..42d4f47e 100644 --- a/.github/KnowledgeBase/KB_VlppOS_InterProcessNetworkProtocolsAndChannels.md +++ b/.github/KnowledgeBase/KB_VlppOS_InterProcessNetworkProtocolsAndChannels.md @@ -36,7 +36,7 @@ For an accepted connection: 3. Return `WaitForClientResult::Accept`, or return `Reject` without using the connection. 4. Exchange nonempty `WString` messages with `SendString`. -The most-derived server destructor must call `Stop` before destroying fields that connection callbacks can access. From outside callbacks, `Stop` is the callback-draining shutdown boundary. A callback-reentrant `Stop` prevents further work but does not unwind the current callback, whose visible state must survive until it returns. +The most-derived server destructor must call `Stop` before destroying fields that connection callbacks can access and must suppress any shutdown exception. From outside callbacks, `Stop` is the callback-draining shutdown boundary and reports a recorded completion exception at most once. A callback-reentrant `Stop` prevents further work but does not unwind the current callback, whose visible state must survive until it returns. `INetworkProtocolClient` owns one logical connection. The normal client sequence is: @@ -113,7 +113,13 @@ public: ~ChannelServer() { - Stop(); + try + { + Stop(); + } + catch (...) + { + } } WaitForClientResult OnClientConnected( @@ -182,10 +188,14 @@ channels[L"Events"]->BroadcastFromClient(package); channels[L"Events"]->BatchWrite(disconnected); ``` -The derived client can also handle `OnConnected`, `OnDisconnected`, `OnReadError` and `OnLocalError`. +The derived client can also handle `OnConnected`, `OnDisconnected`, `OnReadError` and `OnLocalError`. A raw protocol callback returns `true` from `INetworkProtocolCallback::OnLocalError` when it needs to promote a recoverable transport error to fatal; the protocol then stops only after the callback returns. `NetworkProtocolChannelClient` uses this hook after its channel reaches `Connected`: it reports every local error to its `IChannelClient` user with `fatal == true`, transitions the channel to disconnected, and asks the raw transport to stop. Before the channel is connected, raw retry policy remains in control. `IChannelServer` routes messages but is not itself a channel participant. When server-side code must send ordinary channel messages, connect a `NetworkProtocolLocalChannelClient` with `ConnectLocalClient`; this gives the local participant a normal positive client id. +`BroadcastError` is an idempotent terminal admission boundary: the first error wins, it snapshots existing recipients, and it rejects new admissions. A client whose application admission callback was already in flight cannot commit after that snapshot; if the application accepted it, the server delivers the retained first terminal error before disconnecting it. A committed client becomes eligible for the snapshot only after its network client-id response or local `OnConnected` callback completes, so retained delivery preserves `connected -> fatal -> disconnected` ordering. + +The server keeps its underlying transport alive until raw protocol callbacks, admission callbacks and fatal-delivery work have left a shared stop barrier. Concurrent `Stop` calls wait for the same physical shutdown and cannot overtake retained delivery. If an admission, fatal, or disconnection callback itself calls `BroadcastError` or `Stop`, terminal state is published synchronously and physical stop is deferred until the protected callback unwinds so it cannot wait on itself. When the last barrier is a raw protocol callback, completion runs on a separate thread so the underlying transport's `Stop` can drain that callback. Recipient exceptions are recorded without bypassing best-effort terminal delivery and shutdown; a later non-reentrant `Stop` reports the recorded completion exception once. + ## How Socket HTTP Implements the Protocol `async_tcp_socket::SocketHttpServer`, defined in `Source/InterProcess/AsyncSocket/AsyncSocket_HttpServer.h`, derives from `async_tcp_socket::SocketHttpServerApi` and implements `INetworkProtocolServer`. `async_tcp_socket::SocketHttpClient`, defined in `Source/InterProcess/AsyncSocket/AsyncSocket_HttpClient.h`, implements both `INetworkProtocolClient` and its single logical `INetworkProtocolConnection`. @@ -270,6 +280,6 @@ Use `VlppOS.Windows.h` on Windows; Winsock initialization and `Ws2_32.lib` linka `urlPrefix` is empty for the origin root or an ASCII origin-form prefix such as `/example`. Both adapters remove trailing slashes, so `/` becomes the origin root. A nonempty normalized prefix must start with `/` and must not contain a query, fragment, backslash, NUL, malformed escape, or encoded path separator. Server and client must use the same normalized prefix, and their injected sockets must report the same port. -The portable `HttpRequestClient` treats a 404 response as `HttpResponseFailure::NotFound`, reports a fatal error, and stops its socket. `SocketHttpClientApi` exposes this as `SocketHttpClientErrorCode::ResponseNotFound`; the logical `SocketHttpClient` reports one fatal local error immediately instead of retrying `/Connect`, `/Request`, or `/Response`. +The portable `HttpRequestClient` treats a 404 response as `HttpResponseFailure::NotFound`, reports a terminal physical-lane error, and stops that socket. `SocketHttpClientApi` exposes this as `SocketHttpClientErrorCode::ResponseNotFound`. The logical `SocketHttpClient` does not inherit that physical fatal classification: it reports the endpoint failure as nonfatal and applies the endpoint's normal retry policy. `/Connect` and `/Response` retry up to their normal attempt limit, replacing a failed physical lane when needed; `/Request` replaces its receive lane and continues polling. If a connected `NetworkProtocolChannelClient` owns the callback, its promotion response stops the logical client after the first reported failure instead. For the lower socket, HTTP request and Mini HTTP API startup rules on each platform, see [Inter-Process Async-Socket-Based Mini HTTP API](./KB_VlppOS_InterProcessAsyncSocketBasedMiniHttpApi.md). diff --git a/.github/KnowledgeBase/KB_VlppReflection_AttributeRegistration.md b/.github/KnowledgeBase/KB_VlppReflection_AttributeRegistration.md index 77f34a07..01313c4a 100644 --- a/.github/KnowledgeBase/KB_VlppReflection_AttributeRegistration.md +++ b/.github/KnowledgeBase/KB_VlppReflection_AttributeRegistration.md @@ -69,6 +69,8 @@ Use `IAttributeInfo` to inspect an attribute: Attributes are serialized into metaonly binary metadata by `GenerateMetaonlyTypes` and deserialized by `LoadMetaonlyTypes`. Ordinary attribute values are serialized through `ISerializableType::Serialize` and deserialized through `ISerializableType::Deserialize`. `ITypeDescriptor*` attribute values are encoded separately as referenced type-descriptor indices with empty serialized data. +For general metadata generation, dependency layers, and loading order, see [GenerateMetaonlyTypes](./KB_VlppReflection_GenerateMetaonlyTypes.md). + Attributes appear in the logged text output (`.txt` baseline files) in the format: ``` @Attribute:(:, ...) diff --git a/.github/KnowledgeBase/KB_VlppReflection_GenerateMetaonlyTypes.md b/.github/KnowledgeBase/KB_VlppReflection_GenerateMetaonlyTypes.md new file mode 100644 index 00000000..1215e9f9 --- /dev/null +++ b/.github/KnowledgeBase/KB_VlppReflection_GenerateMetaonlyTypes.md @@ -0,0 +1,95 @@ +# GenerateMetaonlyTypes + +## Generate and load binary reflection metadata + +`GenerateMetaonlyTypes` serializes the registered reflection metadata in a loaded global type manager. +The output can be a self-contained file or a dependent layer whose previously registered types are referenced without repeating their metadata records. + +The related APIs are: + +```cpp +void CollectRegisteredTypes(collections::List& types); +void GenerateMetaonlyTypes( + const collections::List& excludedTypes, + stream::IStream& outputStream +); +Ptr LoadMetaonlyTypes( + stream::IStream& inputStream, + const collections::Dictionary>& serializableTypes +); +``` + +## Self-contained metadata + +Pass an empty exclusion list to create an independently loadable file: + +```cpp +collections::List dependencies; +stream::FileStream output(fileName, stream::FileStream::WriteOnly); +GenerateMetaonlyTypes(dependencies, output); +``` + +The global type manager must already be loaded before generation. +An empty dependency list is serialized at the beginning of the file, and `LoadMetaonlyTypes` does not require pre-existing registered types when reading it. + +## Dependent metadata layers + +Use a snapshot of all currently registered descriptors when a later group of types should be stored in a separate file: + +1. Register and load every type in the base layer. +2. Call `CollectRegisteredTypes` to capture the base descriptors. The function replaces the output list instead of appending to it. +3. Register the types for the dependent layer. Adding a loader to an already loaded type manager applies it immediately; do not call `ITypeManager::Load` again. +4. Pass the captured base descriptors to `GenerateMetaonlyTypes`. + +```cpp +auto manager = GetGlobalTypeManager(); +manager->Load(); + +collections::List baseTypes; +CollectRegisteredTypes(baseTypes); + +manager->AddTypeLoader(CreateDependentTypeLoader()); + +stream::FileStream output(layerFileName, stream::FileStream::WriteOnly); +GenerateMetaonlyTypes(baseTypes, output); +``` + +The exclusion snapshot identifies descriptors supplied by previous layers. +Those descriptors can still be referenced by local base types, signatures, generic arguments, properties, events, attributes, and other metadata, but their own records are not emitted again. + +## Loading order + +Load and activate every dependency before even calling `LoadMetaonlyTypes` for a dependent file: + +```cpp +auto manager = GetGlobalTypeManager(); + +auto baseLoader = LoadMetaonlyTypes(baseStream, serializableTypes); +manager->AddTypeLoader(baseLoader); +manager->Load(); + +auto dependentLoader = LoadMetaonlyTypes(dependentStream, serializableTypes); +manager->AddTypeLoader(dependentLoader); +``` + +Dependency validation happens while `LoadMetaonlyTypes` reads the file. +If any required registered name is missing, it raises an error that includes that name. +When the manager is already loaded, adding the dependent loader registers its local descriptors immediately. + +## Dependency identity and ordering + +- Dependencies are identified by `ITypeDescriptor::GetTypeName()`, which is the registered reflection name. Do not use the C++ full name. +- `GenerateMetaonlyTypes` sorts dependency names in ascending order. The result does not depend on caller order or `ITypeManager::GetTypeDescriptor(vint)` enumeration order. +- The file begins with exactly one serialized `List` containing the sorted dependency names. +- Foreign descriptor indices occupy the dependency prefix in serialized-name order. Local descriptors follow in deterministic type-name order. +- Type-descriptor references use the combined foreign-and-local table. Method, property, and event indices remain local to the current layer. + +## Preconditions and lifetime + +- The global type manager must be loaded before calling `CollectRegisteredTypes` or `GenerateMetaonlyTypes`. +- Every excluded descriptor must be non-null, unique, and the exact descriptor currently registered under its name. +- Captured descriptors must remain registered and unchanged until generation finishes. Adding the new local types is expected, but do not replace captured descriptors, reset the manager, or mutate it concurrently with collection or generation. +- Keep dependency layers loaded while their dependent layers are in use. The dependent loader retains resolved foreign descriptors so its local metadata references remain valid. + +Attribute values participate in the same combined descriptor table. +For the attribute-specific representation of serializable values and `ITypeDescriptor*`, see [Attribute Registration](./KB_VlppReflection_AttributeRegistration.md). diff --git a/.github/KnowledgeBase/Learning.md b/.github/KnowledgeBase/Learning.md index ab86d55f..46451cdd 100644 --- a/.github/KnowledgeBase/Learning.md +++ b/.github/KnowledgeBase/Learning.md @@ -3,20 +3,20 @@ # Orders - Process staged tasks one by one with verification [19] -- Verify generated artifacts with downstream consumer checks [15] -- Port fixes from imports to source repositories [13] -- Crash early instead of adding error-tolerance fallbacks [9] -- Keep design documentation aligned with code after refactoring [8] -- Proactively remove code made redundant by refactoring [8] -- Fix behavior at the owning state instead of patching symptoms [7] +- Verify generated artifacts with downstream consumer checks [17] +- Crash early instead of adding error-tolerance fallbacks [14] +- Port fixes from imports to source repositories [14] +- Proactively remove code made redundant by refactoring [12] +- Keep design documentation aligned with code after refactoring [10] +- Fix behavior at the owning state instead of patching symptoms [10] +- Verify and localize portability on every target OS [7] +- Extract abstractions only for real shared behavior [7] - Make `Stop()` drain asynchronous work before returning [6] - Validate expectations against implementation and existing tests [5] -- Verify and localize portability on every target OS [5] - Use `WString::IndexOf` with `wchar_t` (not `const wchar_t*`) [4] - Use `collections::BinarySearchLambda` on contiguous buffers (guard empty) [4] +- Do not assume async callback owners are heap allocated [4] - Use `vl::Exception` for expected semantic failures and `CHECK_ERROR` for invariants [3] -- Extract abstractions only for real shared behavior [3] -- Do not assume async callback owners are heap allocated [3] - Capture dependent lambdas explicitly [2] - Don't assume observable changes are batched [2] - Use `ERROR_MESSAGE_PREFIX` for meaningful `CHECK_ERROR` / `CHECK_FAIL` messages [2] @@ -25,6 +25,9 @@ - Prefer well-defined tests over ambiguous edge cases [2] - Prefer raw pointers unless shared ownership is required [2] - Start async callbacks after most-derived construction [2] +- Sort serialization metadata by deterministic keys, not pointer addresses [2] +- Use RAII scope cleanup instead of manual catch cleanup [2] +- Treat environment correlation as evidence, not a cause [2] - Prefer `operator<=> = default` for lexicographic key structs [1] - Prefer two-pointer merge for sorted range maps [1] - Use named sentinel constants instead of raw values [1] @@ -32,9 +35,7 @@ - Avoid references into containers when mutating them [1] - Prefer designated initializers for aggregate-like structs [1] - Construct `Nullable` explicitly in function calls [1] -- Sort serialization metadata by deterministic keys, not pointer addresses [1] - Do not rely on `Event` handler invocation order [1] -- Use RAII scope cleanup instead of manual catch cleanup [1] - `collections::Dictionary` copy assignment is deleted (use move/swap) [1] - Dereference `Ptr` via `.Obj()` (not `*ptr`) [1] - `vl::regex` separator regex: `L"[\\/\\\\]+"` [1] @@ -46,7 +47,6 @@ - Keep generated makefiles platform-invariant [1] - Group non-template C++ implementations by class in `.cpp` files [1] - Use reentrant POSIX date-time conversions [1] -- Treat environment correlation as evidence, not a cause [1] # Refinements @@ -66,6 +66,8 @@ When an invariant says a value must exist or a conversion must succeed, prefer u Build-generation scripts must also preserve failures from compiler or dependency probes across command substitutions and formatting pipelines. Stop before emitting tracked output when a required header or package is missing, and declare the dependency in the canonical environment bootstrap so the original diagnostic is reported instead of a malformed generated rule. +For HTTP automation endpoints, return protocol errors such as 404 only for recognized request rejection. Unexpected parser, automation, callback, or invariant failures should reach a process-terminating boundary instead of being translated into a healthy-looking endpoint response. + ## Process staged tasks one by one with verification When a request is split into explicit tasks, complete and verify each task before starting the next one. This keeps commits easy to understand and review, limits side effects to the current task, and avoids having to diagnose many unrelated issues at the same time. If a task has its own finishing instructions, finish that task properly before moving on. @@ -204,6 +206,8 @@ When multiple handlers attached to the same `Event` can both observe, mutate, When a helper temporarily suppresses callbacks, changes ownership flags, or otherwise establishes scoped state, use a small scope object whose destructor restores state during normal return and exception unwinding. Avoid manual `try`/`catch` blocks that only restore state and rethrow; C++ stack unwinding should own that cleanup. +Do not add a scope wrapper solely to run normal-path finalization after an exception in a fail-fast test app. When the exception is intended to terminate the process and the OS releases the endpoint, keep ordinary shutdown calls straight-line at the end. Retain scoped ownership where destruction order prevents callbacks from reaching expired stack objects. + ## `collections::Dictionary` copy assignment is deleted (use move/swap) `collections::Dictionary` does not support copy assignment. When you need to replace one dictionary with another, use move semantics (when appropriate), or rebuild/swap explicitly instead of `a = b`. @@ -232,6 +236,8 @@ When generated build metadata must be platform-invariant, generate it with nativ When a generator produces runnable sample applications, verify the generated output through the actual app workflow too. For example, generated ChatBot RPC code should be checked by running the server and multiple clients through joins, chat messages, client exit, and server shutdown, not only by confirming generation succeeds. +When CodePack ownership is split, inspect the exact generated code pairs and their dependency boundaries. Confirm neutral output excludes platform-only dependencies, ordinary library pairs do not absorb the helper layer, and generated formatting is preserved rather than hand-edited. + When a shared dispatcher schema such as `Rpc.d.ts` changes, type-check the shared schema itself as well as generated fixtures so envelope changes are caught even before concrete generated values instantiate every request shape. For a released VlppOS namespace change, validate Workflow through the ChatBot SOP and validate GacUI through `RemotingTest_Core /RPT /Http` with `RemotingTest_Rendering_Win32 /Http`, plus GacJS against the HTTP remoting core. An upstream build alone does not prove the imported public surface works. @@ -302,6 +308,8 @@ When a layered transport needs a stricter response policy than its general parse When a bug reproduces on one machine but not another, do not assume hardware speed or timing is causal merely because the machines differ. Reproduce the smallest differing state or response shape deterministically, then identify the missing transition or dependency that the other environment happens to mask. +The same standard applies when severe CPU usage coincides with a debugger or terminal session: treat the tool/output feedback path as a hypothesis until resource evidence identifies the consuming process or thread. Do not turn an unobserved incident into a durable application-thread-leak diagnosis. + ## Verify and localize portability on every target OS Run the relevant tests on every target operating system whose behavior is being claimed, and report only the platforms actually exercised. Use contrasts between passing and failing platforms to narrow investigation toward the failing platform's implementation before changing shared code, while retaining cross-platform regression verification. Instructions may be prepared for an untested operating system only when they are clearly labeled untested; never report that platform as verified. diff --git a/.github/KnowledgeBase/manual/coding-agent/context.md b/.github/KnowledgeBase/manual/coding-agent/context.md index 4c664c77..5189877f 100644 --- a/.github/KnowledgeBase/manual/coding-agent/context.md +++ b/.github/KnowledgeBase/manual/coding-agent/context.md @@ -10,7 +10,7 @@ The copied `.github` folder contains several kinds of files: - `copilot-instructions.md`: the main instruction file. It tells the agent to read `Project.md`, use the knowledge base, and prefer provided scripts. - `Guidelines`: build, run, debug, source-file, coding, and GacUI resource instructions. - `prompts`: job prompts such as `ask.prompt.md`, `investigate.prompt.md`, `refine.prompt.md`, and `kb.prompt.md`. -- `Scripts`: Windows PowerShell wrappers for building, executing, debugging, and archiving task logs. +- `Scripts`: Windows PowerShell wrappers for building, executing, and archiving task logs. - `Ubuntu`: Linux build wrapper and helper commands. - `KnowledgeBase`: copied API, design, manual, and learning documents that the agent can read without network access. - `Learning`: project-local lessons that refine future agent behavior. diff --git a/.github/KnowledgeBase/manual/coding-agent/installing/windows.md b/.github/KnowledgeBase/manual/coding-agent/installing/windows.md index e53ccbe7..09bc4518 100644 --- a/.github/KnowledgeBase/manual/coding-agent/installing/windows.md +++ b/.github/KnowledgeBase/manual/coding-agent/installing/windows.md @@ -1,15 +1,16 @@ # Installing Required Tools on Windows -The Windows agent scripts build with MSBuild through Visual Studio's developer environment and debug with CDB. Install Visual Studio with the C++ desktop workload, a Windows SDK, and the Windows debugging tools that provide `cdb.exe`. If CDB is not available, install the Windows Driver Kit or the Windows debugging tools component from the Visual Studio Installer. +The Windows agent scripts build with MSBuild through Visual Studio's developer environment, and the agent debugs directly with CDB. Install Visual Studio with the C++ desktop workload, a Windows SDK, and the Windows debugging tools that provide `cdb.exe`. If CDB is not available, install the Windows Driver Kit or the Windows debugging tools component from the Visual Studio Installer. Define these environment variables before asking the agent to build or debug: - `VLPP_VSDEVCMD_PATH`: absolute path to `VsDevCmd.bat`, for example `C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat`. - `CDBPATH`: absolute path to `cdb.exe`, for example `C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe`. -After copying the Release `.github` folder, the agent should use these scripts instead of calling tools directly: +After copying the Release `.github` folder, the agent should use these scripts instead of calling build or execution tools directly: - `.github\Scripts\copilotBuild.ps1`: builds the solution found from `Project.md` context and writes `Build.log`. - `.github\Scripts\copilotExecute.ps1`: runs unit-test or CLI projects and writes `Execute.log` for unit tests. -- `.github\Scripts\copilotDebug_Start.ps1` and `copilotDebug_RunCommand.ps1`: start CDB and send debugger commands. + +Run CDB directly in an interactive terminal when debugging. If the application uses the tools shipped by Release, build them from the Release repository: - Open `Tools\Executables\Executables.sln` in Visual Studio. @@ -17,5 +18,5 @@ If the application uses the tools shipped by Release, build them from the Releas - Run `Tools\CopyExecutables.ps1`. - Confirm `CodePack.exe`, `CppMerge.exe`, `GacGen.exe`, and `GlrParserGen.exe` exist in `Release\Tools`. -For debugger readability, copy `Import\vlpp.natvis` from Release to Visual Studio's visualizers folder. The CDB startup script also loads the natvis file for debugger commands such as `dx`. +For debugger readability, copy `Import\vlpp.natvis` from Release to Visual Studio's visualizers folder. Load the natvis file in CDB for debugger commands such as `dx`. diff --git a/.github/KnowledgeBase/manual/coding-agent/jobs.md b/.github/KnowledgeBase/manual/coding-agent/jobs.md index 2a1e69c6..3d866367 100644 --- a/.github/KnowledgeBase/manual/coding-agent/jobs.md +++ b/.github/KnowledgeBase/manual/coding-agent/jobs.md @@ -1,6 +1,6 @@ # Investigate and Refine Jobs -`AGENTS.md` and `CLAUDE.md` route short request keywords to prompt files in `.github/prompts`. These jobs make agent work repeatable because the agent writes durable task documents, uses the copied knowledge base, and follows the repository's build and debug scripts. +`AGENTS.md` and `CLAUDE.md` route short request keywords to prompt files in `.github/prompts`. These jobs make agent work repeatable because the agent writes durable task documents, uses the copied knowledge base, and follows the repository's build, run, and debugging instructions. ## investigate diff --git a/.github/KnowledgeBase/manual/gacui/coding-agent/automation-service.md b/.github/KnowledgeBase/manual/gacui/coding-agent/automation-service.md index cc3a45fa..3c21ac07 100644 --- a/.github/KnowledgeBase/manual/gacui/coding-agent/automation-service.md +++ b/.github/KnowledgeBase/manual/gacui/coding-agent/automation-service.md @@ -8,7 +8,7 @@ The service is independent from the remote protocol. A normal Windows applicatio `INativeAutomationService` has one service-level availability flag and three feature groups: - `Available`: returns false when no automation service exists for the current controller. -- `Stop`: turns off all features. Windows implementations also stop the HTTP listener. +- `Stop`: turns off all features. Endpoint lifetime is owned separately. - `CanDumpControlTree` and `DumpControlTree`: expose visible GacUI windows, popups, controls and compositions. - `CanDumpDomTree` and `DumpDomTree`: expose the remote protocol renderer DOM. - `CanRunIOCommands` and `RunIOCommand`: send a textual IO command to the main window or to a selected native window. @@ -22,7 +22,7 @@ Feature availability is checked separately. A real service returns true from `Av ## Windows HTTP Layer -`StartWindowsHttpAutomationService` creates a localhost HTTP wrapper around the current `INativeAutomationService`. It is declared in `PlatformProviders/Windows/WinNativeWindow.h` and implemented by the Windows platform provider. If `GetCurrentController()->AutomationService()->Available()` is false, the function returns without starting a listener. +`StartWindowsHttpAutomationService` creates a localhost HTTP wrapper around the current `INativeAutomationService`. The test-support implementation lives in `Source/RemotingHelpers/AutomationService/Windows`, outside the ordinary `GacUI.Windows` library pair. Test applications consume it through the shared `Source_RemotingHelpers` project. The function takes `applicationName` as a URL path fragment and `port` as the localhost port. Given `applicationName == L"Automation/MyApp"` and `port == 8888`, the listener prefix is `http://localhost:8888/Automation/MyApp/`. The service offers exactly these HTTP URLs: - `GET http://localhost:8888/Automation/MyApp/Controls`: calls `DumpControlTree` on the UI thread when `CanDumpControlTree` is true. @@ -34,40 +34,31 @@ The window id is a path segment after `IO`, not a query parameter. All other met ## Starting The Service -Call `StartWindowsHttpAutomationService` from `GuiMain`, after the setup function has installed the current native controller and before entering the application event loop. Every code path that calls `StartWindowsHttpAutomationService` must later call `StopWindowsHttpAutomationService` before the native controller or substituted automation service is torn down. Skipping the stop leaks the process-wide HTTP service. Use a local guard or equivalent try/catch so the stop runs after the start on normal returns and exceptions. +Each application owns the automation service and endpoint directly. After the setup function has installed the current native controller, construct the concrete service matching that controller, substitute it, start an endpoint, and enter the application event loop. On exit, reverse the dependency order: stop the endpoint, stop the service, and finally unsubstitute it. A normal Windows application can start the service before `GetApplication()->Run`: ```c++ -#include "../../../Source/PlatformProviders/Windows/WinNativeWindow.h" +#include "../../../Source/RemotingHelpers/AutomationService/Windows/WindowsAutomationService.Windows.h" using namespace vl; using namespace vl::presentation; using namespace vl::presentation::controls; -class WindowsHttpAutomationServiceScope -{ -public: - WindowsHttpAutomationServiceScope(const WString& applicationName, vint port) - { - windows::StartWindowsHttpAutomationService(applicationName, port); - } - - ~WindowsHttpAutomationServiceScope() - { - windows::StopWindowsHttpAutomationService(); - } -}; - void GuiMain() { demo::MainWindow window; window.ForceCalculateSizeImmediately(); window.MoveToScreenCenter(); - WindowsHttpAutomationServiceScope httpAutomationService( + windows::WindowsAutomationServiceHosted automationService; + GetNativeServiceSubstitution()->Substitute(&automationService, false); + windows::StartWindowsHttpAutomationService( WString::Unmanaged(L"Automation/MyApp"), 8888); GetApplication()->Run(&window); + windows::StopWindowsHttpAutomationService(); + automationService.Stop(); + GetNativeServiceSubstitution()->Unsubstitute(&automationService); } int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int) @@ -76,46 +67,42 @@ int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int) } ``` -Repeated calls do not create multiple listeners. The Windows implementation keeps one process-wide HTTP service until `StopWindowsHttpAutomationService` stops it. +Only one substituted automation service and one endpoint may be active in a process. Duplicate starts fail immediately. `StartMiniHttpAutomationService(socketServer, applicationName)` follows the same lifecycle and URL contract, but requires the application to pass the exact `IAsyncSocketServer` that should host its automation prefix. ## Setup Function Cases -The setup function decides which controller and service are active while `GuiMain` runs. Use the following cases when deciding whether user code must substitute another automation service: -- `SetupWindowsGDIRenderer`: installs `WindowsAutomationService`. Use `StartWindowsHttpAutomationService` directly for multi-window control tree and IO. The DOM route is normally unavailable. -- `SetupWindowsDirect2DRenderer`: same automation behavior as `SetupWindowsGDIRenderer`, with the Direct2D renderer. -- `SetupHostedWindowsGDIRenderer`: installs `WindowsAutomationServiceHosted`. Use the HTTP service directly. IO should use `/IO` without a window id because hosted sub windows and popups are represented under the main window. -- `SetupHostedWindowsDirect2DRenderer`: same automation behavior as `SetupHostedWindowsGDIRenderer`, with the Direct2D renderer. -- `SetupRawWindowsGDIRenderer`: installs `WindowsAutomationService` for the raw native window controller. If the raw process is a remote protocol renderer and owns a `GuiRemoteRendererSingle`, substitute `WindowsAutomationServiceRenderer` to expose renderer DOM and renderer-side IO. -- `SetupRawWindowsDirect2DRenderer`: same automation behavior as `SetupRawWindowsGDIRenderer`, with the Direct2D renderer. +The setup function decides which controller is active while `GuiMain` runs; it no longer installs test automation implicitly. Select the matching helper service explicitly: +- `SetupWindowsGDIRenderer` and `SetupWindowsDirect2DRenderer`: construct `WindowsAutomationService` for multi-window control tree and IO. The DOM route is normally unavailable. +- `SetupHostedWindowsGDIRenderer` and `SetupHostedWindowsDirect2DRenderer`: construct `WindowsAutomationServiceHosted`. IO should use `/IO` without a window id because hosted sub windows and popups are represented under the main window. +- `SetupRawWindowsGDIRenderer` and `SetupRawWindowsDirect2DRenderer`: construct `WindowsAutomationService` for an ordinary raw native controller. A remote protocol renderer that owns a `GuiRemoteRendererSingle` constructs `WindowsAutomationServiceRenderer` with the renderer pointer to expose renderer DOM and renderer-side IO. - `SetupRemoteNativeController`: the remote controller itself does not return an automation service. Substitute `RemoteProtocolAutomationService` around `GetApplication()->Run` when the remote core should expose the hosted control tree and core-side IO. - `SetupGacGenNativeController`: this setup is for generation-time resource processing, not an interactive UI session. It does not provide an automation service for HTTP control. - `SetupGtkRenderer`: do not call the Windows HTTP helper. A Gtk port must provide its own endpoint layer and automation service implementation if it needs coding-agent automation. -- `SetupWGacRenderer`: do not call the Windows HTTP helper. A Wayland port must provide its own endpoint layer and automation service implementation if it needs coding-agent automation. -- `SetupWGacHostedRenderer`: same requirement as `SetupWGacRenderer`, but the service should follow hosted-mode window-id behavior if it exposes hosted windows. +- `SetupWGacRenderer`: construct the Wayland platform service such as `WGacAutomationServiceRenderer` and expose it through MiniHTTP. +- `SetupWGacHostedRenderer`: use the corresponding hosted Wayland service and hosted-mode window-id behavior. +- A macOS remote renderer constructs `CocoaAutomationServiceRenderer` and exposes it through MiniHTTP. ## Substituting a Service -Use `GetNativeServiceSubstitution()->Substitute(service, false)` before the automation service is first requested. The substitution layer rejects a late substitution after a service has already been used. Keep the substituted object alive until it is unsubstituted. +Use `GetNativeServiceSubstitution()->Substitute(service, false)` before the automation service is first requested. The substitution layer rejects a late substitution after a service has already been used. Keep the concrete service alive until the endpoint has stopped, `service.Stop()` has completed, and the service is unsubstituted. -A remote protocol core can expose the core-side automation surface like this. The sample uses the same `WindowsHttpAutomationServiceScope` guard from the normal Windows application example. +A remote protocol core owns its neutral service and endpoint directly: ```c++ void GuiMain() { RemoteProtocolAutomationService automationService; GetNativeServiceSubstitution()->Substitute(&automationService, false); - - { - WindowsHttpAutomationServiceScope httpAutomationService( - WString::Unmanaged(L"Automation/RemoteCore"), - 8888); - GetApplication()->Run(mainWindow); - } - + windows::StartWindowsHttpAutomationService( + WString::Unmanaged(L"Automation/RemoteCore"), + 8888); + GetApplication()->Run(mainWindow); + windows::StopWindowsHttpAutomationService(); + automationService.Stop(); GetNativeServiceSubstitution()->Unsubstitute(&automationService); } ``` -A remote protocol renderer that owns a `GuiRemoteRendererSingle` can substitute `WindowsAutomationServiceRenderer` in the same scope before starting the HTTP service. This is the case where `GET /Dom` becomes meaningful. +A Windows remote protocol renderer constructs `WindowsAutomationServiceRenderer` with its `GuiRemoteRendererSingle`, substitutes it, and owns either the Windows HTTP or MiniHTTP endpoint directly. Linux uses `WGacAutomationServiceRenderer`; macOS uses `CocoaAutomationServiceRenderer`; both use MiniHTTP. These renderer cases are where `GET /Dom` becomes meaningful. When a remote renderer is retained after a fatal remote-protocol error, renderer automation keeps `GET /Dom` available. The DOM response is still an HTTP success containing the frozen renderer DOM, and it adds a lowercase top-level `fatalError` string with the original error. Renderer IO switches to `ExitOnly`: ordinary IO returns exactly `!Application stopped responding.`, while exact `!Exit` is still accepted so automation can close the retained renderer. diff --git a/.github/KnowledgeBase/manual/gacui/modes/remote_client.md b/.github/KnowledgeBase/manual/gacui/modes/remote_client.md index 6ece18ab..9bcb4f60 100644 --- a/.github/KnowledgeBase/manual/gacui/modes/remote_client.md +++ b/.github/KnowledgeBase/manual/gacui/modes/remote_client.md @@ -15,7 +15,7 @@ using namespace vl::presentation::remote_renderer; GuiRemoteRendererSingle* remoteRenderer = nullptr; GuiRemoteProtocolAsyncJsonChannelRenderer* asyncChannel = nullptr; -class GuiMainInvoker : public IGuiRemoteProtocolAsyncRendererInvoker +class GuiMainInvoker : public Object, public virtual IGuiRemoteProtocolAsyncRendererInvoker { public: void InvokeInMainThread(const Func& proc) override @@ -29,9 +29,10 @@ void GuiMain() auto mainWindow = GetCurrentController()->WindowService()->CreateNativeWindow(INativeWindow::Normal); mainWindow->SetTitle(L"Connecting ..."); - GuiMainInvoker invoker; + auto invoker = Ptr(new GuiMainInvoker); remoteRenderer->RegisterMainWindow(mainWindow); - asyncChannel->SetInvokeInMainThread(&invoker); + asyncChannel->SetInvokeInMainThread(invoker); + asyncChannel->ProcessPendingMessages(); GetCurrentController()->WindowService()->Run(mainWindow); @@ -66,5 +67,4 @@ GuiRemoteProtocolAsyncJsonChannelRenderer queues messages from the channel and r The portable **/MiniHttp** path creates a default loopback TCP client with **vl::inter_process::async_tcp_socket::CreateDefaultAsyncSocketClient(port)**, wraps it in **vl::inter_process::async_tcp_socket::SocketHttpClient(socketClient, L"localhost", urlPrefix)**, and passes that object to **GuiRemoteProtocolChannelClient**. The renderer application shown by the linked test remains Win32 even though the VlppOS transport itself is available on Windows, Linux and macOS. -Override GuiRemoteProtocolChannelClient::OnReadError, OnLocalError, or OnDisconnected when the renderer should show a fatal error dialog or call GuiRemoteRendererSingle::ForceExitByFatelError. See [RemotingTest_Rendering_Win32](https://github.com/vczh-libraries/GacUI/tree/master/Test/GacUISrc/RemotingTest_Rendering_Win32) for the complete named-pipe (**/Pipe**), Windows HTTP.sys/WinHTTP (**/Http**) and portable Mini HTTP (**/MiniHttp**) implementations. - +Override `GuiRemoteProtocolChannelClient::OnReadError` when a Core-authored `!Error` should show fatal UI. Treat `OnLocalError(..., true)` as an independently complete, prompt-free disconnected transition and do not wait for `OnDisconnected`; use `OnDisconnected` as the idempotent fallback when it is delivered. See [RemotingTest_Rendering_Win32](https://github.com/vczh-libraries/GacUI/tree/master/Test/GacUISrc/RemotingTest_Rendering_Win32) for the complete named-pipe (**/Pipe**), Windows HTTP.sys/WinHTTP (**/Http**) and portable Mini HTTP (**/MiniHttp**) implementations. diff --git a/.github/KnowledgeBase/manual/vlppos/using-inter-process.md b/.github/KnowledgeBase/manual/vlppos/using-inter-process.md index b5a4fcf3..4b442832 100644 --- a/.github/KnowledgeBase/manual/vlppos/using-inter-process.md +++ b/.github/KnowledgeBase/manual/vlppos/using-inter-process.md @@ -172,6 +172,8 @@ The bridge uses **NetworkPackage** as the raw text envelope. Its string shape is A network channel client connects by sending one handshake package with an empty client id, an empty channel name and all supported channel names joined by **!**. The server validates the names, assigns a positive client id, records channel membership and sends the id back in an empty-channel response. After that, normal channel packages are delivered only when sender and receiver membership matches the channel name. +**BroadcastError** is a terminal admission boundary. The first broadcast error is retained, later broadcasts are ignored, and new network or local admissions are rejected. If an application **OnClientConnected** callback was already running, the server keeps the underlying transport alive until an accepted client receives that retained error and disconnects. A committed client is not included in the broadcast snapshot until its client-id response or local **OnConnected** callback completes, preserving connected-before-fatal ordering. Concurrent **Stop** calls share this barrier and cannot stop the transport early. Callback-reentrant terminal calls never wait on themselves; when broadcast owns the terminal boundary, physical stop is deferred until protected raw protocol, admission, fatal-delivery and disconnection callbacks unwind. If the last barrier is a raw protocol callback, completion moves to another thread so the underlying transport can drain that callback. A non-reentrant **Stop** reports a recorded shutdown exception once; a most-derived server destructor must call **Stop** before destroying callback-visible fields and catch any exception. + A channel server over a Windows named pipe can be declared like this: ```C++ #include @@ -265,7 +267,7 @@ The raw HTTP protocol uses these routes under the configured base URL: - **POST /VlppInterProcess/Request/GUID** is the client-maintained long-poll request for server-to-client messages. - **POST /VlppInterProcess/Response/GUID** sends client-to-server messages and may also receive one queued server-to-client message. -**vl::inter_process::windows_http::HttpClient::WaitForServer** sends the connect request, validates the returned URLs, records them and reports connection. **BeginReadingLoopUnsafe** starts the long-poll request loop. **SendString** posts to the response URL. Connect and response failures retry a limited number of times; request failures retry while the client is still running. +**vl::inter_process::windows_http::HttpClient::WaitForServer** sends the connect request, validates the returned URLs, records them and reports connection. **BeginReadingLoopUnsafe** starts the long-poll request loop. **SendString** posts to the response URL. Each recoverable failed Connect, Request or Response exchange calls **INetworkProtocolCallback::OnLocalError** with **fatal == false** before retrying; Connect and Response retry a limited number of times, while Request retries while the client is still running. Bounded retry exhaustion remains raw-fatal. Returning **true** from **OnLocalError** promotes a recoverable failure and makes the raw client stop after the callback returns. **NetworkProtocolChannelClient** does this for every local error after its channel reaches **Connected**, forwards **fatal == true** to its **IChannelClient** user and disconnects the channel; pre-handshake Connect retries remain under raw-client policy. **vl::inter_process::windows_http::HttpServer** creates a **vl::inter_process::windows_http::HttpServerConnection** for each connect request. Server-to-client messages are returned through a pending long-poll request when possible, or queued until the next request. Client-to-server request bodies are dispatched as inbound strings. When the server stops, pending long-poll requests are cancelled and connection callbacks receive disconnection. diff --git a/.github/Scripts/copilotDebug_Init.txt b/.github/Scripts/copilotDebug_Init.txt deleted file mode 100644 index 60b2bda8..00000000 --- a/.github/Scripts/copilotDebug_Init.txt +++ /dev/null @@ -1,4 +0,0 @@ -.lines -l+s -l+t -.nvload "C:\Program Files\Microsoft Visual Studio\18\Community\Common7\Packages\Debugger\Visualizers\vlpp.natvis" \ No newline at end of file diff --git a/.github/Scripts/copilotDebug_RunCommand.ps1 b/.github/Scripts/copilotDebug_RunCommand.ps1 deleted file mode 100644 index da7c5236..00000000 --- a/.github/Scripts/copilotDebug_RunCommand.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -param( - [string]$Command = $null -) - -if ([string]::IsNullOrEmpty($Command)) { - throw "\$Command parameter is required." -} - -. $PSScriptRoot\copilotShared.ps1 -$cdbpath = GetCDBPath -$debuggerNamedPipe = GetDebuggerNamedPipe -$commandLine = "echo .remote_exit | `"$($cdbpath)`" -remote npipe:server=.,pipe=$debuggerNamedPipe -clines 0 -c `"$Command`"" -echo $commandLine -cmd.exe /S /C $commandLine diff --git a/.github/Scripts/copilotDebug_Start.ps1 b/.github/Scripts/copilotDebug_Start.ps1 deleted file mode 100644 index c23b8374..00000000 --- a/.github/Scripts/copilotDebug_Start.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -param( - [string]$Executable = $null -) - -if ([string]::IsNullOrEmpty($Executable)) { - throw "\$Executable parameter is required." -} - -. $PSScriptRoot\copilotShared.ps1 - -# Ensure the executable name does not have the .exe extension -if ($Executable.EndsWith(".exe")) { - throw "\$Executable parameter should not include the .exe extension: $Executable" -} -$executableName = $Executable + ".exe" - -# Find the solution folder by looking for *.sln files -$solutionFolder = GetSolutionDir - -# Find the file with the latest modification time -$latestFile = GetLatestModifiedExecutable $solutionFolder $executableName -Write-Host "Selected $executableName`: $($latestFile.Path) (Modified: $($latestFile.LastWriteTime))" - -# Try to read debug arguments from the corresponding .vcxproj.user file -$debugArgs = GetDebugArgs $solutionFolder $latestFile $Executable - -$cdbpath = GetCDBPath -$debuggerNamedPipe = GetDebuggerNamedPipe -$commandLine = "`"$($cdbpath)`" -server npipe:pipe=$debuggerNamedPipe -cf `"$PSScriptRoot\copilotDebug_Init.txt`" -o `"$($latestFile.Path)`" $debugArgs" -echo $commandLine -cmd.exe /S /C $commandLine diff --git a/.github/Scripts/copilotShared.ps1 b/.github/Scripts/copilotShared.ps1 index ca12fe1e..306abc5a 100644 --- a/.github/Scripts/copilotShared.ps1 +++ b/.github/Scripts/copilotShared.ps1 @@ -1,18 +1,3 @@ -function GetCDBPath { - if ($env:CDBPATH -eq $null) { - $MESSAGE_1 = "You have to add an environment variable named CDBPATH and set its value to the path of cdb.exe, e.g.:" - $MESSAGE_2 = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe" - $MESSAGE_3 = "To get cdb.exe, you need to install WDK via VS Installer (Individual Components page) followed by Windows WDK." - throw "$MESSAGE_1\r\n$MESSAGE_2\r\n$MESSAGE_3" - } - return $env:CDBPATH -} - -function GetDebuggerNamedPipe { - $debuggerRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path - return ($debuggerRoot -replace "[^A-Za-z0-9]", "").ToUpperInvariant() -} - function GetSolutionDir { $currentDir = Get-Location $solutionFolder = $null diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e014713d..6c778624 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -17,6 +17,7 @@ - None of the test apps is going to be published, unnecessary error recovery will just cover mistakes up, preventing us from finding the root cause of the problem, making it harder to fix issues in libraries. - Defensive cleanup should always be avoided when affected errors and exceptions crash the app. Since the app is going to be killed, cleaning up just make the code more complex and gain no benefit. - Any `REPO-ROOT/Tools/` only allow very limited error recovery just to print error messages and exit. +- No heart beats or similar construction is needed, as all test apps are supposed to be running in the same computer, where the network quality is not an issue. ### For Multi-Process Communication @@ -28,8 +29,9 @@ ## (Windows Specific) External Tools Environment and Context -- Always prefer the offered script files instead of direct CLI commands. -- DO NOT call `msbuild` or other executable files directly. +- Always prefer the offered script files for building and running projects. +- DO NOT call `msbuild` directly. +- Run CDB directly following `REPO-ROOT/.github/Guidelines/Debugging.md`. - DO NOT create or delete any file unless explicitly directed. - MUST run any PowerShell script in this format: `& absolute-path.ps1 parameters...`. - Multiple PowerShell commands are concatenated with `;` to be executed in one line. @@ -104,8 +106,6 @@ If you need to find any document for the current working task, they are in the ` If you need to find any script or support files, they are in the `REPO-ROOT/.github/Scripts` folder: - `copilotBuild.ps1` - `copilotExecute.ps1` -- `copilotDebug_Start.ps1` -- `copilotDebug_RunCommand.ps1` - `copilotRemember.ps1` - `Build.log` - `Execute.log` diff --git a/.github/prompts/investigate.prompt.md b/.github/prompts/investigate.prompt.md index 3b01f5c1..1e956b60 100644 --- a/.github/prompts/investigate.prompt.md +++ b/.github/prompts/investigate.prompt.md @@ -1,7 +1,7 @@ # Investigate - Check out `Accessing Task Documents`, `(Windows Specific) Accessing Script Files`, and `(Linux Specific) Accessing Script Files` in `REPO-ROOT/.github/copilot-instructions.md` for context about mentioned `*.md`, `*.ps1` and `*.sh` files. -- Check out `(Windows Specific) External Tools Environment and Context` and `(Linux Specific) External Tools Environment and Context` in `REPO-ROOT/.github/copilot-instructions.md` for accessing scripts for testing and debugging. +- Check out `(Windows Specific) External Tools Environment and Context` and `(Linux Specific) External Tools Environment and Context` in `REPO-ROOT/.github/copilot-instructions.md` for accessing testing scripts and debugging tools. - Check out `REPO-ROOT/Project.md` to find out what solutions you need to build. - All `*.md`, `*.ps1` and `*.sh` files should exist; you should not create any new files unless explicitly instructed. - The `Copilot_Investigate.md` file should already exist, it may or may not contain content from the last investigation.