IIS部署.NET Core如何不终止全部dotnet进程释放指定目录文件锁
在Windows Server 2019服务器上使用CodeDeploy部署多个IIS应用时,部署其中一个.NET Core 3.1应用出现如下报错:
Permission denied @ unlink_internal - C:/inetpub/wwwroot/webApi/AutocheckerEntities.dll
最初为解决该问题,在停止站点、停止对应应用池的操作逻辑外,额外添加了强制结束所有dotnet.exe进程的命令——仅停止站点和应用池无法解决上述文件占用报错。
初始版本的appstop.ps1脚本内容如下:
Stop-WebSite 'WebApi' Stop-WebAppPool (Get-Website -Name WebApi).applicationPool taskkill /IM "dotnet.exe" /F
对应appspec.yml配置内容如下:
version: 0.0 os: windows files: - source: / destination: C:\inetpub\wwwroot\webApi hooks: BeforeInstall: - location: CodeDeploy/appstop.ps1 runas: administrator ApplicationStart: - location: CodeDeploy/appstart.ps1 runas: administrator
该方案存在明确业务影响风险:服务器上同时运行了其他.NET应用,强制结束全部dotnet.exe进程会导致其他业务异常,需要实现仅临时释放C:\inetpub\wwwroot\webApi目录下的文件锁、无需终止全部dotnet.exe进程的方案。
根因说明
仅停止站点和应用池后仍存在文件锁,核心原因是IIS停止站点、回收应用池的操作不是同步完成的:工作进程(w3wp.exe)、OutOfProcess托管模式下的.NET Core dotnet宿主进程,会等待现有请求处理完成后才退出,默认最长等待时间是90秒,这段时间内进程会持续持有应用目录下dll文件的句柄,就会触发部署时的unlink权限报错。
直接全局杀所有dotnet.exe属于过度操作,完全可以通过精准定位、终止仅占用目标部署目录的进程解决问题,不会影响其他业务。
具体实现
修改appstop.ps1脚本,替换全局杀进程的逻辑,改为等待IIS回收+精准查杀占用目标目录的关联进程,修改后的脚本如下:
$siteName = "WebApi" $deployPath = "C:\inetpub\wwwroot\webApi" # 停止目标站点 Stop-WebSite -Name $siteName # 获取站点绑定的应用池并执行停止操作 $appPoolName = (Get-Website -Name $siteName).applicationPool Stop-WebAppPool -Name $appPoolName # 等待3秒,给IIS留出默认进程回收的缓冲时间 Start-Sleep -Seconds 3 # 二次校验应用池状态,未完全停止则强制停止 if ((Get-WebAppPoolState -Name $appPoolName).Value -ne "Stopped") { Stop-WebAppPool -Name $appPoolName -Force } # 精准查找命令行参数包含目标部署路径的w3wp、dotnet进程,仅终止这部分进程 Get-CimInstance Win32_Process | Where-Object { $_.Name -in @('w3wp.exe', 'dotnet.exe') -and $_.CommandLine -match [regex]::Escape($deployPath) } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
优化配置(可选)
可以提前在IIS中调整目标应用池的配置,从根源减少文件锁出现的概率:
- 打开目标应用池的高级设置
- 找到进程模型分组,将
闲置超时设置为0,关闭时间限制设置为1秒 - 找到常规分组,将
启动模式设置为OnDemand
配置完成后,停止应用池时工作进程会在1秒内被强制销毁,基本不会出现残留文件锁的情况。
内容的提问来源于stack exchange,提问作者Georgi Koemdzhiev

