11package us .abstracta .jmeter .javadsl .core .threadgroups ;
22
33import com .blazemeter .jmeter .threads .AbstractDynamicThreadGroup ;
4+ import com .blazemeter .jmeter .threads .DynamicThread ;
45import com .blazemeter .jmeter .threads .concurrency .ConcurrencyThreadGroup ;
56import com .blazemeter .jmeter .threads .concurrency .ConcurrencyThreadGroupGui ;
67import java .time .Duration ;
1617import org .apache .jmeter .testelement .TestElement ;
1718import org .apache .jmeter .testelement .property .CollectionProperty ;
1819import org .apache .jmeter .threads .AbstractThreadGroup ;
20+ import org .apache .jmeter .threads .JMeterContextService ;
1921import org .apache .jorphan .collections .HashTree ;
2022import us .abstracta .jmeter .javadsl .core .BuildTreeContext ;
2123import us .abstracta .jmeter .javadsl .core .util .JmeterFunction ;
@@ -256,12 +258,28 @@ private TestElement buildTestAction() {
256258 }
257259
258260 private TestElement buildTimer () {
259- VariableThroughputTimer ret = new VariableThroughputTimer ();
261+ VariableThroughputTimer ret = new NonInterruptingVariableThroughputTimer ();
260262 ret .setData (buildTimerSchedulesData ());
261263 configureTestElement (ret , buildTimerName (timerId ++), VariableThroughputTimerGui .class );
262264 return ret ;
263265 }
264266
267+ /**
268+ * Always stops thread group gracefully, avoiding potential exceptions generated by
269+ * {@link VariableThroughputTimer} when stopping a test, due to thread interruptions.
270+ * <p>
271+ * <a href="https://github.com/abstracta/jmeter-java-dsl/issues/257">Here</a> are more details on
272+ * this issue.
273+ */
274+ public static class NonInterruptingVariableThroughputTimer extends VariableThroughputTimer {
275+
276+ @ Override
277+ protected void stopTest () {
278+ JMeterContextService .getContext ().getThreadGroup ().stop ();
279+ }
280+
281+ }
282+
265283 private String buildTimerName (int id ) {
266284 return "rpsTimer" + id ;
267285 }
@@ -276,7 +294,7 @@ private CollectionProperty buildTimerSchedulesData() {
276294
277295 @ Override
278296 protected AbstractThreadGroup buildThreadGroup () {
279- ConcurrencyThreadGroup ret = new ConcurrencyThreadGroup ();
297+ ConcurrencyThreadGroup ret = new ContractComplyingConcurrencyThreadGroup ();
280298 ret .setTargetLevel (
281299 JmeterFunction .from ("__tstFeedback" , buildTimerName (timerId ), initThreads , maxThreads ,
282300 spareThreads ));
@@ -285,6 +303,29 @@ protected AbstractThreadGroup buildThreadGroup() {
285303 return ret ;
286304 }
287305
306+ /**
307+ * This implementation of ConcurrencyThreadGroup complies with {@link AbstractThreadGroup}
308+ * contract, and the original doesn't.
309+ * <p>
310+ * ConcurrencyThreadGroup stop should be graceful and is not, tellThreadsToStop should interrupt
311+ * threads and is not.
312+ */
313+ public static class ContractComplyingConcurrencyThreadGroup extends ConcurrencyThreadGroup {
314+
315+ @ Override
316+ public void stop () {
317+ running = false ;
318+ threadStarter .interrupt ();
319+ threads .forEach (DynamicThread ::stop );
320+ }
321+
322+ @ Override
323+ public void tellThreadsToStop () {
324+ super .stop ();
325+ }
326+
327+ }
328+
288329 @ Override
289330 public LoadTimeLine buildLoadTimeline () {
290331 LoadTimeLine ret = new LoadTimeLine (name , counting .label + " per second" );
0 commit comments