Skip to content

Commit 96f8964

Browse files
authored
Fix erroneous debug.assert in SessionManager (#3759)
* Erroneous debug.assert in SessionManager * Improve build pipelines
1 parent 46db944 commit 96f8964

7 files changed

Lines changed: 1002 additions & 86 deletions

File tree

.azurepipelines/get-matrix.ps1

Lines changed: 133 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,178 @@
11
<#
22
.SYNOPSIS
3-
Creates buildjob matrix based on the specified file names in the tree.
3+
Creates a job-matrix variable for downstream pipeline jobs.
44
55
.DESCRIPTION
6-
The script traverses the build root to find all folders with a matching
7-
file and populates the matrix. The matrix is used to spawn jobs that
8-
run on multiple different environments for each file. E.g. build all
9-
solution files on all platforms, run tests per particular folder of
10-
the tree, etc.
6+
Discovers files matching a pattern in the repository (or uses an explicit
7+
caller-supplied list) and emits a pipeline output variable named
8+
"jobMatrix" containing one matrix entry per (file × agent × configuration)
9+
tuple. Either the agent or the configuration dimension may be left empty
10+
in which case the matrix is not fanned out across that dimension.
11+
12+
Each matrix entry carries these variables:
13+
folder - directory containing the file (relative to BuildRoot)
14+
fullFolder - directory containing the file (absolute)
15+
file - file path (relative to BuildRoot)
16+
agent - agent key (only when -AgentTable is non-empty)
17+
poolImage - vmImage for the agent (only when -AgentTable is non-empty)
18+
configuration - build configuration (only when -Configurations is non-empty)
1119
1220
.PARAMETER BuildRoot
1321
The root folder to start traversing the repository from.
1422
1523
.PARAMETER FileName
16-
File patterns to match defining the folders and files in the matrix
24+
File pattern to match defining the files in the matrix. Defaults to
25+
"Directory.Build.props". Ignored when -Files is supplied.
1726
1827
.PARAMETER ExcludeFileName
19-
Optional file pattern to exclude from the matrix
28+
Optional file pattern to exclude from discovery.
2029
2130
.PARAMETER JobPrefix
22-
Optional name prefix for each job
31+
Optional name prefix for each matrix key.
2332
2433
.PARAMETER AgentTable
25-
Table of agents for matrix
34+
Optional hashtable mapping agent name to vmImage. When non-empty, the
35+
matrix is fanned out across agents.
36+
37+
.PARAMETER Configurations
38+
Optional comma-separated list of build configurations. When non-empty,
39+
the matrix is fanned out across configurations.
40+
41+
.PARAMETER Files
42+
Optional comma-separated list of file paths (relative to BuildRoot).
43+
When supplied, recursive discovery via -FileName is skipped.
2644
#>
2745

2846
Param(
29-
[string] $BuildRoot = $null,
30-
[string] $FileName = $null,
31-
[string] $ExcludeFileName = $null,
32-
[string] $JobPrefix = "",
33-
[hashtable] $AgentTable = $null
47+
[string] $BuildRoot = $null,
48+
[string] $FileName = $null,
49+
[string] $ExcludeFileName = $null,
50+
[string] $JobPrefix = '',
51+
[hashtable] $AgentTable = $null,
52+
[string] $Configurations = '',
53+
[string] $Files = ''
3454
)
3555

3656
if ([string]::IsNullOrEmpty($BuildRoot)) {
37-
$BuildRoot = & (Join-Path $PSScriptRoot "get-root.ps1") -fileName "*.slnx"
57+
$BuildRoot = & (Join-Path $PSScriptRoot 'get-root.ps1') -fileName '*.slnx'
3858
}
3959

4060
if ([string]::IsNullOrEmpty($FileName)) {
41-
$FileName = "Directory.Build.props"
61+
$FileName = 'Directory.Build.props'
4262
}
4363
if (![string]::IsNullOrEmpty($JobPrefix)) {
4464
$JobPrefix = "$($JobPrefix)-"
4565
}
4666

47-
if ($AgentTable -eq $null -or $AgentTable.Count -eq 0)
48-
{
49-
$agents = @{
50-
windows = "windows-2025-vs2026"
51-
linux = "ubuntu-22.04"
52-
mac = "macOS-15"
67+
if ($null -eq $AgentTable) {
68+
# Caller did not supply an AgentTable - do not fan out across agents.
69+
$AgentTable = @{}
70+
}
71+
elseif ($AgentTable.Count -eq 0) {
72+
# Caller supplied an empty AgentTable - use the cross-platform defaults
73+
# (preserves the original get-matrix.ps1 behaviour for ci.yml callers).
74+
$AgentTable = @{
75+
windows = 'windows-2025-vs2026'
76+
linux = 'ubuntu-22.04'
77+
mac = 'macOS-15'
5378
}
5479
}
55-
else {
56-
$agents = $AgentTable
80+
$useAgents = $AgentTable.Count -gt 0
81+
82+
function Split-CsvList([string] $value) {
83+
if ([string]::IsNullOrWhiteSpace($value)) { return @() }
84+
return $value -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
5785
}
5886

59-
$jobMatrix = @{}
87+
$configList = Split-CsvList $Configurations
88+
$useConfigs = $configList.Count -gt 0
89+
90+
$fileList = Split-CsvList $Files
91+
92+
$jobMatrix = [ordered]@{}
93+
94+
# Discover files when no explicit list was supplied.
95+
if ($fileList.Count -gt 0) {
96+
Write-Host "Using caller-supplied file list: $($fileList -join ', ')"
97+
$buildRootFull = (Resolve-Path -Path $BuildRoot).Path
98+
$items = @()
99+
foreach ($rel in $fileList) {
100+
$full = Join-Path $buildRootFull $rel
101+
if (Test-Path $full -PathType Leaf) {
102+
$items += Get-Item -LiteralPath $full
103+
}
104+
else {
105+
Write-Warning "File not found: $rel"
106+
}
107+
}
108+
}
109+
else {
110+
Write-Host "Discovering files matching '$FileName' beneath '$BuildRoot'"
111+
$items = Get-ChildItem $BuildRoot -Recurse `
112+
| Where-Object Name -like $FileName `
113+
| Where-Object { [string]::IsNullOrEmpty($ExcludeFileName) -or $_.Name -notlike $ExcludeFileName }
114+
}
60115

61-
# Traverse from build root and find all files to create job matrix
62-
Get-ChildItem $BuildRoot -Recurse `
63-
| Where-Object Name -like $FileName `
64-
| Where-Object { [string]::IsNullOrEmpty($ExcludeFileName) -or $_.Name -notlike $ExcludeFileName } `
65-
| ForEach-Object {
116+
foreach ($item in $items) {
117+
$fullFolder = $item.DirectoryName.Replace('\', '/')
118+
$folder = $item.DirectoryName.Replace($BuildRoot, '').Replace('\', '/').TrimStart('/')
119+
$file = $item.FullName.Replace($BuildRoot, '').Replace('\', '/').TrimStart('/')
66120

67-
$fullFolder = $_.DirectoryName.Replace("\", "/")
68-
$folder = $_.DirectoryName.Replace($BuildRoot, "").Replace("\", "/").TrimStart("/")
69-
$file = $_.FullName.Replace($BuildRoot, "").Replace("\", "/").TrimStart("/")
70-
$postFix = ""
121+
# Build the key prefix for this file. Preserve the legacy shortcut that
122+
# treats a single yml file at the repository root as agent-only (so the
123+
# ci.yml caller continues to produce matrix keys like "windows"/"linux"
124+
# without a file-name prefix). For every other file include the file
125+
# stem so that multiple files in the same folder don't collide.
126+
$postFix = ''
71127
if ([string]::IsNullOrEmpty($folder)) {
72-
if (-not $file.contains(".yml"))
73-
{
74-
$postFix = $file.Replace(".", "-")
75-
$postFix = "$($postFix)-"
128+
if (-not $file.Contains('.yml')) {
129+
$postFix = [System.IO.Path]::GetFileNameWithoutExtension($file) + '_'
76130
}
77131
}
78132
else {
79-
$postFix = $folder.Replace("/", "-")
80-
$postFix = "$($postFix)-"
133+
$stem = [System.IO.Path]::GetFileNameWithoutExtension($file)
134+
$postFix = $folder.Replace('/', '_') + '_' + $stem + '_'
135+
}
136+
137+
if ($useAgents) {
138+
$agentKeys = @($AgentTable.Keys)
139+
}
140+
else {
141+
# Use a single sentinel iteration so the inner loop runs once.
142+
$agentKeys = @('')
143+
}
144+
if ($useConfigs) {
145+
$configKeys = $configList
146+
}
147+
else {
148+
$configKeys = @('')
81149
}
82-
$agents.keys | ForEach-Object {
83-
$jobName = "$($JobPrefix)$($postFix)$($_)"
84-
$jobMatrix.Add($jobName, @{
85-
"poolImage" = $agents.Item($_)
86-
"folder" = $folder
87-
"fullFolder" = $fullFolder
88-
"file" = $file
89-
"agent" = $($_)
90-
})
150+
151+
foreach ($agentKey in $agentKeys) {
152+
foreach ($configuration in $configKeys) {
153+
$entry = @{
154+
folder = $folder
155+
fullFolder = $fullFolder
156+
file = $file
157+
}
158+
$jobName = "$($JobPrefix)$($postFix)"
159+
if ($useAgents) {
160+
$entry['agent'] = $agentKey
161+
$entry['poolImage'] = $AgentTable.Item($agentKey)
162+
$jobName += $agentKey
163+
}
164+
if ($useConfigs) {
165+
$entry['configuration'] = $configuration
166+
$jobName += "_$configuration"
167+
}
168+
# Matrix keys must be alphanumeric or underscore for Azure DevOps.
169+
$jobName = ($jobName -replace '[^A-Za-z0-9_]', '_')
170+
$jobName = ($jobName -replace '_+', '_').Trim('_')
171+
$jobMatrix.Add($jobName, $entry)
172+
}
91173
}
92174
}
93175

94-
# Set pipeline variable
176+
Write-Host ("Job matrix:`n" + ($jobMatrix | ConvertTo-Json -Depth 4))
95177
Write-Host ("##vso[task.setVariable variable=jobMatrix;isOutput=true] {0}" `
96178
-f ($jobMatrix | ConvertTo-Json -Compress))

.azurepipelines/sln.yml

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
11
#
2-
# Build all solutions on windows
2+
# Build a matrix of (solution × configuration) on windows.
3+
# Each matrix entry becomes a parallel job with its own 60-minute timeout.
4+
#
5+
# Parameters:
6+
# solutions - Comma-separated list of .slnx paths to build. When empty,
7+
# every .slnx file in the repository is discovered and built.
8+
#
9+
# Matrix entries supply these variables:
10+
# file - path of the .slnx file
11+
# configuration - Debug or Release
312
#
413
parameters:
514
poolImage: 'windows-2025-vs2026'
615
jobnamesuffix: ''
16+
solutions: ''
717

818
jobs:
19+
- job: buildslnprep${{ parameters.jobnamesuffix }}
20+
displayName: Prepare Build Solutions Jobs
21+
pool:
22+
vmImage: 'windows-2025-vs2026'
23+
steps:
24+
- task: PowerShell@2
25+
name: solutionmatrix
26+
displayName: Prepare Solution Matrix
27+
inputs:
28+
targetType: filePath
29+
filePath: ./.azurepipelines/get-matrix.ps1
30+
arguments: -FileName '*.slnx' -Configurations 'Debug,Release' -Files "${{ parameters.solutions }}"
31+
932
- job: buildallsln${{ parameters.jobnamesuffix }}
10-
displayName: Build Solutions ${{ parameters.jobnamesuffix }}
11-
timeoutInMinutes: 90
33+
displayName: Build Solution
34+
timeoutInMinutes: 60
35+
dependsOn: buildslnprep${{ parameters.jobnamesuffix }}
36+
strategy:
37+
matrix: $[ dependencies.buildslnprep${{ parameters.jobnamesuffix }}.outputs['solutionmatrix.jobMatrix'] ]
1238
pool:
1339
vmImage: ${{ parameters.poolImage }}
1440
steps:
@@ -26,30 +52,17 @@ jobs:
2652
targetType: filePath
2753
filePath: ./.azurepipelines/set-version.ps1
2854
- task: NuGetCommand@2
29-
displayName: Restore Release
30-
inputs:
31-
command: restore
32-
restoreSolution: '**/*.slnx'
33-
configuration: 'Release'
34-
- task: MSBuild@1
35-
displayName: MS Build Release
36-
inputs:
37-
solution: '**/*.slnx'
38-
msbuildVersion: 'latest'
39-
configuration: 'Release'
40-
platform: 'Any CPU'
41-
msbuildArguments:
42-
- task: NuGetCommand@2
43-
displayName: Restore Debug
55+
displayName: Restore $(configuration) $(file)
4456
inputs:
4557
command: restore
46-
restoreSolution: '**/*.slnx'
47-
configuration: 'Debug'
58+
restoreSolution: $(file)
59+
configuration: $(configuration)
4860
- task: MSBuild@1
49-
displayName: MS Build Debug
61+
displayName: MS Build $(configuration) $(file)
5062
inputs:
51-
solution: '**/*.slnx'
63+
solution: $(file)
5264
msbuildVersion: 'latest'
53-
configuration: 'Debug'
65+
configuration: $(configuration)
5466
platform: 'Any CPU'
55-
msbuildArguments:
67+
maximumCpuCount: true
68+
msbuildArguments: '/p:BuildInParallel=true'

Libraries/Opc.Ua.Server/Session/SessionManager.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,11 +491,10 @@ public virtual async ValueTask CloseSessionAsync(NodeId sessionId, CancellationT
491491
if (current.Value.Id == sessionId)
492492
{
493493
#pragma warning disable CA2000 // Disposed correctly later
494-
if (m_sessions.TryRemove(current.Key, out session))
494+
if (!m_sessions.TryRemove(current.Key, out session))
495495
#pragma warning restore CA2000
496496
{
497-
// found but was already removed
498-
System.Diagnostics.Debug.Assert(session == null);
497+
// found but was already removed by another thread
499498
return;
500499
}
501500
break;

UA.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
<Folder Name="/SourceGeneration/">
114114
<Project Path="Tools/Opc.Ua.SourceGeneration.Core/Opc.Ua.SourceGeneration.Core.csproj" />
115115
<Project Path="Tools/Opc.Ua.SourceGeneration.Stack/Opc.Ua.SourceGeneration.Stack.csproj" />
116+
<Project Path="Tools/Opc.Ua.SourceGeneration.Tester/Opc.Ua.SourceGeneration.Tester.csproj" />
116117
<Project Path="Tools/Opc.Ua.SourceGeneration/Opc.Ua.SourceGeneration.csproj" />
117118
</Folder>
118119
<Folder Name="/Stack/">

azure-pipelines-preview.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ pr: none
77
trigger:
88
branches:
99
include:
10-
- master
10+
- 'master'
11+
- 'main'
1112
paths:
1213
include:
13-
- 'Stack/'
14-
- 'Libraries/'
15-
- 'Applications/McpServer/'
14+
- 'Stack/**'
15+
- 'Libraries/**'
16+
- 'Tools/**'
17+
- 'Applications/Opc.Ua.*/**'
1618

1719
stages:
1820
- stage: nugetpreview

azure-pipelines.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ trigger:
66
batch: 'true'
77
branches:
88
include:
9-
- '*'
9+
- 'master'
10+
- 'main'
11+
- 'release/*'
1012
paths:
1113
include:
1214
- '*'
1315
exclude:
14-
- 'Docs/*'
15-
- 'README.md'
16-
16+
- 'Docs/**'
17+
- '**/*.md'
1718
pr:
1819
autoCancel: 'true'
1920
branches:
@@ -49,6 +50,12 @@ stages:
4950
parameters:
5051
poolImage: 'windows-2025-vs2026'
5152
jobnamesuffix: win2025
53+
# PR builds compile only UA.slnx; master/scheduled/manual/fork-PR builds
54+
# discover every *.slnx in the repository automatically.
55+
${{ if eq(variables.FullBuild, 'False') }}:
56+
solutions: 'UA.slnx'
57+
${{ else }}:
58+
solutions: ''
5259
- stage: testrelease
5360
dependsOn: [build]
5461
displayName: 'Test Core and SDK Release'

0 commit comments

Comments
 (0)