From a85bd46c18d33130298f0e223bd84ea6e29a17de Mon Sep 17 00:00:00 2001 From: Duansg Date: Sun, 13 Sep 2026 19:18:08 +0800 Subject: [PATCH] [fix] make the Windows native collector start, and smoke test the packages in CI (#4379) --- .github/workflows/collector-native-build.yml | 57 +++++++++++++++++++ .../collect/redis/RedisCommonCollectImpl.java | 27 +++++++-- 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/.github/workflows/collector-native-build.yml b/.github/workflows/collector-native-build.yml index 715526918a..3c8aadbf1f 100644 --- a/.github/workflows/collector-native-build.yml +++ b/.github/workflows/collector-native-build.yml @@ -75,6 +75,63 @@ jobs: } "archive=$($package.FullName)" >> $env:GITHUB_OUTPUT + - name: Smoke test native collector package + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $archive = "${{ steps.package.outputs.archive }}" + $work = Join-Path ([System.IO.Path]::GetTempPath()) "hzb-smoke" + Remove-Item -Recurse -Force $work -ErrorAction SilentlyContinue + New-Item -ItemType Directory -Path $work | Out-Null + + if ($archive.EndsWith(".zip")) { + Expand-Archive -Path $archive -DestinationPath $work -Force + } else { + tar -xzf $archive -C $work + } + + $binary = Get-ChildItem -Path $work -Recurse -File | + Where-Object { $_.Name -like "apache-hertzbeat-collector-native-*" -and $_.Name -notlike "*.txt" } | + Where-Object { $_.Length -gt 10MB } | Select-Object -First 1 + if (-not $binary) { throw "native executable not found in $archive" } + $conf = Join-Path $binary.DirectoryName "config" + Write-Host "executable: $($binary.FullName)" + + $out = Join-Path $work "stdout.log" + $err = Join-Path $work "stderr.log" + $env:MANAGER_HOST = "127.0.0.1" + $env:IDENTITY = "ci-smoke-${{ matrix.platform }}" + $proc = Start-Process -FilePath $binary.FullName ` + -ArgumentList "--spring.config.location=$conf$([IO.Path]::DirectorySeparatorChar)" ` + -RedirectStandardOutput $out -RedirectStandardError $err -PassThru + + # The collector prints "Started Collector" before the runners execute, and past + # failures crashed after that line, so wait for the ServiceLoader registration too + # and then confirm the process is still alive. + $deadline = (Get-Date).AddSeconds(90) + $registered = $false + while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 2 + $log = (Get-Content $out, $err -ErrorAction SilentlyContinue) -join "`n" + if ($log -match "collect strategies") { $registered = $true; break } + if ($proc.HasExited) { break } + } + + Start-Sleep -Seconds 10 + $proc.Refresh() + $alive = -not $proc.HasExited + $exitCode = if ($alive) { $null } else { $proc.ExitCode } + if ($alive) { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } + + $log = (Get-Content $out, $err -ErrorAction SilentlyContinue) -join "`n" + Write-Host "----- collector output (tail) -----" + Write-Host (($log -split "`n") | Select-Object -Last 40 | Out-String) + Write-Host "-----------------------------------" + + if (-not $alive) { throw "collector process exited (exit code $exitCode)" } + if (-not $registered) { throw "collector stayed up but never registered its collect strategies" } + Write-Host "smoke test passed for ${{ matrix.platform }}" + - name: Upload native collector package uses: actions/upload-artifact@v4 with: diff --git a/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/redis/RedisCommonCollectImpl.java b/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/redis/RedisCommonCollectImpl.java index ac520cf72b..c5b05ff148 100644 --- a/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/redis/RedisCommonCollectImpl.java +++ b/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/redis/RedisCommonCollectImpl.java @@ -73,11 +73,26 @@ public class RedisCommonCollectImpl extends AbstractCollect { private static final String UNIQUE_IDENTITY = "identity"; - private final ClientResources defaultClientResources; private final GlobalConnectionCache connectionCache = GlobalConnectionCache.getInstance(); - - public RedisCommonCollectImpl() { - defaultClientResources = DefaultClientResources.create(); + + /** + * Holds the lettuce client resources, created on first use. + *

+ * Loading {@link DefaultClientResources} runs a static initializer that resolves netty's DNS + * address resolver group. That resolver is unavailable in a native image on Windows, where it + * fails with a NullPointerException from sun.net.dns.ResolverConfigurationImpl + * (see oracle/graal#11280 and oracle/graal#4304). Because every collector is instantiated + * eagerly through the ServiceLoader at startup, doing this in the constructor took the whole + * collector process down before it could serve anything. Deferring it keeps startup working; + * on the platforms where the resolver is broken only Redis collection fails, and it fails with + * a clear error at collect time. + */ + private static final class ClientResourcesHolder { + private static final ClientResources INSTANCE = DefaultClientResources.create(); + } + + private static ClientResources clientResources() { + return ClientResourcesHolder.INSTANCE; } @Override @@ -292,7 +307,7 @@ public class RedisCommonCollectImpl extends AbstractCollect { * @return redis cluster client */ private RedisClusterClient buildClusterClient(RedisProtocol redisProtocol, String host, String port) { - return RedisClusterClient.create(defaultClientResources, redisUri(redisProtocol, host, port)); + return RedisClusterClient.create(clientResources(), redisUri(redisProtocol, host, port)); } /** @@ -302,7 +317,7 @@ public class RedisCommonCollectImpl extends AbstractCollect { * @return redis single client */ private RedisClient buildSingleClient(RedisProtocol redisProtocol, String host, String port) { - return RedisClient.create(defaultClientResources, redisUri(redisProtocol, host, port)); + return RedisClient.create(clientResources(), redisUri(redisProtocol, host, port)); } private RedisURI redisUri(RedisProtocol redisProtocol, String host, String port) {