Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8c66406
Fix test failures.
mdaigle Nov 10, 2025
3c74365
Expose transacted connection in debug to assert transacted pool state.
mdaigle Nov 13, 2025
39b7851
Remove unnecessary context messages.
mdaigle Nov 13, 2025
1f4bc05
Test cleanup
mdaigle Nov 14, 2025
fab9a43
Convert TransactedConnections to auto-property.
mdaigle Nov 20, 2025
cddc9eb
Make TransactedConnectionPool accessible when not in DEBUG.
mdaigle Nov 20, 2025
9ba0b9c
Clean up and fix tests. Expose min and max pool size properties.
mdaigle Nov 21, 2025
e1853da
Merge branch 'main' of github.com:dotnet/SqlClient into dev/mdaigle/t…
mdaigle Nov 21, 2025
d4533c6
Clean up dependencies.
mdaigle Nov 21, 2025
6cd9e82
Add missing method override.
mdaigle Nov 21, 2025
d4b1e21
Fix shared transaction test case.
mdaigle Nov 21, 2025
e68ea14
Fix async single transaction test.
mdaigle Nov 21, 2025
1191ca4
Remove uninteresting test cases. Add todos for missing test cases.
mdaigle Nov 21, 2025
b969832
Add more test cases. Speed up tests.
mdaigle Nov 21, 2025
42c8fbd
Clean up new tests.
mdaigle Nov 24, 2025
1f31bd2
Improve cleanup
mdaigle Nov 24, 2025
90f2686
Fix copilot issues. Add doc comment.
mdaigle Nov 24, 2025
861b0e7
Address copilot comments.
mdaigle Nov 24, 2025
017353c
Fix tests. Expose TransactedConnectionPool.
mdaigle Nov 24, 2025
516ba13
Improve property accessors
mdaigle Mar 20, 2026
01f176c
Merge branch 'main' of github.com:dotnet/SqlClient into dev/mdaigle/t…
mdaigle Mar 20, 2026
4a85142
Add deterministic tests to unit tests. Move stress situations to stre…
mdaigle Mar 20, 2026
9057103
Remove stress test changes.
mdaigle Mar 23, 2026
700c7b0
Address comments
mdaigle Mar 26, 2026
c13eb6d
Clean up more tests
mdaigle Mar 26, 2026
da057ba
Merge branch 'main' of https://github.com/dotnet/SqlClient into dev/m…
mdaigle Apr 2, 2026
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
2 changes: 1 addition & 1 deletion .github/prompts/generate-doc-comments.prompt.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: doc-comments
name: generate-doc-comments
description: Generate XML documentation comments for C# code following .NET best practices.
argument-hint: <code>
agent: agent
Expand Down
63 changes: 62 additions & 1 deletion .github/prompts/generate-prompt.prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Before generating the prompt, review the available skills in the `.github/skills
* `name`: A concise, kebab-case name for the prompt.
* `description`: A clear, short description of what the prompt does.
* `argument-hint`: (Optional) A hint for what arguments the user can provide when using the prompt.
* `tools`: (Recommended) A list of tool identifiers the prompt is allowed to use. See the **Tool Scoping** section below.
* **Body Structure**:
* **Role**: Define the AI's persona (e.g., "You are an expert C# developer...").
* **Context**: Include specific context instructions or references.
Expand All @@ -50,19 +51,78 @@ Before generating the prompt, review the available skills in the `.github/skills
* Use `${input:variableName}` for user inputs (e.g., `${input:methodName}`).
* Use built-in variables like `${selection}`, `${file}`, or `${workspaceFolder}` where appropriate context is needed.

6. **Best Practices**:
6. **Scope Tools**: Restrict the tools available to each prompt using the `tools` frontmatter field. See the **Tool Scoping** section below for detailed guidance.

7. **Best Practices**:
* Be specific and explicit.
* Encourage chain-of-thought reasoning if the task is complex.
* Reference workspace files using Markdown links `[path/to/file.cs](path/to/file.cs)` only if they are static and necessary for *all* invocations of this prompt.
* Prefer referencing skills over duplicating instructions that already exist in skills.

## Tool Scoping

Every generated prompt **should** include a `tools` list in its YAML frontmatter. Scoping tools keeps the model focused by limiting it to approved, known-effective tools for the task. Without tool scoping, the model may invoke irrelevant tools, waste context, or produce unpredictable results.

