diff --git a/CHANGELOG.md b/CHANGELOG.md index a5d48eeb558..15e948ebb02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Refuse to send data to servers whose certificate was issued by one of the leaked eMudhra intermediates (CVE-2026-111943) ([#6336](https://github.com/getsentry/sentry-java/pull/6336)) - Stop sending the whole release as `dist` on ANR and app-exit events when the release has no `+versionCode` ([#6450](https://github.com/getsentry/sentry-java/pull/6450)) - Relay rejected these values (e.g. `invalid character '@'`). `dist` is now left empty unless `options.dist` is set or the release contains a version code. +- Accept profiling traces directories whose path contains a `.` on the JVM, and keep profiling disabled after its first start rejects the options instead of starting on the next one ([#6334](https://github.com/getsentry/sentry-java/pull/6334)) ### Dependencies diff --git a/sentry-async-profiler/src/main/java/io/sentry/asyncprofiler/profiling/JavaContinuousProfiler.java b/sentry-async-profiler/src/main/java/io/sentry/asyncprofiler/profiling/JavaContinuousProfiler.java index 09c2ed8b5d2..ced363d4a58 100644 --- a/sentry-async-profiler/src/main/java/io/sentry/asyncprofiler/profiling/JavaContinuousProfiler.java +++ b/sentry-async-profiler/src/main/java/io/sentry/asyncprofiler/profiling/JavaContinuousProfiler.java @@ -42,16 +42,15 @@ public final class JavaContinuousProfiler implements IContinuousProfiler, RateLimiter.IRateLimitObserver { private static final long MAX_CHUNK_DURATION_MILLIS = 10000; - private final List invalidFilenameChars = - Arrays.asList( - ".", ",", ":", ";", "|", "\\", "=", "%", "&", "[", "]", "(", ")", "<", ">", "{", "}", "!", - "*", "?", "~", "\"", "'", "$", "`", "^"); + // async-profiler splits its arguments on "," and expands "%" patterns in the file name. + private final List invalidFilenameChars = Arrays.asList(",", "%"); private final @NotNull ILogger logger; private final @Nullable String profilingTracesDirPath; private final int profilingTracesHz; private final @NotNull ISentryExecutorService executorService; private boolean isInitialized = false; + private boolean hasValidOptions = false; private boolean isRunning = false; private @Nullable IScopes scopes; private @Nullable Future stopFuture; @@ -92,7 +91,7 @@ private void initializeProfiler() throws Exception { private boolean init() { if (isInitialized) { - return true; + return hasValidOptions; } isInitialized = true; @@ -107,6 +106,7 @@ private boolean init() { profilingTracesHz); return false; } + hasValidOptions = true; return true; } diff --git a/sentry-async-profiler/src/test/java/io/sentry/asyncprofiler/profiling/JavaContinuousProfilerTest.kt b/sentry-async-profiler/src/test/java/io/sentry/asyncprofiler/profiling/JavaContinuousProfilerTest.kt index 7764249e3eb..745139a20a6 100644 --- a/sentry-async-profiler/src/test/java/io/sentry/asyncprofiler/profiling/JavaContinuousProfilerTest.kt +++ b/sentry-async-profiler/src/test/java/io/sentry/asyncprofiler/profiling/JavaContinuousProfilerTest.kt @@ -421,6 +421,31 @@ class JavaContinuousProfilerTest { ) } + @Test + fun `profiler starts when the traces dir path contains a dot`() { + val profiler = fixture.getSut { + it.profilingTracesDirPath = File("tmp", "sentry.traces").absolutePath + } + File(fixture.options.profilingTracesDirPath!!).mkdirs() + + profiler.startProfiler(ProfileLifecycle.MANUAL, fixture.mockTracesSampler) + assertTrue(profiler.isRunning) + profiler.close(true) + } + + @Test + fun `profiler stays disabled when the traces dir is created after its first start`() { + val tracesDir = File(fixture.options.profilingTracesDirPath!!) + tracesDir.deleteRecursively() + val profiler = fixture.getSut() + profiler.startProfiler(ProfileLifecycle.MANUAL, fixture.mockTracesSampler) + assertFalse(profiler.isRunning) + + tracesDir.mkdirs() + profiler.startProfiler(ProfileLifecycle.MANUAL, fixture.mockTracesSampler) + assertFalse(profiler.isRunning) + } + fun withMockScopes(closure: () -> Unit) = Mockito.mockStatic(Sentry::class.java).use { it.`when` { Sentry.getCurrentScopes() }.thenReturn(fixture.scopes)