This is an automated email from the ASF dual-hosted git repository.

benweidig pushed a commit to branch TAP5-2839
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git

commit 6a5ffd3a8591c8a4216c52da463200a305a700be
Author: Ben Weidig <[email protected]>
AuthorDate: Sat Aug 15 10:30:23 2026 +0200

    TAP5-2839: rename IntegrationTestMutex, add to JMH runs
---
 BENCHMARKING.md                                    |  3 ++
 buildSrc/README.md                                 | 19 ++++++++++---
 .../groovy/t5build/IntegrationTestMutex.groovy     | 20 --------------
 buildSrc/src/main/groovy/t5build/Mutex.groovy      | 32 ++++++++++++++++++++++
 .../src/main/groovy/tapestry.jmh-convention.gradle | 14 ++++++++++
 .../main/groovy/tapestry.testng-convention.gradle  |  6 ++--
 6 files changed, 67 insertions(+), 27 deletions(-)

diff --git a/BENCHMARKING.md b/BENCHMARKING.md
index 927ff05c2..0ef8f3bca 100644
--- a/BENCHMARKING.md
+++ b/BENCHMARKING.md
@@ -34,6 +34,9 @@ Benchmarks are **not** part of `check` or `build`, as JMH 
might take minutes or
 
 Every option is documented at the top of 
[`buildSrc/src/main/groovy/tapestry.jmh-convention.gradle`](buildSrc/src/main/groovy/tapestry.jmh-convention.gradle).
 
+A benchmark measures the machine it runs on, so the `jmh` tasks hold a single 
build-service permit and run one at a time, even under a parallel build.
+`./gradlew jmh` across several modules therefore takes as long as all of them 
added up, instead of producing numbers measured while the other modules fought 
for the same cores.
+
 The two that matter most:
 
 - **`-Pjmh.quick`**\
diff --git a/buildSrc/README.md b/buildSrc/README.md
index e09ca7200..ee11ca66d 100644
--- a/buildSrc/README.md
+++ b/buildSrc/README.md
@@ -142,15 +142,22 @@ The `testng.xml` suite file for these modules should 
contain **only** integratio
 Unit tests are handled by the `test` task via testng-engine by Jupiter and 
must not appear in `testng.xml`.
 
 The task **must** be named `testNG`: the convention gives every task with that 
name a permit on the
-`integrationTestMutex` build service, described below.
+`mutex` build service, described below.
 A differently named task gets no permit and will race the others for the ports.
 
-#### Serializing integration tests
+#### Serializing tasks that need the machine to themselves
 
 Every Selenium suite binds the same ports, so no two may run at once.
 Rather than disabling parallel builds (not a simple flag on project-level), 
the convention registers
-an `IntegrationTestMutex` build service with `maxParallelUsages = 1` and 
declares it on each `testNG` task.
-Gradle then admits one integration test task at a time while compilation, 
asset generation and the unit test suites still run in parallel.
+a `Mutex` build service with `maxParallelUsages = 1` and declares it on each 
`testNG` task.
+Gradle then admits one such task at a time while compilation, asset generation 
and the unit test suites still run in parallel.
+
+`tapestry.jmh-convention` registers the same service, under the same name, for 
its `jmh` task.
+A benchmark measures the machine it runs on, so a second benchmark or a 
Selenium suite running beside it does not merely slow the run down, it makes 
the numbers meaningless.
+Sharing one permit across both conventions keeps benchmarks apart from each 
other **and** from the integration tests.
+
+The permit only holds back tasks that ask for it, so a compilation or a unit 
test suite can still land next to a benchmark.
+Use `--max-workers=1` when a run has to be quiet.
 
 ---
 
@@ -158,3 +165,7 @@ Gradle then admits one integration test task at a time 
while compilation, asset
 
 **`GenerateChecksums`**: custom Gradle task type that generates MD5/SHA-256 
checksum
 files for release archives.