### Why scope tools?
- **Focus**: Fewer tools means the model spends less reasoning on tool selection and more on the task.
- **Reliability**: Restricting to tested tools avoids unexpected side effects (e.g., a read-only review prompt shouldn't have edit tools).
- **Safety**: Prevents prompts from accidentally running terminal commands or making file changes when they shouldn't.

### How to choose tools
Apply the **principle of least privilege** — include only the tools the prompt actually needs:

| Prompt type | Recommended tools |
|---|---|
| **Read-only analysis** (review, triage, explain) | `read/readFile`, `search/codebase`, `search/textSearch` |
| **Code editing** (bug fix, feature, refactor) | `edit/editFiles`, `edit/createFile`, `read/readFile`, `search/codebase` |
| **Needs terminal** (build, test, scripts) | All of the above + `execute/runInTerminal`, `execute/getTerminalOutput` |
| **Needs GitHub data** (triage, release notes) | All of the above + `github/search_issues` or other GitHub tools |
| **Needs web content** (docs lookup) | `web/fetch` |

### Available built-in tool identifiers

You can specify individual tools or tool sets (which include all tools in that group).

**Tool sets** (use these to include all tools in a category):
- `edit` — File creation and editing tools
- `read` — File and notebook reading tools
- `search` — Codebase, text, and file search tools
- `execute` — Terminal, task, and notebook execution tools
- `web` — Web content fetching tools

**Commonly used individual tools:**

| Tool identifier | Purpose |
|---|---|
| `edit/editFiles` | Apply edits to existing files |
| `edit/createFile` | Create a new file |
| `read/readFile` | Read file contents |
| `read/problems` | Get workspace problems/diagnostics |
| `search/codebase` | Semantic code search |
| `search/textSearch` | Text/regex search in files |
| `search/fileSearch` | Search for files by glob pattern |
| `search/listDirectory` | List directory contents |
| `search/usages` | Find references and implementations |
| `execute/runInTerminal` | Run a shell command |
| `execute/getTerminalOutput` | Get terminal output |
| `execute/testFailure` | Get test failure details |
| `web/fetch` | Fetch a web page |

**Extension / MCP tools** can also be included using their identifier (e.g., `github/search_issues`). Use `<server-name>/*` to include all tools from an MCP server.

### Frontmatter syntax
```yaml
tools: ['read/readFile', 'search/codebase', 'edit/editFiles']
```

## Example Output Structure (with skill reference)

```markdown
---
name: my-new-prompt
description: specialized task description
argument-hint: input parameter hint
tools: ['edit/editFiles', 'read/readFile', 'search/codebase', 'execute/runInTerminal']
---
You are a specialized agent for...

Expand All @@ -89,6 +149,7 @@ Use ${input:param1} to...
name: my-new-prompt
description: specialized task description
argument-hint: input parameter hint
tools: ['read/readFile', 'search/codebase']
---
You are a specialized agent for...

Expand Down
12 changes: 7 additions & 5 deletions .github/prompts/refine-test-overlap.prompt.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
---
name: test-minimize-overlap
name: refine-test-overlap
description: Run coverage overlap analysis and suggest test suite optimizations
argument-hint: Test filter (e.g. FullyQualifiedName~MyTests) or describe the tests you want to analyze
agent: agent
tools: ['edit/editFiles', 'read/readFile', 'search/codebase', 'execute/runInTerminal', 'execute/getTerminalOutput']
---
You are an expert .NET Test Engineer specialized in optimizing test coverage and reducing technical debt.

Expand All @@ -10,19 +12,19 @@ Your task is to analyze the user's test suite using the `AnalyzeTestOverlap.ps1`

## Skills
This prompt leverages the following skills for specific sub-tasks:
- [generate-mstest-filter](../skills/generate-mstest-filter/SKILL.md) - For generating well-formed MSTest filter expressions
- [generate-mstest-filter](.github/skills/generate-mstest-filter/SKILL.md) - For generating well-formed MSTest filter expressions

## Tools
You have access to the analysis script at `[AnalyzeTestOverlap.ps1](./scripts/AnalyzeTestOverlap.ps1)`.
You have access to the analysis script at [AnalyzeTestOverlap.ps1](.github/prompts/scripts/AnalyzeTestOverlap.ps1).

## Workflow
1. **Parse or Generate Test Filter**:
* If `${input:filter}` is a valid MSTest filter expression (e.g., `FullyQualifiedName~MyTests`), use it directly.
* If `${input:filter}` is a loose description (e.g., "connection tests" or "SqlCommand class"), follow the instructions in the [generate-mstest-filter](../skills/generate-mstest-filter/SKILL.md) skill to generate a proper filter expression.
* If `${input:filter}` is a loose description (e.g., "connection tests" or "SqlCommand class"), follow the instructions in the [generate-mstest-filter](.github/skills/generate-mstest-filter/SKILL.md) skill to generate a proper filter expression.
* If `${input:filter}` is empty, ask the user for a test filter or description to target specific tests.

2. **Run Analysis**:
* Run the script using the filter: `.\scripts\AnalyzeTestOverlap.ps1 -Filter "<filter>"`.
* Run the script from the workspace root: `.\.github\prompts\scripts\AnalyzeTestOverlap.ps1 -Filter "<filter>"`.
* *Note*: The script produces a console summary and a `test-coverage-analysis.json` file.

3. **Review Overlap**:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ internal ChannelDbConnectionPool(
Identity = identity;
AuthenticationContexts = new();
MaxPoolSize = Convert.ToUInt32(PoolGroupOptions.MaxPoolSize);
TransactedConnectionPool = new(this);

_connectionSlots = new(MaxPoolSize);

Expand Down Expand Up @@ -148,6 +149,9 @@ public ConcurrentDictionary<
/// <inheritdoc />
public DbConnectionPoolState State { get; private set; }

/// <inheritdoc />
public TransactedConnectionPool TransactedConnectionPool { get; }

/// <inheritdoc />
public bool UseLoadBalancing => PoolGroupOptions.UseLoadBalancing;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ bool hasTransactionAffinity
_hasTransactionAffinity = hasTransactionAffinity;
}

/// <summary>
/// The time (in milliseconds) to wait for a connection to be created/returned before terminating the attempt.
/// </summary>
public int CreationTimeout
{
get { return _creationTimeout; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ internal interface IDbConnectionPool
/// </summary>
DbConnectionPoolState State { get; }

/// <summary>
/// Holds connections that are currently enlisted in a transaction.
/// </summary>
TransactedConnectionPool TransactedConnectionPool { get; }

/// <summary>
/// Indicates whether the connection pool is using load balancing.
/// </summary>
Expand All @@ -106,7 +111,7 @@ internal interface IDbConnectionPool
/// <param name="userOptions">The user options to use if a new connection must be opened.</param>
/// <param name="connection">The retrieved connection will be passed out via this parameter.</param>
/// <returns>True if a connection was set in the out parameter, otherwise returns false.</returns>
bool TryGetConnection(DbConnection owningObject, TaskCompletionSource<DbConnectionInternal> taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection);
bool TryGetConnection(DbConnection owningObject, TaskCompletionSource<DbConnectionInternal>? taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection);

/// <summary>
/// Replaces the internal connection currently associated with owningObject with a new internal connection from the pool.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal class TransactedConnectionPool
/// A specialized list that holds database connections associated with a specific transaction.
/// Maintains a reference to the transaction for proper cleanup when the transaction completes.
/// </summary>
private sealed class TransactedConnectionList : List<DbConnectionInternal>
internal sealed class TransactedConnectionList : List<DbConnectionInternal>
{
private readonly Transaction _transaction;

Expand Down Expand Up @@ -60,9 +60,6 @@ internal void Dispose()
}

#region Fields

private readonly Dictionary<Transaction, TransactedConnectionList> _transactedCxns;

private static int _objectTypeCount;
internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);

Expand All @@ -80,7 +77,7 @@ internal void Dispose()
internal TransactedConnectionPool(IDbConnectionPool pool)
{
Pool = pool;
_transactedCxns = new Dictionary<Transaction, TransactedConnectionList>();
TransactedConnections = new Dictionary<Transaction, TransactedConnectionList>();
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.TransactedConnectionPool.TransactedConnectionPool|RES|CPOOL> {0}, Constructed for connection pool {1}", Id, Pool.Id);
}

Expand All @@ -98,6 +95,8 @@ internal TransactedConnectionPool(IDbConnectionPool pool)
/// <value>The IDbConnectionPool instance that owns this transacted pool.</value>
internal IDbConnectionPool Pool { get; }

internal Dictionary<Transaction, TransactedConnectionList> TransactedConnections { get; }

Comment thread
mdaigle marked this conversation as resolved.
#endregion

#region Methods
Expand All @@ -123,9 +122,9 @@ internal TransactedConnectionPool(IDbConnectionPool pool)
TransactedConnectionList? connections;
bool txnFound = false;

lock (_transactedCxns)
lock (TransactedConnections)
Comment thread
paulmedynski marked this conversation as resolved.
{
txnFound = _transactedCxns.TryGetValue(transaction, out connections);
txnFound = TransactedConnections.TryGetValue(transaction, out connections);
}

// NOTE: GetTransactedObject is only used when AutoEnlist = True and the ambient transaction
Expand Down Expand Up @@ -182,10 +181,10 @@ internal void PutTransactedObject(Transaction transaction, DbConnectionInternal
// NOTE: because TransactionEnded is an asynchronous notification, there's no guarantee
// around the order in which PutTransactionObject and TransactionEnded are called.

lock (_transactedCxns)
lock (TransactedConnections)
{
// Check if a transacted pool has been created for this transaction
if ((txnFound = _transactedCxns.TryGetValue(transaction, out connections))
if ((txnFound = TransactedConnections.TryGetValue(transaction, out connections))
&& connections is not null)
{
// synchronize multi-threaded access with GetTransactedObject
Expand Down Expand Up @@ -213,14 +212,14 @@ internal void PutTransactedObject(Transaction transaction, DbConnectionInternal
transactionClone = transaction.Clone();
newConnections = new TransactedConnectionList(2, transactionClone); // start with only two connections in the list; most times we won't need that many.

lock (_transactedCxns)
lock (TransactedConnections)
{
// NOTE: in the interim between the locks on the transacted pool (this) during
// execution of this method, another thread (threadB) may have attempted to
// add a different connection to the transacted pool under the same
// transaction. As a result, threadB may have completed creating the
// transacted pool while threadA was processing the above instructions.
if (_transactedCxns.TryGetValue(transaction, out connections)
if (TransactedConnections.TryGetValue(transaction, out connections)
&& connections is not null)
{
// synchronize multi-threaded access with GetTransactedObject
Expand All @@ -238,7 +237,7 @@ internal void PutTransactedObject(Transaction transaction, DbConnectionInternal
// add the connection/transacted object to the list
newConnections.Add(transactedObject);

_transactedCxns.Add(transactionClone, newConnections);
TransactedConnections.Add(transactionClone, newConnections);
transactionClone = null; // we've used it -- don't throw it or the TransactedConnectionList that references it away.
}
}
Expand Down Expand Up @@ -297,9 +296,9 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra
// TODO: that the pending creation of a transacted pool for this transaction is aborted when
// TODO: PutTransactedObject finally gets some CPU time?

lock (_transactedCxns)
lock (TransactedConnections)
{
if (_transactedCxns.TryGetValue(transaction, out connections)
if (TransactedConnections.TryGetValue(transaction, out connections)
&& connections is not null)
{
bool shouldDisposeConnections = false;
Expand All @@ -319,7 +318,7 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra
if (0 >= connections.Count)
{
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.TransactedConnectionPool.TransactionEnded|RES|CPOOL> {0}, Transaction {1}, Removing List from transacted pool.", Id, transaction.GetHashCode());
_transactedCxns.Remove(transaction);
TransactedConnections.Remove(transaction);

// we really need to dispose our connection list; it may have
// native resources via the tx and GC may not happen soon enough.
Expand Down Expand Up @@ -351,4 +350,4 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,9 @@ public bool IsRunning
get { return State is Running; }
}

private int MaxPoolSize => PoolGroupOptions.MaxPoolSize;
internal int MaxPoolSize => PoolGroupOptions.MaxPoolSize;

private int MinPoolSize => PoolGroupOptions.MinPoolSize;
internal int MinPoolSize => PoolGroupOptions.MinPoolSize;

public DbConnectionPoolGroup PoolGroup => _connectionPoolGroup;

Expand All @@ -324,6 +324,8 @@ public bool IsRunning

private bool UsingIntegrateSecurity => _identity != null && DbConnectionPoolIdentity.NoIdentity != _identity;

public TransactedConnectionPool TransactedConnectionPool => _transactedConnectionPool;

Comment thread
mdaigle marked this conversation as resolved.
private void CleanupCallback(object state)
{
// Called when the cleanup-timer ticks over.
Expand Down Expand Up @@ -940,6 +942,8 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj

// If automatic transaction enlistment is required, then we try to
// get the connection from the transacted connection pool first.
// If automatic enlistment is not enabled, then we cannot vend connections
// from the transacted pool.
if (HasTransactionAffinity)
{
obj = GetFromTransactedPool(out transaction);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading.Tasks;
using System.Diagnostics;
using System.Data;
using System.Transactions;
Comment thread
mdaigle marked this conversation as resolved.
using Microsoft.Data.SqlClient;
using System.Xml;
Comment thread
mdaigle marked this conversation as resolved.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -668,13 +668,14 @@ internal class MockDbConnectionPool : IDbConnectionPool
public DbConnectionPoolGroupOptions PoolGroupOptions => throw new NotImplementedException();
public DbConnectionPoolProviderInfo ProviderInfo => throw new NotImplementedException();
public DbConnectionPoolState State => throw new NotImplementedException();
public TransactedConnectionPool TransactedConnectionPool => throw new NotImplementedException();
public bool UseLoadBalancing => throw new NotImplementedException();

public ConcurrentBag<DbConnectionInternal> ReturnedConnections { get; } = new();

public void Clear() => throw new NotImplementedException();

public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource<DbConnectionInternal> taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection)
public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource<DbConnectionInternal>? taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection)
{
throw new NotImplementedException();
}
Expand Down Expand Up @@ -739,4 +740,4 @@ internal override void ResetConnection()
}

#endregion
}
}
Loading
Loading