Skip to content

Commit 3577b49

Browse files
committed
Add functions to build[sh|ps1] scripts that kill any lingering processes holding on to things in artifacts before performing actions that write to artifacts.
1 parent b64c645 commit 3577b49

File tree

2 files changed

+178
-4
lines changed

2 files changed

+178
-4
lines changed

eng/common/build.ps1

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Param(
3737
# Unset 'Platform' environment variable to avoid unwanted collision in InstallDotNetCore.targets file
3838
# some computer has this env var defined (e.g. Some HP)
3939
if($env:Platform) {
40-
$env:Platform=""
40+
$env:Platform=""
4141
}
4242
function Print-Usage() {
4343
Write-Host "Common settings:"
@@ -108,10 +108,10 @@ function Build {
108108
# Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons.
109109
# Explicitly set the type as string[] because otherwise PowerShell would make this char[] if $properties is empty.
110110
[string[]] $msbuildArgs = $properties
111-
112-
# Resolve relative project paths into full paths
111+
112+
# Resolve relative project paths into full paths
113113
$projects = ($projects.Split(';').ForEach({Resolve-Path $_}) -join ';')
114-
114+
115115
$msbuildArgs += "/p:Projects=$projects"
116116
$properties = $msbuildArgs
117117
}
@@ -139,9 +139,59 @@ function Build {
139139
@properties
140140
}
141141

142+
function Stop-ArtifactLockers {
143+
<#
144+
.SYNOPSIS
145+
Terminates dotnet processes that are holding locks on artifacts DLLs
146+
147+
.DESCRIPTION
148+
This function finds dotnet processes spawned from .dotnet/dotnet.exe that have file handles
149+
open to DLL files in the artifacts directory and terminates them to prevent build conflicts.
150+
151+
.PARAMETER RepoRoot
152+
The root directory of the repository. Defaults to current location.
153+
#>
154+
param(
155+
[string]$RepoRoot = (Get-Location).Path
156+
)
157+
158+
$artifactsPath = Join-Path $RepoRoot 'artifacts'
159+
160+
# Exit early if artifacts directory doesn't exist
161+
if (-not (Test-Path $artifactsPath)) {
162+
return
163+
}
164+
165+
# Find dotnet processes spawned from this repository's .dotnet directory
166+
$repoDotnetPath = Join-Path $RepoRoot '.dotnet\dotnet.exe'
167+
$artifactsRoot = Join-Path $RepoRoot 'artifacts'
168+
$dotnetExes = @()
169+
try {
170+
$dotnetExes = Get-Process -Name 'dotnet' -ErrorAction SilentlyContinue | Where-Object { $_.Path -eq $repoDotnetPath -and $_.CommandLine.StartsWith($artifactsRoot) }
171+
}
172+
catch {
173+
return
174+
}
175+
176+
if ($dotnetExes.Count -eq 0) {
177+
return
178+
}
179+
180+
# Check each dotnet process for locks on artifacts DLLs
181+
foreach ($process in $dotnetExes) {
182+
try {
183+
$process.Kill()
184+
}
185+
catch {
186+
# Silently continue if we can't check or kill a process
187+
}
188+
}
189+
}
190+
142191
try {
143192
if ($clean) {
144193
if (Test-Path $ArtifactsDir) {
194+
Stop-ArtifactLockers -RepoRoot $RepoRoot
145195
Remove-Item -Recurse -Force $ArtifactsDir
146196
Write-Host 'Artifacts directory deleted.'
147197
}
@@ -167,6 +217,8 @@ try {
167217
InitializeNativeTools
168218
}
169219

220+
Stop-ArtifactLockers -RepoRoot $RepoRoot
221+
170222
Build
171223
}
172224
catch {

eng/common/build.sh

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,127 @@ function Build {
262262
ExitWithExitCode 0
263263
}
264264

265+
function Stop-ArtifactLockers {
266+
# Terminates dotnet processes that are holding locks on artifacts shared libraries
267+
#
268+
# This function finds dotnet processes spawned from <repo>/.dotnet/dotnet that have file handles
269+
# open to shared library files in the artifacts directory and terminates them to prevent build conflicts.
270+
#
271+
# Parameters:
272+
# $1 - RepoRoot: The root directory of the repository (required)
273+
274+
local repo_root="${1:-$(pwd)}"
275+
local artifacts_path="$repo_root/artifacts"
276+
277+
# Exit early if artifacts directory doesn't exist
278+
if [[ ! -d "$artifacts_path" ]]; then
279+
return 0
280+
fi
281+
282+
# Find dotnet processes spawned from this repository's .dotnet directory
283+
local repo_dotnet_path="$repo_root/.dotnet/dotnet"
284+
local dotnet_pids=()
285+
286+
# Get all dotnet processes and filter by exact path
287+
if command -v pgrep >/dev/null 2>&1; then
288+
# Use pgrep if available (more efficient)
289+
while IFS= read -r line; do
290+
local pid
291+
local path
292+
pid=$(echo "$line" | cut -d' ' -f1)
293+
path=$(echo "$line" | cut -d' ' -f2-)
294+
if [[ "$path" == "$repo_dotnet_path" ]]; then
295+
dotnet_pids+=("$pid")
296+
fi
297+
done < <(pgrep -f dotnet -l 2>/dev/null | grep -E "^[0-9]+ .*dotnet$" || true)
298+
else
299+
# Fallback to ps if pgrep is not available
300+
while IFS= read -r line; do
301+
local pid
302+
local cmd
303+
pid=$(echo "$line" | awk '{print $2}')
304+
cmd=$(echo "$line" | awk '{for(i=11;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/ $//')
305+
if [[ "$cmd" == "$repo_dotnet_path" ]]; then
306+
dotnet_pids+=("$pid")
307+
fi
308+
done < <(ps aux | grep dotnet | grep -v grep || true)
309+
fi
310+
311+
if [[ ${#dotnet_pids[@]} -eq 0 ]]; then
312+
return 0
313+
fi
314+
315+
# Check each dotnet process for command lines pointing to artifacts DLLs
316+
local pids_to_kill=()
317+
for pid in "${dotnet_pids[@]}"; do
318+
# Skip if process no longer exists
319+
if ! kill -0 "$pid" 2>/dev/null; then
320+
continue
321+
fi
322+
323+
local has_artifact_dll=false
324+
325+
# Get the command line for this process
326+
local cmdline=""
327+
if [[ -r "/proc/$pid/cmdline" ]]; then
328+
# Linux: read from /proc/pid/cmdline
329+
cmdline=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true)
330+
elif command -v ps >/dev/null 2>&1; then
331+
# macOS/other: use ps to get command line
332+
cmdline=$(ps -p "$pid" -o command= 2>/dev/null || true)
333+
fi
334+
335+
# Check if command line contains any DLL path under artifacts
336+
if [[ -n "$cmdline" && "$cmdline" == *"$artifacts_path"*.dll* ]]; then
337+
has_artifact_dll=true
338+
fi
339+
340+
if [[ "$has_artifact_dll" == true ]]; then
341+
echo "Terminating dotnet process $pid with artifacts DLL in command line"
342+
pids_to_kill+=("$pid")
343+
fi
344+
done
345+
346+
# Kill all identified processes in parallel
347+
if [[ ${#pids_to_kill[@]} -gt 0 ]]; then
348+
# Send SIGTERM to all processes
349+
for pid in "${pids_to_kill[@]}"; do
350+
kill "$pid" 2>/dev/null || true
351+
done
352+
353+
# Wait up to 5 seconds for all processes to exit
354+
local count=0
355+
local still_running=()
356+
while [[ $count -lt 50 ]]; do
357+
still_running=()
358+
for pid in "${pids_to_kill[@]}"; do
359+
if kill -0 "$pid" 2>/dev/null; then
360+
still_running+=("$pid")
361+
fi
362+
done
363+
364+
if [[ ${#still_running[@]} -eq 0 ]]; then
365+
break
366+
fi
367+
368+
sleep 0.1
369+
((count++))
370+
done
371+
372+
# Force kill any processes still running after 5 seconds
373+
for pid in "${still_running[@]}"; do
374+
if kill -0 "$pid" 2>/dev/null; then
375+
echo "Force killing dotnet process $pid"
376+
kill -9 "$pid" 2>/dev/null || true
377+
fi
378+
done
379+
fi
380+
}
381+
265382
if [[ "$clean" == true ]]; then
266383
if [ -d "$artifacts_dir" ]; then
384+
# Kill any lingering dotnet processes that might be holding onto artifacts
385+
Stop-ArtifactLockers "$repo_root"
267386
rm -rf $artifacts_dir
268387
echo "Artifacts directory deleted."
269388
fi
@@ -274,4 +393,7 @@ if [[ "$restore" == true ]]; then
274393
InitializeNativeTools
275394
fi
276395

396+
# Kill any lingering dotnet processes that might be holding onto artifacts
397+
Stop-ArtifactLockers "$repo_root"
398+
277399
Build

0 commit comments

Comments
 (0)