+
+**`Mutex`**: build service without behaviour, registered with one permit so 
that the tasks
+declaring it run one at a time — the Selenium integration tests, which share 
fixed ports, and
+the JMH benchmarks, which share the machine.
diff --git a/buildSrc/src/main/groovy/t5build/IntegrationTestMutex.groovy 
b/buildSrc/src/main/groovy/t5build/IntegrationTestMutex.groovy
deleted file mode 100644
index 245bcc743..000000000
--- a/buildSrc/src/main/groovy/t5build/IntegrationTestMutex.groovy
+++ /dev/null
@@ -1,20 +0,0 @@
-package t5build
-
-import org.gradle.api.services.BuildService
-import org.gradle.api.services.BuildServiceParameters
-
-/**
- * A build service with no behaviour, used purely as a mutex.
- *
- * <p>Selenium integration tests bind fixed ports (9090/8443 by default, see
- * {@code @TapestryTestConfiguration}), so two modules testing at the same 
time fight over one
- * socket and the loser dies with "Failed to bind".
- * <p>Registering this service with {@code maxParallelUsages = 1} and 
declaring it on
- * the integration test tasks tells Gradle to allow only one of them at a 
time, whereas
- * compilation, asset generation and the unit test suites carry on in parallel.
- *
- * @see tapestry.testng-convention.gradle
- */
-abstract class IntegrationTestMutex implements 
BuildService<BuildServiceParameters.None>
-{
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/t5build/Mutex.groovy 
b/buildSrc/src/main/groovy/t5build/Mutex.groovy
new file mode 100644
index 000000000..b55a6078c
--- /dev/null
+++ b/buildSrc/src/main/groovy/t5build/Mutex.groovy
@@ -0,0 +1,32 @@
+package t5build
+
+import org.gradle.api.services.BuildService
+import org.gradle.api.services.BuildServiceParameters
+
+/**
+ * A build service with no behaviour, used purely as a mutex.
+ *
+ * <p>Registered with {@code maxParallelUsages = 1} and declared on a task with
+ * {@code usesService}, it makes Gradle admit one such task at a time, whereas 
compilation,
+ * asset generation and the unit test suites carry on in parallel.
+ *
+ * <p>Two kinds of task need this, and they share the single permit:
+ *
+ * <ul>
+ * <li><b>Selenium integration tests</b> bind fixed ports (9090/8443 by 
default, see
+ * {@code @TapestryTestConfiguration}), so two modules testing at the same 
time fight over one
+ * socket and the loser dies with "Failed to bind".</li>
+ * <li><b>JMH benchmarks</b> measure the machine they run on. A second 
benchmark task, or a
+ * Selenium suite, running beside one competes for the same cores and makes 
the numbers
+ * meaningless rather than merely slow.</li>
+ * </ul>
+ *
+ * <p>The permit only holds back tasks that ask for it. It cannot keep a 
compilation or a unit
+ * test suite off the machine while a benchmark runs; {@code --max-workers=1} 
does that.
+ *
+ * @see tapestry.testng-convention.gradle
+ * @see tapestry.jmh-convention.gradle
+ */
+abstract class Mutex implements BuildService<BuildServiceParameters.None>
+{
+}
diff --git a/buildSrc/src/main/groovy/tapestry.jmh-convention.gradle 
b/buildSrc/src/main/groovy/tapestry.jmh-convention.gradle
index 2e8bde94e..d7fbd96e0 100644
--- a/buildSrc/src/main/groovy/tapestry.jmh-convention.gradle
+++ b/buildSrc/src/main/groovy/tapestry.jmh-convention.gradle
@@ -10,6 +10,11 @@
 // Benchmarks are deliberately NOT part of 'check' or 'build': a JMH run takes
 // minutes or longer and has no place in CI.
 //
+// A benchmark measures the machine it runs on, so the 'jmh' tasks hold a 
one-permit
+// build service (t5build.Mutex) and run one at a time. './gradlew jmh' over 
several
+// modules therefore takes as long as the sum of them, rather than producing 
numbers
+// taken while the other modules fought for the cores.
+//
 // `./gradlew jmhCompileAll` (root project) compiles every benchmark source set
 // so they cannot silently rot.
 //
@@ -49,6 +54,8 @@
 //
 //   ./gradlew :tapestry-ioc:jmh -Pjmh.include=Lambda -Pjmh.profilers=gc
 
+import t5build.Mutex
+
 plugins {
     id 'tapestry.java-convention'
 }
@@ -187,6 +194,11 @@ def jmhRunArguments = { File resultFile ->
             rawArguments()
 }
 
+// A benchmark has the machine to itself, or it measures the rest of the build 
instead.
+def mutex = gradle.sharedServices.registerIfAbsent('mutex', Mutex) {
+    maxParallelUsages = 1
+}
+
 tasks.register('jmh', JavaExec) {
     group = 'benchmark'
     description = 'Runs this module\'s JMH benchmarks. Use -Pjmh.include=Regex 
to pick some, -Pjmh.quick for a rough number.'
@@ -194,6 +206,8 @@ tasks.register('jmh', JavaExec) {
     mainClass = 'org.openjdk.jmh.Main'
     classpath = sourceSets.jmh.runtimeClasspath
 
+    usesService(mutex)
+
     // A benchmark result is never up to date: the point of re-running it is 
that
     // the machine, the JDK or the code under measurement may have changed.
     outputs.upToDateWhen { false }
diff --git a/buildSrc/src/main/groovy/tapestry.testng-convention.gradle 
b/buildSrc/src/main/groovy/tapestry.testng-convention.gradle
index 70d0c7636..f568a0770 100644
--- a/buildSrc/src/main/groovy/tapestry.testng-convention.gradle
+++ b/buildSrc/src/main/groovy/tapestry.testng-convention.gradle
@@ -14,7 +14,7 @@
 //   tapestry.testing-base-convention       platform runner, BOM, logging
 //     └─ tapestry.testng-convention          + TestNG + EasyMock + 
testng-engine (this file)
 
-import t5build.IntegrationTestMutex
+import t5build.Mutex
 
 plugins {
     id 'tapestry.testing-base-convention'
@@ -29,12 +29,12 @@ dependencies {
 // Every Selenium suite binds the same ports, so no two of them may run at 
once.
 // Gradle's build services express exactly that: one permit, handed to one 
integration
 // test task at a time, while everything else in the build still runs in 
parallel.
-def integrationTestMutex = 
gradle.sharedServices.registerIfAbsent('integrationTestMutex', 
IntegrationTestMutex) {
+def mutex = gradle.sharedServices.registerIfAbsent('mutex', Mutex) {
     maxParallelUsages = 1
 }
 
 tasks.withType(Test).configureEach {
     if (name == 'testNG') {
-        usesService(integrationTestMutex)
+        usesService(mutex)
     }
 }

Reply via email to