Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ jobs:
python3 -m unittest discover -s eng -p test_candidate_preflight.py
python3 -m unittest discover -s eng -p test_native_postgresql.py
python3 -m unittest discover -s eng -p test_analyze_windows_hang.py
python3 -m unittest discover -s eng -p test_development_dependencies.py
python3 -m unittest discover -s eng -p test_windows_stress.py
python3 eng/validate_candidate.py

- name: Restore
Expand All @@ -86,15 +88,7 @@ jobs:
env:
PGCLI_TEST_TRACE_DIRECTORY: ${{ github.workspace }}/test-results/process-stages
if: runner.os == 'Windows'
shell: pwsh
run: |
foreach ($tfm in @('net48', 'net8.0', 'net10.0')) {
$rounds = if ($tfm -eq 'net48') { 30 } else { 3 }
1..$rounds | ForEach-Object {
dotnet test tests/PgCliSharp.Tests/PgCliSharp.Tests.csproj -c Release -f $tfm --no-build --no-restore --filter 'FullyQualifiedName~PsqlCompletionTests|FullyQualifiedName~ProcessSessionRunnerTests' --logger trx --results-directory "test-results/stress-$tfm-$_" --blame-hang-timeout 2m --blame-hang-dump-type full --diag "test-results/stress-$tfm-$_/vstest.log"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
}
run: python eng/run_windows_stress.py test-results

- name: Read Windows hang stacks and remove intermediate dumps
if: always() && runner.os == 'Windows'
Expand Down Expand Up @@ -155,6 +149,7 @@ jobs:
artifacts/*.nupkg
artifacts/*.snupkg
artifacts/DEVELOPMENT-PACKAGE-PROVENANCE.txt
artifacts/DEVELOPMENT-DEPENDENCIES.json
if-no-files-found: error

release-preflight:
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ If an Accepted decision changes, create a new superseding ADR instead of rewriti
- Prefer enums for finite option sets and dedicated value objects for structured values.
- Do not expose arbitrary command-line strings as the primary API when a typed representation is practical.
- Validate option availability and incompatible combinations before starting the process.
- Use `ProcessStartInfo.ArgumentList` on modern targets. The `netstandard2.0` compatibility backend uses CliWrap according to ADR-0008. Do not execute PostgreSQL tools through `cmd.exe`, PowerShell, `bash -c`, or another shell.
- Use `ProcessStartInfo.ArgumentList` on modern targets. The `netstandard2.0` compatibility backend shares the Process lifecycle with attributed native argument serialization, race-safe exit events and best-effort legacy Windows tree termination according to ADR-0024. Do not execute PostgreSQL tools through `cmd.exe`, PowerShell, `bash -c`, or another shell.
- Keep the core package free of unnecessary runtime dependencies.
- Public APIs must have bilingual English/Japanese XML documentation according to `docs/localization.md`.
- Human-facing project surfaces must provide Japanese as well as English. This includes the repository README, GitHub Release titles/notes, user-oriented guides, and other prominent public documentation. The root README may link prominently to a maintained `README.ja.md` instead of duplicating the full text inline.
Expand Down
2 changes: 1 addition & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ dotnet test PgCliSharp.slnx --configuration Release --no-build --no-restore

ターゲットフレームワークは `netstandard2.0;net8.0;net10.0` です。

- `netstandard2.0`: ADR-0008 に基づき、内部のプロセス実行互換層として CliWrap を使用します。
- `netstandard2.0`: ADR-0024に基づき、Processの終了監視を共有し、内部で引数整形・終了通知を補います。System.ManagementでWindows子孫終了を試み、CliWrap依存は外します。子孫終了APIのない古いUnixランタイムでは直接の子のみ停止できます。固定alpha.2候補は旧実装のままです。
- `net8.0` / `net10.0`: .NET BCL のプロセス API を直接使用します。

## ライセンス
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ dotnet build PgCliSharp.slnx --configuration Release --no-restore
dotnet test PgCliSharp.slnx --configuration Release --no-build --no-restore
```

The `netstandard2.0` process backend uses CliWrap internally according to ADR-0008. Modern targets use the .NET BCL process APIs directly.
All targets use the .NET BCL Process lifecycle. Under ADR-0024, `netstandard2.0` adds internal argument/exit compatibility and uses System.Management for best-effort Windows descendant termination; CliWrap is no longer a dependency. Modern targets use ArgumentList and native asynchronous exit/tree APIs. Older Unix runtimes without tree termination support can terminate only the immediate child. The preserved alpha.2 candidate still contains its original backend.

## License

Expand Down
27 changes: 18 additions & 9 deletions THIRD-PARTY-NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,20 @@ PgCliSharp packages contain PgCliSharp assemblies and documentation, not
PostgreSQL, libpq, .NET runtime binaries or dependency assemblies. NuGet resolves
the following dependencies separately for netstandard2.0. This inventory records
the reviewed lock; applications must preserve notices for their actual resolution
and redistributed files. Modern net8.0/net10.0 assets have no CliWrap dependency.
and redistributed files. Modern net8.0/net10.0 assets have no execution runtime dependencies. This inventory
applies to the ADR-0024 development source; frozen alpha.2 retains its own notices
and lock at its immutable source.

| Package | Reviewed version | License | Package copyright |
|---|---|---|---|
| CliWrap | 3.10.5 | MIT | Copyright (C) Oleksii Holub |
| Microsoft.Bcl.AsyncInterfaces | 10.0.8 | MIT | © Microsoft Corporation. All rights reserved. |
| System.Buffers | 4.6.1 | MIT | © Microsoft Corporation. All rights reserved. |
| System.CodeDom | 10.0.10 | MIT | © Microsoft Corporation. All rights reserved. |
| System.Management | 10.0.10 | MIT | © Microsoft Corporation. All rights reserved. |
| System.Memory | 4.6.3 | MIT | © Microsoft Corporation. All rights reserved. |
| System.Numerics.Vectors | 4.6.1 | MIT | © Microsoft Corporation. All rights reserved. |
| System.Runtime.CompilerServices.Unsafe | 6.1.2 | MIT | © Microsoft Corporation. All rights reserved. |
| System.Threading.Tasks.Extensions | 4.6.3 | MIT | © Microsoft Corporation. All rights reserved. |

The expressions and copyright strings are from the exact NuGet packages.
The standard MIT permission/disclaimer below accompanies these notices; it does
not replace an upstream package's own files or grant additional rights.
The .NET packages also retain attribution to the .NET Foundation and Contributors.
Microsoft.Bcl.AsyncInterfaces, System.CodeDom and System.Management supply the same
System.CodeDom and System.Management supply the same
upstream [additional notices](docs/third-party/dotnet-notices.txt), preserved byte
for byte (SHA256 `6d15e10a101c6bfff2ab4429ed061bf76c456fc4b23ad6b03e0d0f8377148a21`).
Keeping that entire upstream notice does not assert all its components are used.
Expand All @@ -38,6 +33,16 @@ obtaining/using or redistributing them. The candidate audit retains the original
text/hash and rejects unexpected shipped files. This review covers the PgCliSharp
nupkg/snupkg boundary, not every downstream or self-contained application.

### Attributed native argument serialization

ProcessCompatibility.QuoteArgument adapts the .NET Foundation PasteArguments
algorithm, replacing its internal buffer with StringBuilder. Source:
https://github.com/dotnet/runtime/blob/9a50493f9f1125fda5e2212b9d6718bc7cdbc5c0/src/libraries/System.Private.CoreLib/src/System/PasteArguments.cs

Copyright (c) .NET Foundation and Contributors. Licensed under MIT. The following
permission and disclaimer apply to this adapted source as well as the MIT
packages listed above. No CliWrap/PolyShim implementation is vendored.

### MIT permission text

Permission is hereby granted, free of charge, to any person obtaining a copy
Expand Down Expand Up @@ -67,3 +72,7 @@ PgCliSharpは外部CLI・libpq・.NETランタイム・依存DLLを同梱しま
古いMicrosoft独自ライセンスの参照パッケージはMITと読み替えず、原文・hashを監査記録へ残します。
今回の配布物にそれらが入らないことを確認し、その範囲で配布境界のレビューを完了します。
別のアプリや.NET/PostgreSQLを同梱する配布物の条件までは代行しません。

引数整形は.NET FoundationのPasteArgumentsをStringBuilder向けに改変したMITコードです。
上記の著作権・許諾文を保持します。CliWrap/PolyShimのコードは取り込みません。
固定alpha.2の旧依存・表示はimmutable sourceに保持し、新しい開発ソースと区別します。
4 changes: 2 additions & 2 deletions docs/adr/0008-netstandard20-cliwrap-process-backend.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# ADR-0008: Use a conditional CliWrap backend for .NET Standard 2.0 process execution

- Status: Accepted
- Status: Superseded
- Date: 2026-09-18
- Decision owners: PgCliSharp maintainers
- Supersedes: ADR-0005
- Superseded by: None
- Superseded by: ADR-0024

## Context

Expand Down
68 changes: 68 additions & 0 deletions docs/adr/0023-isolate-legacy-startup-synchronization-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# ADR-0023: Isolate the legacy backend's startup synchronization context

- Status: Superseded
- Date: 2026-10-04
- Superseded by: ADR-0024
- Related: ADR-0008, ADR-0013, ADR-0014, ADR-0017

## Context

Exact-main CI 37188258263 reproduced a net48 completion stall. The actual CLR4
dump showed all four CliWrap process/input/output/error tasks normally completed
while the outer async enumeration and wrapper completion remained pending.
This narrows the captured incident but does not by itself identify which caller
context or continuation mechanism caused the historical stall.

Pinned CliWrap 3.10.5 uses an unconfigured `await foreach` over its internalized
PolyShim Task.WhenEach. Awaiting the returned command with ConfigureAwait(false)
does not prevent the dependency's own awaits from capturing the startup context.
Draft PR #25 baseline CI 37188939390 confirms this separate, deterministic defect:
session and one-shot execution both fail to complete under an occupied queued
context on net48 (two posts each); modern targets pass. The one-shot fixture is
gated until startup returns, preventing a synchronous early-exit false positive.

## Decision

Keep the target matrix, CliWrap 3.10.5 dependency and shell-free execution policy.
Initialize CliWrap's command task synchronously under a null SynchronizationContext
for both finite and redirected execution. Restore the caller's context in finally,
including synchronous startup failures. Continue observing the returned task with
ConfigureAwait(false), mapping results/failures through existing PgCliSharp types.

Do not wrap startup in Task.Run, block on asynchronous startup, suppress the wider
ExecutionContext, or change culture/cancellation/timeout/stream ownership. This
scope only prevents capture of the caller's SynchronizationContext by dependency
initialization; it does not promise arbitrary custom TaskScheduler independence.
No public API or runtime dependency changes are needed.

## Consequences and validation

The compatibility backend's internal completion no longer requires the caller's
startup synchronization context to pump callbacks. The caller context is restored
before Start returns and after start errors. Existing synchronous startup error
translation remains intact.

The deterministic regressions must turn green on net48 and remain green on modern
targets; run the existing full suites, bounded Windows 30/3/3 stress, real PostgreSQL
and candidate audits at the final PR head and exact main commit. Preserve failed
baseline/main runs, and inspect actual diagnostic artifacts. Diagnostic heap filters
also include the dependency's internalized MemberPolyfills types, whose names do
not contain the PolyShim namespace.

The reproduced context defect is confirmed; attributing every historical stall to
it would exceed the evidence. A passing bounded run is regression evidence, not
proof of absence of all intermittent process defects. 1.0 acceptance and postponed
publication remain separate.

CI 37189457631 confirms the limitation: the context regressions pass, but net48
stress round 27 still stalls with all four lower tasks complete and a yielded
WhenEach value awaiting consumer progress. Context isolation is a confirmed
partial fix; final merge gates remain blocked. Nested promise/awaiter diagnostics
are expanded before choosing any additional runtime/dependency change.

## Primary-source evidence

- [Pinned CliWrap execution](https://github.com/Tyrrrz/CliWrap/blob/3.10.5/CliWrap/Command.Execution.cs)
- [Pinned PolyShim WhenEach](https://github.com/Tyrrrz/PolyShim/blob/2.12.2/PolyShim/Net90/Task.cs)
- [Framework Task state flags](https://github.com/microsoft/referencesource/blob/main/mscorlib/system/threading/Tasks/Task.cs)
- [Investigation and preserved receipts](../phase-8-net48-investigation.md)
87 changes: 87 additions & 0 deletions docs/adr/0024-share-process-backend-with-legacy-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# ADR-0024: Share the Process backend with narrow legacy compatibility

- Status: Accepted
- Date: 2026-10-04
- Related: ADR-0008, ADR-0013, ADR-0014, ADR-0023
- Supersedes: ADR-0008, ADR-0023

## Context

The exact-main CLR4 capture and the context-isolated PR #25 capture both show
CliWrap's four lower tasks normally completed while its async enumeration and
outer command remain pending. The deterministic startup-context defect is real,
but isolating startup does not resolve the intermittent stall. Failed runs and
artifact digests are preserved in the [investigation](../phase-8-net48-investigation.md).
Nested-diagnostic CI 37191060046 again aborts net48 in the initial full suite.
Its actual CLR4 promise version and consumer token both equal 3, with iterator
state -4 and all four lower tasks complete. Pinned PolyShim's promise uses lazy
unsynchronized TCS initialization, which introduces a potential race; the capture
does not prove the continuation graph or establish this as the root cause.
The backend replacement removes that iterator/promise transition entirely.

The maintainer authorizes consideration of replacing CliWrap if necessary. The
target matrix, public API, binary I/O, caller stream ownership, cancellation and
timeout contracts remain requirements; removing netstandard2.0 is not proposed.

## Alternatives

| Approach | Benefit | Cost / acceptance condition |
|---|---|---|
| Fix the pinned dependency upstream | Retains upstream process maintenance | A demonstrated fix must cover both the deterministic context defect and the captured completion stall; a newer fixed release is currently unavailable |
| Vendor/fork CliWrap and change its async loop | Can remove the observed iterator transition | Carries an entire process library and upstream patch/license maintenance; changing that loop alone is not proof of a root fix |
| Share the existing Process backend, add narrow compatibility | Removes the suspect command/iterator completion path and several legacy dependencies | Own argument serialization, race-safe exit waiting and legacy tree termination must receive explicit review and runtime tests |
| Keep the current adapter and repeat until green | Small code change | Rejected: it already reproduced the stall and a later pass would not explain it |

## Decision

- Reuse the existing finite/session Process lifecycle and its explicit awaits,
fault supervision, original deadline through output draining, and bounded
abnormal task observation.
- Keep modern ArgumentList, WaitForExitAsync and Kill(entireProcessTree: true).
- For netstandard2.0, serialize argument tokens using the .NET Foundation's MIT
PasteArguments rules with attribution and execution tests for empty values,
quotes, Unicode, whitespace and trailing backslashes. Never add a shell.
- Wait for legacy exit with an asynchronously continuing TaskCompletionSource,
subscribe before checking HasExited, and detach the event handler after exit.
- Retain Windows best-effort descendant discovery using System.Management
10.0.10, already present transitively, instead of adding job-object/native
launch machinery. Document unsupported legacy platform limits honestly.
- Retain the 64 KiB / 16-segment legacy input bridge, feed it directly to stdin,
and deliver EOF only after accepted bytes drain. Exit/cancel/disposal must
release blocked producers without disposing caller streams.
- Preserve the former legacy finite-command EOF when no stdin source is supplied.
Modern finite commands keep their existing inherited-input behavior. Sessions
always redirect stdin on every target and require explicit EOF unless the child
exits first. A dedicated net48 regression verifies the legacy no-source case.
- Remove CliWrap and its now-unneeded async-iterator dependencies from new
package assets. Do not retarget frozen alpha.2 or alter its dependency lock.

## Acceptance gates

Build all three TFMs; exercise netstandard2.0 on Windows net48. Controlled-context
regressions, binary equality/EOF, early exit, nonzero status, stderr/environment,
caller-stream faults, cancellation/timeout and descendant termination must pass.
Run bounded Windows 30/3/3 stress without retrying failures, review actual TRX and
stage artifacts, and require fresh full PR CI plus exact-main CI. Native PostgreSQL
3-OS and Linux 10–18 evidence remain required. Inspect the new dependency graph
and notices separately from the preserved preview candidate.

This decision replaces the legacy completion path; it is not proof that every
historical stall shares one cause. A replacement changes the owned lifecycle path; its
evidence must establish maintained behavior rather than merely a passing retry.
External publication stays postponed.

## Primary sources

- [Pinned CliWrap execution](https://github.com/Tyrrrz/CliWrap/blob/45819e3bc44d9be4d778e91a0ec5cf6a5176de56/CliWrap/Command.Execution.cs)
- [Pinned PolyShim legacy tree termination](https://github.com/Tyrrrz/PolyShim/blob/2.12.2/PolyShim/NetCore30/Process.cs)
- [.NET argument serialization](https://github.com/dotnet/runtime/blob/9a50493f9f1125fda5e2212b9d6718bc7cdbc5c0/src/libraries/System.Private.CoreLib/src/System/PasteArguments.cs)

## 日本語

CliWrapの下位4Taskが完了した後も上位が保留される停止が、同期コンテキスト修正後も
再現しました。必要なら依存を置き換える方針を検討します。既存のProcess実装を共有し、
古いAPIに不足する引数整形・終了通知・Windows子孫終了だけを補う案を優先します。
対応TFM・公開API・バイナリI/O・入力上限・呼出元ストリームの所有権・期限を維持します。
互換処理の保守責任は本プロジェクトへ移るため、実行テスト・依存と権利表示の確認・
最終PRとmainのCIを移行条件とします。移行の最終PR/main検証が済むまで完了扱いにはせず、alpha.2と公開延期は保持します。
Loading
Loading