88import com .datadoghq .profiler .AbstractProfilerTest ;
99import com .datadoghq .profiler .ProfilerOwnedBlockHooks ;
1010import java .lang .reflect .Method ;
11+ import java .util .concurrent .CountDownLatch ;
1112import java .util .concurrent .TimeUnit ;
13+ import java .util .concurrent .atomic .AtomicBoolean ;
14+ import java .util .concurrent .atomic .AtomicReference ;
1215import java .util .concurrent .locks .LockSupport ;
1316import org .junit .jupiter .api .Test ;
1417import org .junit .jupiter .api .Assumptions ;
1518import org .openjdk .jmc .common .item .IItemCollection ;
1619
1720import static org .junit .jupiter .api .Assertions .assertFalse ;
21+ import static org .junit .jupiter .api .Assertions .assertTrue ;
1822
1923/** Verifies TaskBlock production from Java-owned platform-thread park hooks. */
2024public class ParkTaskBlockTest extends AbstractProfilerTest {
@@ -94,9 +98,17 @@ public void virtualParkDoesNotMutateCarrierProducerState() throws Exception {
9498 TaskBlockAssertions .assertContains (events , 0 , 0 , BLOCKER , UNBLOCKING_SPAN_ID );
9599 }
96100
101+ @ Test
102+ public void platformParkSuppressesSignalsAndClearsOwnership () throws Exception {
103+ long baseline = profiler .getDebugCounters ()
104+ .getOrDefault ("wc_signals_suppressed_owned_block" , 0L );
105+ long afterFirstPark = runSuppressedPark (baseline );
106+ runSuppressedPark (afterFirstPark );
107+ }
108+
97109 @ Override
98110 protected String getProfilerCommand () {
99- return "wall=1ms,wallprecheck=true" ;
111+ return "wall=1ms,filter=, wallprecheck=true" ;
100112 }
101113
102114 protected void assertTaskBlockStackReference (IItemCollection events ) {
@@ -112,4 +124,46 @@ private static void parkForMillis(long millis) {
112124 LockSupport .parkNanos (remaining );
113125 }
114126 }
127+
128+ private long runSuppressedPark (long baseline ) throws Exception {
129+ CountDownLatch armed = new CountDownLatch (1 );
130+ AtomicBoolean release = new AtomicBoolean ();
131+ AtomicReference <Throwable > error = new AtomicReference <>();
132+ Thread worker = new Thread (() -> {
133+ try {
134+ ProfilerOwnedBlockHooks .parkEnter (profiler );
135+ armed .countDown ();
136+ while (!release .get ()) {
137+ Thread .yield ();
138+ }
139+ } catch (Throwable t ) {
140+ error .set (t );
141+ } finally {
142+ ProfilerOwnedBlockHooks .parkExit (profiler , BLOCKER , UNBLOCKING_SPAN_ID );
143+ }
144+ }, "taskblock-park-suppression" );
145+
146+ worker .start ();
147+ assertTrue (armed .await (5 , TimeUnit .SECONDS ));
148+ try {
149+ waitForCounterAbove ("wc_signals_suppressed_owned_block" , baseline , 5_000L );
150+ } finally {
151+ release .set (true );
152+ }
153+ worker .join (5_000L );
154+ assertFalse (worker .isAlive ());
155+ if (error .get () != null ) throw new AssertionError (error .get ());
156+ return profiler .getDebugCounters ()
157+ .getOrDefault ("wc_signals_suppressed_owned_block" , 0L );
158+ }
159+
160+ private void waitForCounterAbove (String name , long baseline , long timeoutMillis )
161+ throws Exception {
162+ long deadline = System .nanoTime () + TimeUnit .MILLISECONDS .toNanos (timeoutMillis );
163+ while (System .nanoTime () < deadline ) {
164+ if (profiler .getDebugCounters ().getOrDefault (name , 0L ) > baseline ) return ;
165+ Thread .sleep (10L );
166+ }
167+ throw new AssertionError ("Counter did not increase: " + name );
168+ }
115169}
0 commit comments