4343import java .util .concurrent .ScheduledExecutorService ;
4444import java .util .concurrent .TimeUnit ;
4545import java .util .concurrent .locks .LockSupport ;
46+ import java .util .concurrent .locks .ReentrantLock ;
4647import java .util .function .Supplier ;
4748import java .util .logging .Level ;
4849import java .util .logging .Logger ;
@@ -153,6 +154,7 @@ public class SamplingProfiler implements Runnable {
153154 @ Nullable private final File tempDir ;
154155
155156 private final AsyncProfiler profiler ;
157+ private final ReentrantLock profilerLock = new ReentrantLock ();
156158 @ Nullable private volatile Future <?> profilingTask ;
157159
158160 /**
@@ -317,6 +319,9 @@ public boolean onActivation(Span activeSpan, @Nullable Span previouslyActive) {
317319 if (previouslyActive == null ) {
318320 profiler .addThread (Thread .currentThread ());
319321 }
322+ if (!config .isPostProcessingEnabled ()) {
323+ return true ;
324+ }
320325 boolean success =
321326 eventBuffer .tryPublishEvent (activationEventTranslator , activeSpan , previouslyActive );
322327 if (!success ) {
@@ -343,6 +348,9 @@ public boolean onDeactivation(Span deactivatedSpan, @Nullable Span previouslyAct
343348 if (previouslyActive == null ) {
344349 profiler .removeThread (Thread .currentThread ());
345350 }
351+ if (!config .isPostProcessingEnabled ()) {
352+ return true ;
353+ }
346354 boolean success =
347355 eventBuffer .tryPublishEvent (
348356 deactivationEventTranslator , deactivatedSpan , previouslyActive );
@@ -373,7 +381,9 @@ public void run() {
373381 Duration profilingDuration = config .getProfilingDuration ();
374382 boolean postProcessingEnabled = config .isPostProcessingEnabled ();
375383
376- setProfilingSessionOngoing (postProcessingEnabled );
384+ // We need to enable the session so that onActivation is called and threads are added to the
385+ // profiler (profiler.addThread). Otherwise, with the "filter" option, nothing is profiled.
386+ setProfilingSessionOngoing (true );
377387
378388 if (postProcessingEnabled ) {
379389 logger .fine ("Start full profiling session (async-profiler and agent processing)" );
@@ -394,37 +404,62 @@ public void run() {
394404 config .isNonStopProfiling () && !interrupted && postProcessingEnabled ;
395405 setProfilingSessionOngoing (continueProfilingSession );
396406
397- if (!interrupted && !scheduler .isShutdown ()) {
398- long delay = config .getProfilingInterval ().toMillis () - profilingDuration .toMillis ();
399- profilingTask = scheduler .schedule (this , delay , TimeUnit .MILLISECONDS );
407+ profilerLock .lock ();
408+ try {
409+ // it's possible for an interruption to occur just before the lock was acquired. This is
410+ // handled by re-reading Thread.currentThread().isInterrupted() to ensure no task is scheduled
411+ // if an interruption occurred just before acquiring the lock
412+ if (!Thread .currentThread ().isInterrupted () && !scheduler .isShutdown ()) {
413+ long delay = config .getProfilingInterval ().toMillis () - profilingDuration .toMillis ();
414+ profilingTask = scheduler .schedule (this , delay , TimeUnit .MILLISECONDS );
415+ }
416+ } finally {
417+ profilerLock .unlock ();
400418 }
401419 }
402420
403421 @ SuppressWarnings ({"NonAtomicVolatileUpdate" , "EmptyCatch" })
404422 private void profile (Duration profilingDuration ) throws Exception {
405423 try {
406424 String startCommand = createStartCommand ();
407- String startMessage = profiler .execute (startCommand );
425+ String startMessage ;
426+ try {
427+ startMessage = profiler .execute (startCommand );
428+ } catch (IllegalStateException e ) {
429+ if (e .getMessage () != null && e .getMessage ().contains ("already started" )) {
430+ logger .fine ("Profiler already started. Stopping and restarting." );
431+ try {
432+ profiler .stop ();
433+ } catch (RuntimeException ignore ) {
434+ logger .log (Level .FINE , "Ignored error on stopping profiler" , ignore );
435+ }
436+ startMessage = profiler .execute (startCommand );
437+ } else {
438+ throw e ;
439+ }
440+ }
408441 logger .fine (startMessage );
409- if (!profiledThreads .isEmpty ()) {
410- restoreFilterState (profiler );
442+ try {
443+ // try-finally because if the code is interrupted we want to ensure the
444+ // profiler.execute("stop") is called
445+ if (!profiledThreads .isEmpty ()) {
446+ restoreFilterState (profiler );
447+ }
448+ // Doesn't need to be atomic as this field is being updated only by a single thread
449+ profilingSessions ++;
450+
451+ // When post-processing is disabled activation events are ignored, but we still need to
452+ // invoke this method as it is the one enforcing the sampling session duration. As a side
453+ // effect it will also consume residual activation events if post-processing is disabled
454+ // dynamically
455+ consumeActivationEventsFromRingBufferAndWriteToFile (profilingDuration );
456+ } finally {
457+ String stopMessage = profiler .execute ("stop" );
458+ logger .fine (stopMessage );
411459 }
412- // Doesn't need to be atomic as this field is being updated only by a single thread
413- profilingSessions ++;
414-
415- // When post-processing is disabled activation events are ignored, but we still need to invoke
416- // this method
417- // as it is the one enforcing the sampling session duration. As a side effect it will also
418- // consume
419- // residual activation events if post-processing is disabled dynamically
420- consumeActivationEventsFromRingBufferAndWriteToFile (profilingDuration );
421-
422- String stopMessage = profiler .execute ("stop" );
423- logger .fine (stopMessage );
424460
425461 // When post-processing is disabled, jfr file will not be parsed and the heavy processing will
426- // not occur
427- // as this method aborts when no activation events are buffered
462+ // not occur as this method aborts when no activation events are buffered
428463 processTraces ();
429464 } catch (InterruptedException | ClosedByInterruptException e ) {
430465 try {
@@ -505,6 +540,9 @@ EventPoller.PollState consumeActivationEventsFromRingBufferAndWriteToFile() thro
505540 }
506541
507542 public void processTraces () throws IOException {
543+ if (!config .isPostProcessingEnabled ()) {
544+ return ;
545+ }
508546 if (jfrParser == null ) {
509547 jfrParser = new JfrParser ();
510548 }
@@ -739,18 +777,25 @@ public void start() {
739777
740778 @ SuppressWarnings ({"FutureReturnValueIgnored" , "Interruption" })
741779 public void reschedule () {
742- Future <?> future = this .profilingTask ;
743- if (future != null ) {
744- if (future .cancel (true )) {
780+ profilerLock .lock ();
781+ try {
782+ Future <?> future = this .profilingTask ;
783+ if (future != null && future .cancel (true )) {
745784 Duration profilingDuration = config .getProfilingDuration ();
746785 long delay = config .getProfilingInterval ().toMillis () - profilingDuration .toMillis ();
747786 profilingTask = scheduler .schedule (this , delay , TimeUnit .MILLISECONDS );
748787 }
788+ } finally {
789+ profilerLock .unlock ();
749790 }
750791 }
751792
793+ @ SuppressWarnings ({"FutureReturnValueIgnored" , "Interruption" })
752794 public void stop () throws InterruptedException , IOException {
753795 // cancels/interrupts the profiling thread
796+ if (profilingTask != null ) {
797+ profilingTask .cancel (true );
798+ }
754799 // implicitly clears profiled threads
755800 scheduler .shutdown ();
756801 scheduler .awaitTermination (10 , TimeUnit .SECONDS );
0 commit comments