Skip to content

Registered server commands - Leave the connection of the caller alone - #10577

Open
andreasjordan wants to merge 1 commit into
developmentfrom
fix-regserver-connection-ownership
Open

Registered server commands - Leave the connection of the caller alone#10577
andreasjordan wants to merge 1 commit into
developmentfrom
fix-regserver-connection-ownership

Conversation

@andreasjordan

Copy link
Copy Markdown
Collaborator

Type of Change

Purpose

Seven sites in the registered server commands closed a connection they did not open. Two of them are Get- commands, so merely reading registered servers ended the session of the connection that was handed in - temp tables, SET options, session context - and the connection was silently reopened afterwards, so nothing looked wrong.

Measured on SQL Server 2019 with a temp table as the marker and Get-DbaDatabase as a control:

Get-DbaRegServerGroup                      session survived: False
Get-DbaRegServer                           session survived: False
Add-DbaRegServerGroup                      session survived: False
Move-DbaRegServerGroup                     session survived: False
Move-DbaRegServer                          session survived: False
Remove-DbaRegServer                        session survived: False
Remove-DbaRegServerGroup                   session survived: False
Get-DbaRegServerStore (control)            session survived: True
Get-DbaDatabase (control)                  session survived: True

This is the family that the inventory in #10554 missed, because the grep behind it matched Disconnect-DbaInstance and ConnectionContext.Disconnect() but not the generic .Disconnect() these use.

Approach

One fix, not seven. Removing the disconnect in Add-DbaRegServerGroup alone would change nothing, because it calls Get-DbaRegServerGroup, which disconnects as well - anything short of a decision covering the whole family leaves the symptom in place.

Get-DbaRegServerStore is where the connection is obtained, so it is where the answer is known. It asks Connect-DbaInstance through IsNewConnectionReference and records the result as IsNewConnection on the store.

A note property is safe here, unlike on a server object: the store is built fresh on every call and is never handed back to Connect-DbaInstance, so nothing can re-stamp it. That was the objection to a note property in #10554, and it does not apply to this object.

Disconnect-Regserver becomes the single place that acts on it. It already walked up the parents to find the connection; it now also accepts the store itself, and closes the connection only when the store says we opened it:

$i = 0
while ($null -ne $Server -and $null -eq $Server.ServerConnection -and $i++ -le 20) {
    $Server = $Server.Parent
}

if ($Server.ServerConnection -and $Server.IsNewConnection) {
    $Server.ServerConnection.Disconnect()
}

The six sites that closed the connection by hand now go through it too, so there is one place left in the family that can close a connection.

Commands to test

$server = Connect-DbaInstance -SqlInstance $instance -NonPooledConnection
$null = $server.ConnectionContext.ExecuteNonQuery("CREATE TABLE #marker (id INT)")

$null = Get-DbaRegServerGroup -SqlInstance $server -Id 1

$server.ConnectionContext.ExecuteScalar("SELECT COUNT(*) FROM #marker")   # 0, was: Invalid object name '#marker'

Tests

One regression context per fixed command, all on InstanceSingle, in the shape used for #10554: connect with -NonPooledConnection, create a temp table, call the command with that server object, assert the command still did its work and the temp table is still there. A pooled connection passes either way, so the tests have to be non-pooled.

Get-DbaRegServerStore.Tests.ps1 covers the mechanism itself: IsNewConnection is $true for an instance name and $false for a connection of the caller.

For the commands that take an -InputObject, the marker is created after the lookup on purpose. Get-DbaRegServer and Get-DbaRegServerGroup close the connection too, so a marker created before the lookup would measure them instead of the command under test.

53 tests across the eight files, all passing. Against development every one of the eight files fails on exactly its new assertion.

馃 Generated with Claude Code

Seven sites in the registered server commands closed a connection they did not
open, two of them Get- commands. Reading registered servers ended the session of
the connection that was handed in - temp tables, SET options, session context -
and the connection was silently reopened afterwards, so nothing looked wrong.

They share one mechanism, so this is one fix and not seven: removing the
disconnect in Add-DbaRegServerGroup alone would change nothing, because it calls
Get-DbaRegServerGroup, which disconnects as well.

Get-DbaRegServerStore is where the connection is obtained, so it is where the
answer is known. It asks Connect-DbaInstance through IsNewConnectionReference and
records the result as IsNewConnection on the store, which is safe because the
store is built fresh per call and never handed back to Connect-DbaInstance.

Disconnect-Regserver becomes the single place that acts on it: it walks up from
any object of the registered server tree to the store and closes the connection
only when the store says we opened it. The six sites that closed the connection
by hand now go through it as well.

Measured on SQL Server 2019 with a temp table as the marker, session survived:

    before   after
    False    True    Get-DbaRegServerGroup
    False    True    Get-DbaRegServer
    False    True    Add-DbaRegServerGroup
    False    True    Move-DbaRegServerGroup
    False    True    Move-DbaRegServer
    False    True    Remove-DbaRegServer
    False    True    Remove-DbaRegServerGroup

(do *RegServer*)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Registered server commands disconnect the connection of the caller, including the Get- commands

1 participant