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
30 changes: 28 additions & 2 deletions src/System.Management.Automation/engine/InitialSessionState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1309,7 +1309,7 @@ public void Add(T item)
}

/// <summary>
///
/// Add items to this collection.
/// </summary>
/// <param name="items"></param>
public void Add(IEnumerable<T> items)
Expand All @@ -1326,6 +1326,27 @@ public void Add(IEnumerable<T> items)
}
}

/// <summary>
/// Special add for TypeTable type entries that removes redundant file entries.
/// </summary>
internal void AddTypeTableTypesInfo(IEnumerable<T> items)
{
if (typeof(T) != typeof(SessionStateTypeEntry)) { throw new PSInvalidOperationException(); }

lock (_syncObject)
{
foreach (var element in items)
{
var typeEntry = element as SessionStateTypeEntry;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your check at the start of the function means you can cast instead of using as, which you should prefer because you're not testing if typeEntry is null. And I suppose we don't allow adding null entries?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought so too but a direct cast isn't allowed:

var typeEntry = (SessionStateTypeEntry)element;
"Cannot covert type T to SessionStateTypeEntry"
It looks like a runtime cast is needed. Or is there another way?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why a cast is not allowed - T is even constrained to be InitialSessionStateEntry, so C# should be smart enough to allow the cast. Oh well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also tried using a type constrained on the method but that failed as well because SessionStateTypeEntry is a sealed class which is disallowed as a constraint...

if (typeEntry.TypeData != null)
{
// Skip type file entries.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if the scenario is supported, but does Update-TypeData and Remove-TypeData work with shared type tables? And if so, will that scenario continue to work with these changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update-TypeData and Remove-TypeData work directly on TypeTable and nothing has changed there and the TypeTables remain the same. This bug is when passing the TypeTable to the ISS so I think it is Ok. But I am running regression tests to ensure nothing is broken.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be surprised if we have tests covering the scenario - I think you'll need to write some new tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As near as I can tell these cmdlets have nothing to do with the bug I fixed. They affect the TypeTable directly (add/remove) but are not involved with ISS binding to a runspace where the fix is. Maybe I am missing something but I don't see a scenario that needs testing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the TypeTable is shared. If one changes the TypeTable in one runspace, then uses that shared TypeTable to construct another runspace, does your code handle that correctly?

I think this is a non-issue because changing a shared type table should not be allowed, but I was asking that you confirm that, preferably with tests because I wasn't confident your code was correct if it was allowed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I understand you now. I agree that this is a non-issue, not necessarily because changing a shared type table is not allowed but because the TypeTable is consistent in how it stores types. But I'll add a test for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I verified that Update-TypeData and Remove-TypeData does not affect ISS TypeTable. I also verified that the code disallows a shared TypeTable in the ISS (unless it is the only entry), and added a test for this.

_internalCollection.Add(element);
}
}
}
}

/// <summary>
/// Get enumerator for this collection.
/// </summary>
Expand Down Expand Up @@ -3851,7 +3872,12 @@ internal void UpdateTypes(ExecutionContext context, bool updateOnly)
context.TypeTable = typeTable;

Types.Clear();
Types.Add(typeTable.typesInfo);

// A TypeTable contains types info along with type file references used to create the types info,
// which is redundant information. When resused in a runspace the ISS unpacks the file types again
// resulting in duplicate types and duplication errors when processed.
// So use this special Add method to filter all types files found in the TypeTable.
Types.AddTypeTableTypesInfo(typeTable.typesInfo);

return;
}
Expand Down
13 changes: 13 additions & 0 deletions test/powershell/Common/TestTypeFile.ps1xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>

<Types>
<Type>
<Name>System.Array</Name>
<Members>
<AliasProperty>
<Name>Counts</Name>
<ReferencedMemberName>Length</ReferencedMemberName>
</AliasProperty>
</Members>
</Type>
</Types>
73 changes: 72 additions & 1 deletion test/powershell/engine/InitialSessionState.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,75 @@ Describe "InitialSessionState capacity" -Tags CI {
$ps.AddScript('New-Alias -Name a5000 -Value f1; a5000').Invoke() | Should Be "fn f1"
$ps.Streams.Error | Should Be $null
}
}
}

##
## A reused InitialSessionState created from a TypeTable should not have duplicate types.
##
Describe "TypeTable duplicate types in reused runspace InitialSessionState TypeTable" -Tags 'Feature' {

Context "No duplicate types test" {

BeforeAll {

$typeTable = [System.Management.Automation.Runspaces.TypeTable]::new([string[]](Join-Path $PSScriptRoot "../Common/TestTypeFile.ps1xml"))
[initialsessionstate] $iss = [initialsessionstate]::Create()
$iss.Types.Add($typeTable)
[runspace] $rs1 = [runspacefactory]::CreateRunspace($iss)

# Process TypeTable types from ISS
$rs1.Open()

# Get processed ISS from runspace.
$issReused = $rs1.InitialSessionState.Clone()
$issReused.ThrowOnRunspaceOpenError = $true

# Create new runspace with reused ISS.
$rs2 = [runspacefactory]::CreateRunspace($issReused)
}

AfterAll {

if ($rs1 -ne $null) { $rs1.Dispose() }
if ($rs2 -ne $null) { $rs2.Dispose() }
}

It "Verifies that a reused InitialSessionState object created from a TypeTable object does not have duplicate types" {

{ $rs2.Open() } | Should Not Throw
}
}

Context "Cannot use shared TypeTable in ISS test" {

BeforeAll {

# Create default ISS and add shared TypeTable.
$typeTable = [System.Management.Automation.Runspaces.TypeTable]::new([string[]](Join-Path $PSScriptRoot "../Common/TestTypeFile.ps1xml"))
[initialsessionstate] $iss = [initialsessionstate]::CreateDefault2()
$iss.Types.Add($typeTable)
$iss.ThrowOnRunspaceOpenError = $true
[runspace] $rs = [runspacefactory]::CreateRunspace($iss)
}

AfterAll {

if ($rs -ne $null) { $rs.Dispose() }
}

It "Verifies that shared TypeTable is not allowed in ISS" {

# Process TypeTable types from ISS.
$errorId = ""
try
{
$rs.Open()
throw "No Exception!"
}
catch
{
$_.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should Be "ErrorsUpdatingTypes"
}
}
}
}