diff --git a/CLAUDE.md b/CLAUDE.md index 0c303ef1..46e9b458 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -113,7 +113,7 @@ Guardrails for module `async-test-analysis` are maintained in that module's own - Each enum constant requires synchronized changes in five places: (1) @AsyncTest attribute, (2) AsyncTestConfig field, (3) AsyncTestConfig.Builder default, (4) both branches of AsyncTestConfig.build() (detectAll block + excludes block), and (5) DetectorRegistry constructor. Adding a value here in isolation breaks the system. + Adding or removing a constant requires synchronized changes in five places: (1) @AsyncTest attribute, (2) AsyncTestConfig field, (3) AsyncTestConfig.Builder default, (4) the resolution line in AsyncTestConfig.build() ((detectAll || flag) && !excludes.contains(TYPE)), and (5) DetectorRegistry constructor. Adding a value here in isolation compiles and detects nothing. The lock is on the constant set, not the file: editing javadoc on existing constants cannot break that invariant and needs no ceremony. diff --git a/README.md b/README.md index 500aadb7..41bbfabe 100644 --- a/README.md +++ b/README.md @@ -439,13 +439,28 @@ See [intellij-plugin/README.md](intellij-plugin/README.md) for full instructions |---|---| | CI (any `GITHUB_ACTIONS` or `CI` env var set, no key) | Auto-mocked — tests run freely | | Local, no key, `-Dlicense.mock.mode=true` | Mock mode active — tests run freely | -| Local, no key, no mock flag | License gate runs; outcome depends on the configured backend | +| Local, no key, no mock flag | **The gate runs and can refuse.** See below | | Real key via `-Dlicense.key=` | Full validation against the licensing backend | +> **First run on a new machine.** With no key configured and no mock flag, the gate consults the +> licensing backend and a denial throws, before any test body runs: +> +> ``` +> java.lang.SecurityException: LICENSE DENIED: +> To run locally without a key: -Dlicense.mock.mode=true +> In CI (GITHUB_ACTIONS or CI env var set, no key): mock mode activates automatically. +> ``` +> +> This is the gate working as intended, not a bug in your test. CI is unaffected: mock mode turns +> itself on there when no key is present, which is why a suite that passes in CI can still stop on +> a developer laptop. + To run locally without a key during development: ``` mvn test -Dlicense.mock.mode=true ``` -Or add to your IDE's JVM args: `-Dlicense.mock.mode=true` +Or add to your IDE's JVM args: `-Dlicense.mock.mode=true`. Setting it once in your IDE's default +JUnit configuration is the usual fix, so it applies to every run rather than being remembered +per-test. Set your email identity when using a real key: `-Dlicense.user.email=you@example.com` diff --git a/async-test-lib/.claude/rules/async-test-configuration.md b/async-test-lib/.claude/rules/async-test-configuration.md index 3f4eba91..e2d0e653 100644 --- a/async-test-lib/.claude/rules/async-test-configuration.md +++ b/async-test-lib/.claude/rules/async-test-configuration.md @@ -8,14 +8,14 @@ paths: ["**/DetectorType.java", "**/AsyncTestConfig.java", "**/DetectorRegistry. ## Locked Status ### se.deversity.asynctest.DetectorType -- **Reason**: Each enum constant requires synchronized changes in five places: (1) @AsyncTest attribute, (2) AsyncTestConfig field, (3) AsyncTestConfig.Builder default, (4) both branches of AsyncTestConfig.build() (detectAll block + excludes block), and (5) DetectorRegistry constructor. Adding a value here in isolation breaks the system. +- **Reason**: Adding or removing a constant requires synchronized changes in five places: (1) @AsyncTest attribute, (2) AsyncTestConfig field, (3) AsyncTestConfig.Builder default, (4) the resolution line in AsyncTestConfig.build() ((detectAll || flag) && !excludes.contains(TYPE)), and (5) DetectorRegistry constructor. Adding a value here in isolation compiles and detects nothing. The lock is on the constant set, not the file: editing javadoc on existing constants cannot break that invariant and needs no ceremony. ## Mirrored — Keep In Sync ### se.deversity.asynctest.DetectorType - **Rule**: Free to change, but every mirror must change in the same commit. -- **Mirrors**: se.deversity.asynctest.AsyncTest, se.deversity.asynctest.AsyncTestConfig, se.deversity.asynctest.DetectorRegistry, se.deversity.asynctest.spi.LegacyDetectorFactories, META-INF/services/se.deversity.asynctest.spi.DetectorFactory -- **Reason**: A detector is only reachable from the public API when all of these agree. The enum constant is the name users type in @AsyncTest(excludes=...); the annotation attribute, the config field and its Builder default carry it through resolution; the registry constructor instantiates it; and the SPI factory plus its services entry are what detectAll loads. Adding the constant alone compiles and silently detects nothing. +- **Mirrors**: se.deversity.asynctest.AsyncTest, se.deversity.asynctest.AsyncTestConfig, se.deversity.asynctest.DetectorRegistry, se.deversity.asynctest.spi.LegacyDetectorFactories, META-INF/async-test/builtin-detector-factories +- **Reason**: A detector is only reachable from the public API when all of these agree. The enum constant is the name users type in @AsyncTest(excludes=...); the annotation attribute, the config field and its Builder default carry it through resolution; the registry constructor instantiates it; and the SPI factory plus its entry in the built-in factory list are what detectAll loads. Adding the constant alone compiles and silently detects nothing. - **Enforced by**: se.deversity.asynctest.spi.AllDetectorsSpiCoverageTest ## Context & Focus diff --git a/async-test-lib/CLAUDE.md b/async-test-lib/CLAUDE.md index 52b9e1b3..e32af22f 100644 --- a/async-test-lib/CLAUDE.md +++ b/async-test-lib/CLAUDE.md @@ -12,7 +12,7 @@ you are editing here. - Each enum constant requires synchronized changes in five places: (1) @AsyncTest attribute, (2) AsyncTestConfig field, (3) AsyncTestConfig.Builder default, (4) both branches of AsyncTestConfig.build() (detectAll block + excludes block), and (5) DetectorRegistry constructor. Adding a value here in isolation breaks the system. + Adding or removing a constant requires synchronized changes in five places: (1) @AsyncTest attribute, (2) AsyncTestConfig field, (3) AsyncTestConfig.Builder default, (4) the resolution line in AsyncTestConfig.build() ((detectAll || flag) && !excludes.contains(TYPE)), and (5) DetectorRegistry constructor. Adding a value here in isolation compiles and detects nothing. The lock is on the constant set, not the file: editing javadoc on existing constants cannot break that invariant and needs no ceremony. diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/AsyncAssert.java b/async-test-lib/src/main/java/se/deversity/asynctest/AsyncAssert.java index 34c2f47e..3e8e85a1 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/AsyncAssert.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/AsyncAssert.java @@ -133,18 +133,24 @@ public void awaitDone(Duration timeout) { } /** + * Get result. + * * @return the future's resolved value, or {@code null} if it has not completed * successfully yet (or completed exceptionally) */ public @Nullable T getResult() { return result.get(); } /** + * Get error. + * * @return the exception the future completed with, or {@code null} if it has not * completed exceptionally */ public @Nullable Throwable getError() { return error.get(); } /** + * Is complete. + * * @return {@code true} once the observed future has completed, successfully or not */ public boolean isComplete() { return complete; } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/AsyncTestConfig.java b/async-test-lib/src/main/java/se/deversity/asynctest/AsyncTestConfig.java index 8af5de62..45038791 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/AsyncTestConfig.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/AsyncTestConfig.java @@ -528,7 +528,12 @@ private AsyncTestConfig(Builder b) { licenseMockMode = b.licenseMockMode; } - /** Builds a config from an {@link AsyncTest} annotation instance. */ + /** + * Builds a config from an {@link AsyncTest} annotation instance. + * + * @param ann the annotation instance to read the declared values from + * @return the resolved configuration for this run + */ public static AsyncTestConfig from(AsyncTest ann) { return from(ann, ann.threads()); } @@ -540,6 +545,10 @@ public static AsyncTestConfig from(AsyncTest ann) { * other annotation fields. * * @since 1.6.0 + * + * @param ann the annotation instance to read the declared values from + * @param threadsOverride thread count to use instead of {@link AsyncTest#threads()}, as supplied by a parameterised template + * @return the resolved configuration for this run */ public static AsyncTestConfig from(AsyncTest ann, int threadsOverride) { // Check for global benchmarking system property @@ -725,7 +734,9 @@ public static AsyncTestConfig from(AsyncTest ann, int threadsOverride) { .build(); } - /** {@return a new builder initialised with the library defaults} */ + /** + * {@return a new builder initialised with the library defaults} + */ public static Builder builder() { return new Builder(); } @@ -1768,6 +1779,9 @@ public Builder excludes(DetectorType[] v) { * {@link #excludes(DetectorType[])} still layers on top. * * @since 1.7.0 + * + * @param v the detectors to enable exclusively; {@code null} or empty leaves the selection untouched + * @return this builder */ public Builder includes(DetectorType[] v) { if (v != null && v.length > 0) { @@ -1776,7 +1790,9 @@ public Builder includes(DetectorType[] v) { return this; } - /** {@return the resolved configuration, with preset, includes and excludes applied} */ + /** + * {@return the resolved configuration, with preset, includes and excludes applied} + */ public AsyncTestConfig build() { if (!includes.isEmpty()) { // includes wins over detectAll/per-flag setters: force the diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/AsyncTestContext.java b/async-test-lib/src/main/java/se/deversity/asynctest/AsyncTestContext.java index 8e1ed4b0..c79a85b5 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/AsyncTestContext.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/AsyncTestContext.java @@ -353,7 +353,11 @@ public final class AsyncTestContext { // Exposed via atomicityValidator() so se.deversity.asynctest.telemetry.TelemetryBridge // can route drained agent field-access events into the live per-test detector. final @Nullable AtomicityValidator atomicityValidator; - + /** + * Creates a AsyncTestContext. + * + * @param cfg the resolved configuration deciding which detectors this context installs + */ public AsyncTestContext(AsyncTestConfig cfg) { this.registry = new DetectorRegistry(cfg); // Mirror registry references so package-private field access still works @@ -630,13 +634,19 @@ public AsyncTestContext(AsyncTestConfig cfg) { // ---- Lifecycle (called by ConcurrencyRunner) ---- - /** Installs {@code ctx} into the calling thread's ThreadLocal. */ + /** + * Installs {@code ctx} into the calling thread's ThreadLocal. + * + * @param ctx the context to bind to the calling thread; must be paired with an {@code uninstall()} in a {@code finally} + */ @AICallersOnly({"se.deversity.asynctest.runner.ConcurrencyRunner"}) public static void install(AsyncTestContext ctx) { CURRENT.set(ctx); } - /** Removes the context from the calling thread's ThreadLocal. */ + /** + * Removes the context from the calling thread's ThreadLocal. + */ @AIIdempotent(reason = "ThreadLocal.remove() is documented as a no-op when the thread has no value set; the install/uninstall symmetry rule (CLAUDE.md) tolerates extra uninstalls. ConcurrencyRunner relies on this in its outermost-finally cleanup.") public static void uninstall() { CURRENT.remove(); @@ -682,7 +692,11 @@ public static long replaySeed() { return ctx == null ? 0L : ctx.currentRoundSeed; } - /** Internal: set by {@code ConcurrencyRunner} before each invocation round. */ + /** + * Internal: set by {@code ConcurrencyRunner} before each invocation round. + * + * @param seed the seed for this round, so a reported interleaving can be replayed + */ public void setReplaySeedForRound(long seed) { this.currentRoundSeed = seed; } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/AsyncTestListenerRegistry.java b/async-test-lib/src/main/java/se/deversity/asynctest/AsyncTestListenerRegistry.java index 84d99342..b1f5526f 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/AsyncTestListenerRegistry.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/AsyncTestListenerRegistry.java @@ -146,7 +146,7 @@ public static void fireTestFailed(Throwable cause) { *

Severity is parsed from the report text using {@link IssueSeverity} markers * (emoji or keyword). Reports with no recognisable marker default to {@link IssueSeverity#HIGH}. * - * @param detectorName the detector name + * @param detectorName the reporting detector, as it appears in the report * @param report the report content */ public static void fireDetectorReport(String detectorName, String report) { diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/DetectorType.java b/async-test-lib/src/main/java/se/deversity/asynctest/DetectorType.java index 6e0fef97..fc387aa4 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/DetectorType.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/DetectorType.java @@ -10,192 +10,319 @@ * Enumerates all available detectors for type-safe opt-outs. * Used with {@link AsyncTest#excludes()}. */ -@AILocked(reason = "Each enum constant requires synchronized changes in five places: (1) @AsyncTest attribute, (2) AsyncTestConfig field, (3) AsyncTestConfig.Builder default, (4) both branches of AsyncTestConfig.build() (detectAll block + excludes block), and (5) DetectorRegistry constructor. Adding a value here in isolation breaks the system.") +@AILocked(reason = "Adding or removing a constant requires synchronized changes in five places: (1) @AsyncTest attribute, (2) AsyncTestConfig field, (3) AsyncTestConfig.Builder default, (4) the resolution line in AsyncTestConfig.build() ((detectAll || flag) && !excludes.contains(TYPE)), and (5) DetectorRegistry constructor. Adding a value here in isolation compiles and detects nothing. The lock is on the constant set, not the file: editing javadoc on existing constants cannot break that invariant and needs no ceremony.") @AIKeepInSync( mirrors = { "se.deversity.asynctest.AsyncTest", "se.deversity.asynctest.AsyncTestConfig", "se.deversity.asynctest.DetectorRegistry", "se.deversity.asynctest.spi.LegacyDetectorFactories", - "META-INF/services/se.deversity.asynctest.spi.DetectorFactory" + "META-INF/async-test/builtin-detector-factories" }, reason = "A detector is only reachable from the public API when all of these agree. The enum " + "constant is the name users type in @AsyncTest(excludes=...); the annotation attribute, " + "the config field and its Builder default carry it through resolution; the registry " - + "constructor instantiates it; and the SPI factory plus its services entry are what " - + "detectAll loads. Adding the constant alone compiles and silently detects nothing.", + + "constructor instantiates it; and the SPI factory plus its entry in the built-in factory list are " + + "what detectAll loads. Adding the constant alone compiles and silently detects nothing.", enforcedBy = "se.deversity.asynctest.spi.AllDetectorsSpiCoverageTest" ) @API(status = Status.STABLE) public enum DetectorType { // Phase 1 + /** Enhanced deadlock detector that analyzes thread dumps and identifies circular lock dependencies, thread states, and provides actionable diagnostics. */ DEADLOCKS, + /** Monitors field access patterns to detect visibility issues (stale memory). */ VISIBILITY, + /** Detects thread starvation and livelocks. */ LIVELOCKS, // Phase 2: Core + /** Detects False Sharing - when multiple threads access adjacent memory locations that fall within the same CPU cache line (typically 64 bytes). */ FALSE_SHARING, + /** Detects spurious wakeups and lost notifications in wait/notify patterns. */ WAKEUP_ISSUES, + /** Validates that objects are fully constructed before being shared across threads. */ CONSTRUCTOR_SAFETY, + /** Detects the ABA Problem in atomic operations. */ ABA_PROBLEM, + /** Detects lock ordering violations that can cause deadlocks. */ LOCK_ORDER, + /** Monitors synchronizer behavior (CyclicBarrier, Phaser, CountDownLatch, etc.) Problems detected: - Threads not advancing synchronously through barriers - Phaser . */ SYNCHRONIZERS, + /** Monitors thread pool / executor health and issues. */ THREAD_POOL, + /** Detects memory ordering violations and compiler reordering issues. */ MEMORY_ORDERING, + /** Monitors event flow in async pipelines and detects signal loss. */ ASYNC_PIPELINE, + /** Monitors ReadWriteLock fairness and detects writer starvation. */ READ_WRITE_LOCK_FAIRNESS, // Phase 2: Monitors + /** Detects semaphore misuse patterns in concurrent code. */ SEMAPHORE, + /** Detects exception handling issues in CompletableFuture chains. */ COMPLETABLE_FUTURE_EXCEPTIONS, + /** Detects CompletableFuture instances that are created but never completed. */ COMPLETABLE_FUTURE_COMPLETION_LEAKS, + /** Detects virtual thread pinning issues. */ VIRTUAL_THREAD_PINNING, + /** Detects thread pool deadlock scenarios. */ THREAD_POOL_DEADLOCK, + /** Detects concurrent modification issues in collections during iteration. */ CONCURRENT_MODIFICATIONS, + /** Detects lock leak patterns where locks are acquired but never released. */ LOCK_LEAKS, + /** Detects concurrent use of non-thread-safe Random instances. */ SHARED_RANDOM, + /** Detects BlockingQueue misuse patterns in concurrent code. */ BLOCKING_QUEUE, + /** Detects Condition variable misuse patterns in concurrent code. */ CONDITION_VARIABLES, + /** Detects concurrent use of non-thread-safe SimpleDateFormat instances. */ SIMPLE_DATE_FORMAT, + /** Detects unsafe operations in parallel streams. */ PARALLEL_STREAMS, + /** Detects resource leak patterns in concurrent code. */ RESOURCE_LEAKS, // Phase 2: Additional Concurrency + /** Detects CountDownLatch misuse patterns: - Latch timeout (await with timeout expiring) - Missing countDown (latch never reaches zero) - Extra countDown (more cou. */ COUNTDOWN_LATCH, + /** Detects CyclicBarrier misuse patterns: - Barrier timeout (await with timeout expiring) - Broken barrier (barrier broken due to thread interruption or timeout) -. */ CYCLIC_BARRIER, + /** Detects ReentrantLock misuse patterns: - Lock starvation (thread waiting excessively long) - Unfair lock acquisition (threads not acquiring in FIFO order) - Loc. */ REENTRANT_LOCK, + /** Detects volatile array element visibility issues. */ VOLATILE_ARRAY, + /** Detects broken double-checked locking patterns. */ DOUBLE_CHECKED_LOCKING, + /** Detects wait/notify patterns without timeout (potential deadlock). */ WAIT_TIMEOUT, + /** Detects high lock contention — monitors where many threads compete to acquire the same lock, causing threads to spend significant time in BLOCKED state. */ LOCK_CONTENTION, + /** Detects the anti-pattern of synchronizing on a non-final, reassignable object reference. */ SYNCHRONIZED_NON_FINAL, + /** Detects missed (lost) signals — situations where notify() or notifyAll() is called on a condition before any thread is waiting on it, causing the signal to be s. */ MISSED_SIGNAL, + /** Detects lazy-initialization races — situations where multiple threads simultaneously observe a field as null and each proceeds to initialize it, causing the ini. */ LAZY_INIT_RACE, // Phase 2: Advanced Concurrency Utilities + /** Detects Phaser misuse patterns: - Missing arrive() calls (phaser never advances) - Phaser timeout (awaitAdvance with timeout expiring) - Phaser termination (pha. */ PHASER, + /** Detects StampedLock misuse patterns: - Optimistic read without validation - Lock upgrade issues (optimistic → write) - Stamp not released in finally block - Wro. */ STAMPED_LOCK, + /** Detects Exchanger misuse patterns: - Exchange timeout (exchange with timeout expiring) - Missing exchange partner (odd number of threads) - InterruptedException. */ EXCHANGER, + /** Detects ScheduledExecutorService misuse patterns: - Task scheduling without proper shutdown - Fixed delay vs fixed rate confusion - Long-running tasks blocking . */ SCHEDULED_EXECUTOR, + /** Detects ForkJoinPool misuse patterns: - Fork without join - RecursiveTask not returning result - Pool starvation (too few threads) - Exception in forked tasks. */ FORK_JOIN_POOL, + /** Detects ThreadFactory misuse patterns: - Missing uncaught exception handler - Non-daemon threads in thread pools - Missing thread naming convention - Thread pri. */ THREAD_FACTORY, // Phase 3 + /** Detects potential race conditions by tracking cross-thread field accesses. */ RACE_CONDITIONS, + /** Monitors ThreadLocal lifecycle usage to detect leaks and poor cleanup. */ THREAD_LOCAL_LEAKS, + /** Detects spin loops that perform excessive work before yielding or blocking. */ BUSY_WAITING, + /** Tracks compound operations that should behave atomically. */ ATOMICITY_VIOLATIONS, + /** Tracks caught interrupts and whether they were restored or ignored. */ INTERRUPT_MISHANDLING, // Phase 4: Infrastructure & Resource Management + /** Detects thread leaks in concurrent code. */ THREAD_LEAKS, + /** Detects Thread.sleep() calls while holding a lock. */ SLEEP_IN_LOCK, + /** Detects unbounded queue usage in concurrent code. */ UNBOUNDED_QUEUE, + /** Detects thread starvation in thread pools. */ THREAD_STARVATION, // Phase 5: Thread-Safety of Common Types + /** Detects concurrent use of non-thread-safe java.util.Calendar instances. */ CALENDAR, + /** Detects non-thread-safe collections shared across multiple threads without synchronization. */ SHARED_COLLECTIONS, + /** Detects misuse of java.util.Timer in concurrent code. */ TIMER, + /** Detects CopyOnWriteArrayList and CopyOnWriteArraySet used in write-heavy concurrent scenarios where the copy-on-write overhead becomes a significant performance. */ COPY_ON_WRITE_COLLECTIONS, + /** Detects StringBuilder instances shared across multiple threads without synchronization. */ STRING_BUILDER, // Phase 6: Virtual Thread Concurrency (Java 21+) + /** Detects misuse of Java 21+ Structured Concurrency (StructuredTaskScope). */ STRUCTURED_CONCURRENCY, + /** Detects ThreadLocal context leaks in virtual threads. */ VIRTUAL_THREAD_CONTEXT_LEAKS, + /** Detects misuse of Java 21+ ScopedValue. */ SCOPED_VALUE, + /** Detects CPU-bound tasks running on virtual threads. */ VIRTUAL_THREAD_CPU_BOUND, + /** Detects potential carrier thread exhaustion caused by concurrent blocking of virtual threads. */ VIRTUAL_THREAD_CARRIER_EXHAUSTION, // Phase 7: High-Level Concurrency Patterns + /** Detects HTTP client concurrency issues, particularly with Java 11+ HttpClient. */ HTTP_CLIENT, + /** Detects I/O stream (InputStream, OutputStream, Reader, Writer) not being properly closed in concurrent code. */ STREAM_CLOSING, + /** Detects concurrent access to non-thread-safe cache implementations. */ CACHE_CONCURRENCY, + /** Detects improper CompletableFuture chain usage in concurrent code. */ COMPLETABLEFUTURE_CHAIN, // Phase 8: Lifecycle & Structural Correctness + /** Detects ExecutorService instances that are created and used but never properly shut down, or shut down without a subsequent awaitTermination() call. */ EXECUTOR_SHUTDOWN, + /** Detects mutable objects used as java.util.HashMap / java.util.HashSet keys that are mutated after insertion. */ MUTABLE_MAP_KEY, + /** Detects the nested monitor lockout anti-pattern: performing a blocking operation (e.g. */ NESTED_MONITOR_LOCKOUT, + /** Detects incorrect java.util.concurrent.locks.ReentrantReadWriteLock downgrade and upgrade patterns. */ LOCK_DOWNGRADE, + /** Detects misuse of InheritableThreadLocal in thread-pool environments. */ INHERITABLE_THREAD_LOCAL, // Phase 9: Repository & Environment State + /** Detects untracked or uncommitted changes in the Git repository. */ UNCOMMITTED_CHANGES, // Phase 10: API Traps & Subtle Concurrency Bugs + /** Detects ThreadLocal values that bleed from one task into the next task executing on the same pooled thread — cross-task state contamination. */ THREAD_LOCAL_CONTAMINATION, + /** Detects non-atomic compound updates on AtomicInteger, AtomicLong, AtomicReference, and similar: using get() then set() instead of compareAndSet(), silently losi. */ ATOMIC_NON_ATOMIC_UPDATE, + /** Detects iteration over Collections#synchronizedList, Collections#synchronizedMap, or Collections#synchronizedSet wrappers without holding the wrapper's intrinsi. */ SYNCHRONIZED_COLLECTION_ITERATION, + /** Detects java.util.Formatter, java.io.PrintWriter, and java.io.PrintStream instances shared across multiple threads without external synchronization. */ SHARED_FORMATTER, + /** Detects recursive calls to ConcurrentHashMap#computeIfAbsent (or compute / computeIfPresent / merge) on the same map and key from the same thread — a well-known. */ CONCURRENT_MAP_COMPUTE_RECURSION, + /** Detects synchronized blocks that lock on interned String literals or JVM-cached boxed primitives (Integer / Long in the range [-128, 127]). */ SYNCHRONIZED_ON_LITERAL, + /** Detects classes that use synchronized(this) (or synchronized instance methods) while this is publicly accessible — exposing the internal lock to external caller. */ PUBLIC_LOCK_EXPOSURE, + /** Detects blocking calls (Thread#sleep, Object#wait, Future.get(), blocking I/O) made from within a java.util.concurrent.ForkJoinTask body. */ FORK_JOIN_TASK_BLOCKING, + /** Detects incorrect usage of java.util.concurrent.locks.StampedLock optimistic reads: reading data after tryOptimisticRead() without calling validate(stamp), or c. */ OPTIMISTIC_READ_VALIDATION, + /** Detects blocking operations (Thread#sleep, Object#wait, blocking I/O, Future.get()) running inside CompletableFuture stages that were submitted to the common Fo. */ CF_COMMON_POOL_BLOCKING, // Phase 11: Thread-Safety of Additional Types & Patterns + /** Detects java.util.regex.Matcher instances shared across multiple threads. */ SHARED_MATCHER, + /** Detects java.text.DecimalFormat and java.text.NumberFormat instances shared across multiple threads without external synchronization. */ SHARED_DECIMAL_FORMAT, + /** Detects race conditions around java.lang.ref.WeakReference and java.lang.ref.SoftReference get() calls. */ WEAK_REFERENCE_RACE, + /** Detects lambda / Runnable / java.util.concurrent.Callable instances whose captured mutable state is mutated concurrently from multiple threads. */ STATEFUL_LAMBDA, + /** Detects java.security.MessageDigest instances shared across multiple threads. */ SHARED_MESSAGE_DIGEST, // Phase 12: Operational & Hygiene Concurrency Issues + /** Detects InterruptedException catches where the interrupt flag is silently swallowed. */ INTERRUPT_SWALLOWING, + /** Detects SLF4J MDC (Mapped Diagnostic Context) entries that are not cleared at task end, causing leakage to the next task run on the same pooled thread. */ MDC_CONTEXT_LEAK, + /** Detects concurrent mutations to JVM system properties via System#setProperty or System#clearProperty during an async test run. */ SYSTEM_PROPERTY_MUTATION, + /** Detects java.util.concurrent.Future instances returned from java.util.concurrent.ExecutorService#submit (or similar) that are never inspected. */ FUTURE_IGNORED, + /** Detects explicit garbage-collection invocations (System#gc() or Runtime#gc()) during a concurrent test run. */ EXPLICIT_GC, + /** Detects use of deprecated and unsafe Thread API methods: Thread.stop(), Thread.suspend(), Thread.resume(), Thread.destroy(), and Thread.countStackFrames(). */ DEPRECATED_THREAD_API, + /** Detects XML parser instances shared across multiple threads. */ SHARED_XML_PARSER, + /** Detects synchronized blocks that lock on cached boxed primitives or on JEP 390 value-based classes. */ BOXED_PRIMITIVE_LOCK, + /** Detects java.util.TimeZone instances whose mutable state is modified while being accessed from multiple threads. */ SHARED_TIMEZONE, + /** Detects threads that are started without a custom Thread.UncaughtExceptionHandler and that subsequently throw an uncaught exception. */ UNCAUGHT_EXCEPTION_HANDLER, // Phase 13: Additional concurrency-bug categories (1.0.0+) + /** Detects Thread instances created by user code without Thread#setDaemon(boolean) setDaemon(true) that remain alive at detector tear-down. */ DAEMON_THREAD_HYGIENE, + /** Detects attempted Object#notify() / Object#notifyAll() calls where the calling thread does not hold the target monitor. */ NOTIFY_WITHOUT_MONITOR, + /** Detects SecureRandom instances accessed from multiple threads. */ SHARED_SECURE_RANDOM, + /** Detects WeakHashMap or IdentityHashMap instances accessed from more than one thread. */ WEAK_HASH_MAP_SHARED, + /** Detects Connection, Statement, PreparedStatement, or ResultSet instances accessed from more than one thread. */ JDBC_CONNECTION_SHARED, // Phase 14: Additional thread-unsafe primitives & publication hazards (1.7.0+) + /** Detects stateful JCA cryptographic primitives — Cipher, Mac, and Signature — shared across multiple threads. */ SHARED_STATEFUL_CRYPTO, + /** Detects non-atomic check-then-act compound operations on a ConcurrentMap. */ CONCURRENT_MAP_CHECK_THEN_ACT, + /** Detects Deflater / Inflater instances shared across threads. */ SHARED_DEFLATER, + /** Detects this-escape: a constructor publishing a reference to the object being built before construction finishes. */ THIS_ESCAPE, + /** Detects misuse of ThreadLocalRandom: caching the reference returned by ThreadLocalRandom#current() and using it from a different thread. */ THREAD_LOCAL_RANDOM_MISUSE, // Phase 15: Asynchronous flow & lock-usage hazards (1.8.0+) + /** Detects CompletableFuture.obtrudeValue() or obtrudeException() calls which bypass normal completion pipelines and trigger race conditions or state inconsistency. */ COMPLETABLE_FUTURE_OBTRUDE_ABUSE, + /** Detects wait() or Condition.await() calls invoked outside of a while loop condition check, exposing the thread to spurious wakeups. */ SPURIOUS_WAKEUP_HAZARD, + /** Detects attempts to upgrade a ReentrantReadWriteLock from a read lock to a write lock on the same thread, which inevitably deadlocks. */ LOCK_UPGRADE_DEADLOCK, + /** Detects misuse of Lock.tryLock(), such as calling unlock() unconditionally when tryLock() returned false. */ TRY_LOCK_MISUSE, + /** Detects blocking calls (like get(), join(), sleep()) inside CompletableFuture callback pipelines, which can cause pool thread starvation or deadlocks. */ COMPLETABLE_FUTURE_BLOCKING_CALLBACK, // Phase 16: JDK 25/26 preview-era concurrency detectors + /** Detects misuse of Java 25+ StableValue (JEP 502 — Stable Values, Preview in JDK 25, continuing in JDK 26). */ STABLE_VALUE_MISUSE, + /** Detects misuse of the StructuredTaskScope API (JEP 505/525 — Structured Concurrency; fifth preview in JDK 25, sixth preview in JDK 26). */ STRUCTURED_TASK_SCOPE_MISUSE, + /** Detects unsafe use of Stream.gather(Gatherer) (JEP 485 — Stream Gatherers, finalized in JDK 24 and the standard intermediate-operation extension point in JDK 25. */ GATHERER_CONCURRENCY_MISUSE, // Phase 17: Shared stateful JDK objects, I/O position races & contention advisories + /** Detects java.nio.Buffer / java.nio.ByteBuffer instances shared across threads without coordination. */ SHARED_BYTE_BUFFER, + /** Detects CharsetEncoder / CharsetDecoder instances shared across threads. */ SHARED_CHARSET_CODER, + /** Detects Checksum implementations (e.g. */ SHARED_CHECKSUM, + /** Detects FileChannel / SeekableByteChannel instances whose implicit position is read or mutated from more than one thread. */ FILE_CHANNEL_POSITION_RACE, + /** Detects a single Iterator, ListIterator, or Spliterator instance being driven from more than one thread. */ SHARED_ITERATOR, + /** Advisory detector for hot compare-and-swap loops on shared AtomicLong/AtomicInteger/AtomicReference instances that would perform better as LongAdder/LongAccumul. */ HIGH_CONTENTION_ATOMIC, + /** Detects serializer/mapper instances (Jackson ObjectMapper, a Gson built via GsonBuilder, or similar) that are reconfigured after concurrent use has begun. */ SHARED_JSON_MAPPER_RECONFIG, // Phase 18: JDK 25/26 GA-era concurrency detectors (1.8.0+) + /** Detects misuse of Java 26+ LazyConstant (Lazy Constants, second preview in JDK 26 — the renamed and radically simplified successor of the JDK 25 StableValue pre. */ LAZY_CONSTANT_MISUSE, + /** Detects reflective mutation of final fields (Field.setAccessible(true) + Field.set(...)), which JDK 26 warns about and future JDK releases will deny by default . */ FINAL_FIELD_MUTATION, + /** Detects javax.crypto.KDF (Key Derivation Function, JEP 510 — final in JDK 25) instances shared across threads. */ SHARED_KDF, // Executor / future / latch detectors that shipped implemented and tested but unwired + /** Detects CountDownLatch-style misuse such as missing or extra countdowns. */ LATCH_MISUSE, + /** Detects self-deadlock patterns in single-thread or bounded executors. */ EXECUTOR_DEADLOCK, + /** Detects blocking waits on sibling futures inside bounded executors. */ FUTURE_BLOCKING } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/Preset.java b/async-test-lib/src/main/java/se/deversity/asynctest/Preset.java index 57ac35f8..b94d1b7f 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/Preset.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/Preset.java @@ -105,13 +105,19 @@ public enum Preset { * lets callers distinguish "use everything available right now" from "use * exactly this set", which matters when new detectors are added in future * releases. + * + * @return the detectors this preset turns on, as an unmodifiable set */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "enabled is already an unmodifiable Set.copyOf snapshot stored in a final field") public @Nullable Set enabled() { return enabled; } - /** True when the preset is the legacy default. */ + /** + * True when the preset is the legacy default. + * + * @return {@code true} when this preset enables every {@link DetectorType} + */ public boolean isAll() { return this == ALL || this == STRICT; } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkComparator.java b/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkComparator.java index 1a035a99..35e7e728 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkComparator.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkComparator.java @@ -170,6 +170,9 @@ private String buildRegressionMessage(BenchmarkComparisonResult result) { /** * Load baseline for a specific benchmark key. + * + * @param benchmarkKey identifies the stored baseline, normally {@code testClass#testMethod} + * @return the stored baseline for that key, or empty when none has been recorded */ public Optional loadBaseline(String benchmarkKey) { File storeFile = benchmarkStorePath.toFile(); @@ -224,6 +227,8 @@ private Map readStore(File storeFile) throws IOExceptio /** * Save a benchmark result as the new baseline. + * + * @param result the run to store as the new baseline, replacing any existing one for its key */ public void saveBaseline(BenchmarkResult result) { Map store = loadAllBaselines(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkComparisonResult.java b/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkComparisonResult.java index 9438ef42..53c06594 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkComparisonResult.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkComparisonResult.java @@ -29,6 +29,9 @@ private BenchmarkComparisonResult(Builder builder) { /** * Create a result for the first run (no baseline exists). + * + * @param currentResult the run just measured + * @return a result marked as a first run, with no comparison performed */ public static BenchmarkComparisonResult firstRun(BenchmarkResult currentResult) { return builder() @@ -41,43 +44,59 @@ public static BenchmarkComparisonResult firstRun(BenchmarkResult currentResult) .build(); } - /** {@return the current result} */ + /** + * {@return the current result} + */ public @Nullable BenchmarkResult getCurrentResult() { return currentResult; } - /** {@return the baseline result} */ + /** + * {@return the baseline result} + */ public @Nullable BenchmarkResult getBaselineResult() { return baselineResult; } - /** {@return the percent change} */ + /** + * {@return the percent change} + */ public double getPercentChange() { return percentChange; } - /** {@return whether regression} */ + /** + * {@return whether regression} + */ public boolean isRegression() { return isRegression; } - /** {@return whether improvement} */ + /** + * {@return whether improvement} + */ public boolean isImprovement() { return isImprovement; } - /** {@return whether first run} */ + /** + * {@return whether first run} + */ public boolean isFirstRun() { return isFirstRun; } - /** {@return the threshold percent} */ + /** + * {@return the threshold percent} + */ public double getThresholdPercent() { return thresholdPercent; } /** * Check if the result is within acceptable bounds (not a regression or improvement). + * + * @return {@code true} when the change stayed inside the configured threshold */ public boolean isWithinThreshold() { return !isRegression && !isImprovement; @@ -128,8 +147,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(currentResult, baselineResult, percentChange, isRegression, isImprovement, isFirstRun); } - /** {@return the builder} */ - + /** + * {@return the builder} + */ public static Builder builder() { return new Builder(); } @@ -145,10 +165,9 @@ public static class Builder { /** * Current result. * - * @param currentResult the current result - * @return the current result + * @param currentResult the run just measured + * @return this builder */ - public Builder currentResult(BenchmarkResult currentResult) { this.currentResult = currentResult; return this; @@ -156,10 +175,9 @@ public Builder currentResult(BenchmarkResult currentResult) { /** * Baseline result. * - * @param baselineResult the baseline result - * @return the baseline result + * @param baselineResult the previously stored run being compared against + * @return this builder */ - public Builder baselineResult(BenchmarkResult baselineResult) { this.baselineResult = baselineResult; return this; @@ -167,10 +185,9 @@ public Builder baselineResult(BenchmarkResult baselineResult) { /** * Percent change. * - * @param percentChange the percent change - * @return the percent change + * @param percentChange change against the baseline, positive when the current run is slower + * @return this builder */ - public Builder percentChange(double percentChange) { this.percentChange = percentChange; return this; @@ -178,10 +195,9 @@ public Builder percentChange(double percentChange) { /** * Is regression. * - * @param isRegression the is regression - * @return the is regression + * @param isRegression {@code true} when the slowdown exceeded the configured threshold + * @return this builder */ - public Builder isRegression(boolean isRegression) { this.isRegression = isRegression; return this; @@ -189,10 +205,9 @@ public Builder isRegression(boolean isRegression) { /** * Is improvement. * - * @param isImprovement the is improvement - * @return the is improvement + * @param isImprovement {@code true} when the current run was measurably faster than the baseline + * @return this builder */ - public Builder isImprovement(boolean isImprovement) { this.isImprovement = isImprovement; return this; @@ -200,10 +215,9 @@ public Builder isImprovement(boolean isImprovement) { /** * Is first run. * - * @param isFirstRun the is first run - * @return the is first run + * @param isFirstRun {@code true} when no baseline existed, so nothing was compared + * @return this builder */ - public Builder isFirstRun(boolean isFirstRun) { this.isFirstRun = isFirstRun; return this; @@ -211,16 +225,16 @@ public Builder isFirstRun(boolean isFirstRun) { /** * Threshold percent. * - * @param thresholdPercent the threshold percent - * @return the threshold percent + * @param thresholdPercent the slowdown, in percent, above which a change counts as a regression + * @return this builder */ - public Builder thresholdPercent(double thresholdPercent) { this.thresholdPercent = thresholdPercent; return this; } - /** {@return the build} */ - + /** + * {@return the build} + */ public BenchmarkComparisonResult build() { return new BenchmarkComparisonResult(this); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkRecorder.java b/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkRecorder.java index 509be453..b97aa7b6 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkRecorder.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkRecorder.java @@ -43,21 +43,33 @@ public class BenchmarkRecorder { private boolean benchmarkingEnabled; /** + * Creates a BenchmarkRecorder. + * * @see #BenchmarkRecorder(AsyncTestConfig, String, String, int) — prefer that * overload when the actual thread count may differ from {@code config.threads} * (e.g. {@code virtualThreadStressMode} overrides it). This overload records * {@code config.threads} as-is and exists for direct/unit-test construction. + * + * @param config the configuration whose thread and invocation counts the recorded run is keyed by + * @param testClass the fully-qualified test class, forming half of the benchmark key + * @param testMethod the test method, forming the other half of the benchmark key */ public BenchmarkRecorder(AsyncTestConfig config, String testClass, String testMethod) { this(config, testClass, testMethod, config.threads); } /** + * Creates a BenchmarkRecorder. + * * @param actualThreads the thread count actually used for this run, which may * differ from {@code config.threads} when * {@code virtualThreadStressMode} overrides it; recorded on * the baseline so comparisons are labeled correctly. * @since 1.7.0 + * + * @param config the configuration whose thread and invocation counts the recorded run is keyed by + * @param testClass the fully-qualified test class, forming half of the benchmark key + * @param testMethod the test method, forming the other half of the benchmark key */ public BenchmarkRecorder(AsyncTestConfig config, String testClass, String testMethod, int actualThreads) { this.config = config; @@ -86,6 +98,8 @@ public BenchmarkRecorder(AsyncTestConfig config, String testClass, String testMe /** * Check if benchmarking is enabled. + * + * @return {@code true} when benchmarking is switched on for this run */ public boolean isBenchmarkingEnabled() { return benchmarkingEnabled; @@ -209,6 +223,8 @@ private void printComparisonResult(BenchmarkComparisonResult result) { /** * Get the total execution time in nanoseconds. + * + * @return the get total execution time in nanoseconds */ public long getTotalExecutionTimeNanos() { return System.nanoTime() - startTimeNanos; @@ -216,6 +232,8 @@ public long getTotalExecutionTimeNanos() { /** * Get the number of recorded invocations. + * + * @return the number of invocations recorded so far */ public int getInvocationCount() { synchronized (invocationTimesNanos) { diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkRegressionException.java b/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkRegressionException.java index c7de821d..175c3da6 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkRegressionException.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkRegressionException.java @@ -13,13 +13,20 @@ public class BenchmarkRegressionException extends RuntimeException { // Not serialized: BenchmarkComparisonResult does not implement Serializable. // The message string (from super) carries the human-readable detail. private final transient BenchmarkComparisonResult comparisonResult; - + /** + * Creates a BenchmarkRegressionException. + * + * @param message the assertion text shown to whoever the failing build lands on + * @param comparisonResult the measured comparison, retained so a reporter can show both runs + */ public BenchmarkRegressionException(String message, BenchmarkComparisonResult comparisonResult) { super(message); this.comparisonResult = comparisonResult; } - /** {@return the comparison result} */ + /** + * {@return the comparison result} + */ public @Nullable BenchmarkComparisonResult getComparisonResult() { return comparisonResult; } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkResult.java b/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkResult.java index 6bc49a13..e4d0873e 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkResult.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/benchmark/BenchmarkResult.java @@ -42,58 +42,80 @@ private BenchmarkResult(Builder builder) { this.invocationTimesNanos = new ArrayList<>(builder.invocationTimesNanos); } - /** {@return the test class} */ + /** + * {@return the test class} + */ public @Nullable String getTestClass() { return testClass; } - /** {@return the test method} */ + /** + * {@return the test method} + */ public @Nullable String getTestMethod() { return testMethod; } - /** {@return the timestamp} */ + /** + * {@return the timestamp} + */ public LocalDateTime getTimestamp() { return timestamp; } - /** {@return the threads} */ + /** + * {@return the threads} + */ public int getThreads() { return threads; } - /** {@return the invocations} */ + /** + * {@return the invocations} + */ public int getInvocations() { return invocations; } - /** {@return the total execution time in nanoseconds} */ + /** + * {@return the total execution time in nanoseconds} + */ public long getTotalExecutionTimeNanos() { return totalExecutionTimeNanos; } - /** {@return the avg time per invocation in nanoseconds} */ + /** + * {@return the avg time per invocation in nanoseconds} + */ public long getAvgTimePerInvocationNanos() { return avgTimePerInvocationNanos; } - /** {@return the min time per invocation in nanoseconds} */ + /** + * {@return the min time per invocation in nanoseconds} + */ public long getMinTimePerInvocationNanos() { return minTimePerInvocationNanos; } - /** {@return the max time per invocation in nanoseconds} */ + /** + * {@return the max time per invocation in nanoseconds} + */ public long getMaxTimePerInvocationNanos() { return maxTimePerInvocationNanos; } - /** {@return the invocation times in nanoseconds} */ + /** + * {@return the invocation times in nanoseconds} + */ public List getInvocationTimesNanos() { return Collections.unmodifiableList(invocationTimesNanos); } /** * Get a unique key for this benchmark (class + method). + * + * @return the key this result is stored under, {@code testClass#testMethod} */ public String getBenchmarkKey() { return testClass + "#" + testMethod; @@ -101,6 +123,8 @@ public String getBenchmarkKey() { /** * Calculate the standard deviation of invocation times. + * + * @return the standard deviation of the recorded timings, in nanoseconds */ public double getStandardDeviation() { if (invocationTimesNanos.size() <= 1) { @@ -117,6 +141,9 @@ public double getStandardDeviation() { /** * Format time in nanoseconds to a human-readable string. + * + * @param nanos the in nanoseconds + * @return the duration rendered with a unit, for display in a report */ public static String formatTime(long nanos) { if (nanos < 1_000) { @@ -160,8 +187,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(testClass, testMethod, timestamp); } - /** {@return the builder} */ - + /** + * {@return the builder} + */ public static Builder builder() { return new Builder(); } @@ -180,10 +208,9 @@ public static class Builder { /** * Test class. * - * @param testClass the test class - * @return the test class + * @param testClass the fully-qualified test class this measurement came from + * @return this builder */ - public Builder testClass(String testClass) { this.testClass = testClass; return this; @@ -191,10 +218,9 @@ public Builder testClass(String testClass) { /** * Test method. * - * @param testMethod the test method - * @return the test method + * @param testMethod the test method this measurement came from + * @return this builder */ - public Builder testMethod(String testMethod) { this.testMethod = testMethod; return this; @@ -202,10 +228,9 @@ public Builder testMethod(String testMethod) { /** * Timestamp. * - * @param timestamp the timestamp - * @return the timestamp + * @param timestamp when the measurement was taken + * @return this builder */ - public Builder timestamp(LocalDateTime timestamp) { this.timestamp = timestamp; return this; @@ -213,10 +238,9 @@ public Builder timestamp(LocalDateTime timestamp) { /** * Threads. * - * @param threads the threads - * @return the threads + * @param threads the number of threads the measured run used + * @return this builder */ - public Builder threads(int threads) { this.threads = threads; return this; @@ -224,10 +248,9 @@ public Builder threads(int threads) { /** * Invocations. * - * @param invocations the invocations - * @return the invocations + * @param invocations the number of invocations the measured run performed + * @return this builder */ - public Builder invocations(int invocations) { this.invocations = invocations; return this; @@ -238,7 +261,6 @@ public Builder invocations(int invocations) { * @param totalExecutionTimeNanos the total execution time in nanoseconds * @return the total execution time in nanoseconds */ - public Builder totalExecutionTimeNanos(long totalExecutionTimeNanos) { this.totalExecutionTimeNanos = totalExecutionTimeNanos; return this; @@ -249,7 +271,6 @@ public Builder totalExecutionTimeNanos(long totalExecutionTimeNanos) { * @param avgTimePerInvocationNanos the avg time per invocation in nanoseconds * @return the avg time per invocation in nanoseconds */ - public Builder avgTimePerInvocationNanos(long avgTimePerInvocationNanos) { this.avgTimePerInvocationNanos = avgTimePerInvocationNanos; return this; @@ -260,7 +281,6 @@ public Builder avgTimePerInvocationNanos(long avgTimePerInvocationNanos) { * @param minTimePerInvocationNanos the min time per invocation in nanoseconds * @return the min time per invocation in nanoseconds */ - public Builder minTimePerInvocationNanos(long minTimePerInvocationNanos) { this.minTimePerInvocationNanos = minTimePerInvocationNanos; return this; @@ -271,7 +291,6 @@ public Builder minTimePerInvocationNanos(long minTimePerInvocationNanos) { * @param maxTimePerInvocationNanos the max time per invocation in nanoseconds * @return the max time per invocation in nanoseconds */ - public Builder maxTimePerInvocationNanos(long maxTimePerInvocationNanos) { this.maxTimePerInvocationNanos = maxTimePerInvocationNanos; return this; @@ -282,13 +301,13 @@ public Builder maxTimePerInvocationNanos(long maxTimePerInvocationNanos) { * @param invocationTimesNanos the invocation times in nanoseconds * @return the invocation times in nanoseconds */ - public Builder invocationTimesNanos(List invocationTimesNanos) { this.invocationTimesNanos = new ArrayList<>(invocationTimesNanos); return this; } - /** {@return the build} */ - + /** + * {@return the build} + */ public BenchmarkResult build() { return new BenchmarkResult(this); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ABAProblemDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ABAProblemDetector.java index feb731de..6e15fde3 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ABAProblemDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ABAProblemDetector.java @@ -75,6 +75,10 @@ private static class CASAttempt { /** * Record a value change in an atomic variable. + * + * @param variableName a label identifying the variable in the report + * @param oldValue the value present before the write + * @param newValue the value being written */ public void recordValueChange(String variableName, Object oldValue, Object newValue) { if (!enabled) return; @@ -94,6 +98,12 @@ public void recordValueChange(String variableName, Object oldValue, Object newVa /** * Record a CAS (Compare-And-Swap) attempt. + * + * @param variableName a label identifying the variable in the report + * @param expectedValue the value the compare-and-set expected to find + * @param newValue the value being written + * @param succeeded the {@code succeeded} flag + * @param actualCurrentValue the value actually found, when it differed from the expected one */ public void recordCASAttempt(String variableName, Object expectedValue, Object newValue, boolean succeeded, Object actualCurrentValue) { @@ -183,6 +193,8 @@ private boolean detectABA(AtomicValueHistory history, CASAttempt attempt) { /** * Analyze for ABA problems. + * + * @return the findings this detector collected during the run */ public ABAReport analyzeABA() { ABAReport report = new ABAReport(); @@ -209,6 +221,8 @@ public ABAReport analyzeABA() { /** * Standardized alias for {@link #analyzeABA()}. + * + * @return the findings this detector collected during the run */ public ABAReport analyze() { return analyzeABA(); @@ -216,32 +230,31 @@ public ABAReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { trackedVariables.clear(); } /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class ABAReport { - /** The variables with cycles. */ + /** How many A-B-A cycles were observed per variable. */ public final Map variablesWithCycles = new HashMap<>(); - /** The successful ABA cases. */ + /** Compare-and-set calls that succeeded even though the value had changed and changed back. */ public final Set successfulABACases = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !variablesWithCycles.isEmpty() || !successfulABACases.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/AtomicNonAtomicUpdateDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/AtomicNonAtomicUpdateDetector.java index f39ae03f..946fea99 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/AtomicNonAtomicUpdateDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/AtomicNonAtomicUpdateDetector.java @@ -45,7 +45,13 @@ private AtomicState stateFor(Object atomic, String name) { id -> new AtomicState(name != null ? name : "Atomic@" + id)); } - /** Call after {@code atomic.get()}. */ + /** + * Call after {@code atomic.get()}. + * + * @param atomic the atomic being recorded, tracked by identity + * @param name a label identifying the atomic in the report + * @param thread the thread performing the operation + */ public void recordGet(Object atomic, String name, Thread thread) { if (atomic == null || thread == null) return; stateFor(atomic, name).pendingGetByThread.put(thread.threadId(), 1); @@ -55,6 +61,10 @@ public void recordGet(Object atomic, String name, Thread thread) { * Call after {@code atomic.set()} (a non-CAS write). * If the same thread previously called {@link #recordGet} without an intervening CAS, * the sequence is flagged as a lost-update race. + * + * @param atomic the atomic being recorded, tracked by identity + * @param name a label identifying the atomic in the report + * @param thread the thread performing the operation */ public void recordSet(Object atomic, String name, Thread thread) { if (atomic == null || thread == null) return; @@ -68,13 +78,21 @@ public void recordSet(Object atomic, String name, Thread thread) { } } - /** Call after a successful {@code atomic.compareAndSet()} — clears the pending-get flag. */ + /** + * Call after a successful {@code atomic.compareAndSet()} — clears the pending-get flag. + * + * @param atomic the atomic being recorded, tracked by identity + * @param name a label identifying the atomic in the report + * @param thread the thread performing the operation + */ public void recordCas(Object atomic, String name, Thread thread) { if (atomic == null || thread == null) return; stateFor(atomic, name).pendingGetByThread.remove(thread.threadId()); } - /** {@return report of non-atomic compound updates} */ + /** + * {@return report of non-atomic compound updates} + */ public AtomicNonAtomicUpdateReport analyze() { AtomicNonAtomicUpdateReport r = new AtomicNonAtomicUpdateReport(); for (AtomicState s : atomics.values()) { @@ -92,7 +110,9 @@ public static class AtomicNonAtomicUpdateReport { final List violations = new ArrayList<>(); final List details = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/AtomicityValidator.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/AtomicityValidator.java index cab7d11a..8b349e70 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/AtomicityValidator.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/AtomicityValidator.java @@ -50,9 +50,8 @@ private static class FieldAccessRecord { /** * Records compound operation start so it can be analysed at the end of the run. * - * @param operationName the operation name + * @param operationName a label identifying the operation in the report */ - public void recordCompoundOperationStart(String operationName) { if (!enabled || operationName == null || operationName.isBlank()) { return; @@ -64,9 +63,8 @@ public void recordCompoundOperationStart(String operationName) { /** * Records compound operation end so it can be analysed at the end of the run. * - * @param operationName the operation name + * @param operationName a label identifying the operation in the report */ - public void recordCompoundOperationEnd(String operationName) { if (!enabled || operationName == null || operationName.isBlank()) { return; @@ -77,11 +75,10 @@ public void recordCompoundOperationEnd(String operationName) { /** * Records field access so it can be analysed at the end of the run. * - * @param fieldName the field name - * @param value the value - * @param isWrite the is write + * @param fieldName the field involved, as it should appear in the report + * @param value the value read or written + * @param isWrite {@code true} for a write, {@code false} for a read */ - public void recordFieldAccess(String fieldName, @Nullable Object value, boolean isWrite) { recordFieldAccess(fieldName, value, isWrite, Thread.currentThread().threadId()); } @@ -142,13 +139,12 @@ public void recordFieldAccess(String fieldName, @Nullable Object value, boolean /** * Detect check then act violation. * - * @param fieldName the field name - * @param checkValue the check value - * @param expectedValue the expected value - * @param wouldAct the would act - * @return the detect check then act violation + * @param fieldName the field involved, as it should appear in the report + * @param checkValue the value observed by the check + * @param expectedValue the value the caller expected to find + * @param wouldAct {@code true} when the caller would have acted on the checked value + * @return {@code true} when a check-then-act sequence was observed on that field */ - public boolean detectCheckThenActViolation(String fieldName, Object checkValue, Object expectedValue, boolean wouldAct) { if (!enabled || !wouldAct) { @@ -167,9 +163,8 @@ public boolean detectCheckThenActViolation(String fieldName, Object checkValue, /** * Analyses what has been recorded about atomicity and builds the report for it. * - * @return the analyze atomicity + * @return the findings this detector collected during the run */ - public AtomicityReport analyzeAtomicity() { AtomicityReport report = new AtomicityReport(); report.checkThenActViolations.addAll(atomicityViolations); @@ -208,6 +203,8 @@ public AtomicityReport analyzeAtomicity() { /** * Standardized alias for {@link #analyzeAtomicity()}. + * + * @return the findings this detector collected during the run */ public AtomicityReport analyze() { return analyzeAtomicity(); @@ -219,7 +216,6 @@ private String operationKey(String operationName) { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { activeOperations.clear(); fieldHistory.clear(); @@ -228,27 +224,27 @@ public void reset() { /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class AtomicityReport { - /** The check then act violations. */ + /** Fields checked and then acted on without holding a lock across both. */ public final Set checkThenActViolations = new HashSet<>(); - /** The unsafe field accesses. */ + /** Fields with mixed reads and writes from more than one thread. */ public final Set unsafeFieldAccesses = new HashSet<>(); - /** The totcou races. */ + /** Fields whose state changed between the check and the use (TOCTOU). The field name misspells the acronym; it is public API and kept as-is for compatibility. */ public final Set totcouRaces = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !checkThenActViolations.isEmpty() || !unsafeFieldAccesses.isEmpty() diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/AutoFix.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/AutoFix.java index 9c655bf8..6fed9116 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/AutoFix.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/AutoFix.java @@ -13,7 +13,9 @@ private AutoFix() { } // ============= Deadlock Fixes ============= - /** {@return the deadlock fix} */ + /** + * {@return the deadlock fix} + */ public static String getDeadlockFix() { return """ 💡 AUTO-FIX: How to Fix This Deadlock @@ -61,7 +63,9 @@ public static String getDeadlockFix() { // ============= Race Condition Fixes ============= - /** {@return the race condition fix} */ + /** + * {@return the race condition fix} + */ public static String getRaceConditionFix() { return """ 💡 AUTO-FIX: How to Fix This Race Condition @@ -91,7 +95,7 @@ public static String getRaceConditionFix() { /** * Deposit. * - * @param amount the amount + * @param amount how many, for the operation being recorded */ // Before: public void deposit(long amount) { @@ -100,7 +104,7 @@ public void deposit(long amount) { /** * Deposit. * - * @param amount the amount + * @param amount how many, for the operation being recorded */ // After: @@ -116,9 +120,8 @@ public synchronized void deposit(long amount) { /** * Deposit. * - * @param amount the amount + * @param amount how many, for the operation being recorded */ - public void deposit(long amount) { lock.lock(); try { @@ -132,7 +135,9 @@ public void deposit(long amount) { // ============= Visibility Fixes ============= - /** {@return the visibility fix} */ + /** + * {@return the visibility fix} + */ public static String getVisibilityFix() { return """ 💡 AUTO-FIX: How to Fix This Visibility Issue @@ -165,12 +170,12 @@ public static String getVisibilityFix() { /** * Set ready. */ - public synchronized void setReady() { ready = true; } - /** {@return the is ready} */ - + /** + * {@return the is ready} + */ public synchronized boolean isReady() { return ready; } @@ -179,7 +184,9 @@ public synchronized boolean isReady() { // ============= False Sharing Fixes ============= - /** {@return the false sharing fix} */ + /** + * {@return the false sharing fix} + */ public static String getFalseSharingFix() { return """ 💡 AUTO-FIX: How to Fix False Sharing @@ -227,7 +234,9 @@ private static class ColdFields { // ============= CompletableFuture Leak Fixes ============= - /** {@return the completable future leak fix} */ + /** + * {@return the completable future leak fix} + */ public static String getCompletableFutureLeakFix() { return """ 💡 AUTO-FIX: How to Fix CompletableFuture Completion Leak @@ -272,7 +281,9 @@ public static String getCompletableFutureLeakFix() { // ============= Virtual Thread Pinning Fixes ============= - /** {@return the virtual thread pinning fix} */ + /** + * {@return the virtual thread pinning fix} + */ public static String getVirtualThreadPinningFix() { return """ 💡 AUTO-FIX: How to Fix Virtual Thread Pinning @@ -317,7 +328,9 @@ public static String getVirtualThreadPinningFix() { // ============= Thread Pool Deadlock Fixes ============= - /** {@return the thread pool deadlock fix} */ + /** + * {@return the thread pool deadlock fix} + */ public static String getThreadPoolDeadlockFix() { return """ 💡 AUTO-FIX: How to Fix Thread Pool Deadlock @@ -365,7 +378,9 @@ public static String getThreadPoolDeadlockFix() { // ============= Busy Waiting Fixes ============= - /** {@return the busy waiting fix} */ + /** + * {@return the busy waiting fix} + */ public static String getBusyWaitingFix() { return """ 💡 AUTO-FIX: How to Fix Busy Waiting @@ -415,7 +430,9 @@ public static String getBusyWaitingFix() { // ============= Atomicity Violation Fixes ============= - /** {@return the atomicity violation fix} */ + /** + * {@return the atomicity violation fix} + */ public static String getAtomicityViolationFix() { return """ 💡 AUTO-FIX: How to Fix Atomicity Violation @@ -444,7 +461,6 @@ public static String getAtomicityViolationFix() { /** * Increment. */ - public synchronized void increment() { counter++; // Now atomic } @@ -453,7 +469,9 @@ public synchronized void increment() { // ============= Lock Leak Fixes ============= - /** {@return the lock leak fix} */ + /** + * {@return the lock leak fix} + */ public static String getLockLeakFix() { return """ 💡 AUTO-FIX: How to Fix Lock Leak diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/BlockingQueueDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/BlockingQueueDetector.java index 281e5be9..ef71a702 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/BlockingQueueDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/BlockingQueueDetector.java @@ -83,7 +83,7 @@ public void registerQueue(BlockingQueue queue, String name, int capacity) { /** * Record an offer() call. * - * @param queue the queue + * @param queue the queue being recorded, tracked by identity * @param name the queue name (should match registration) * @param success true if offer succeeded, false if queue was full */ @@ -107,7 +107,7 @@ public void recordOffer(BlockingQueue queue, String name, boolean success) { /** * Record a poll() call. * - * @param queue the queue + * @param queue the queue being recorded, tracked by identity * @param name the queue name (should match registration) * @param success true if poll returned an element, false if queue was empty */ @@ -131,7 +131,7 @@ public void recordPoll(BlockingQueue queue, String name, boolean success) { /** * Record a put() call (blocking insert). * - * @param queue the queue + * @param queue the queue being recorded, tracked by identity * @param name the queue name (should match registration) */ public void recordPut(BlockingQueue queue, String name) { @@ -149,7 +149,7 @@ public void recordPut(BlockingQueue queue, String name) { /** * Record a take() call (blocking retrieval). * - * @param queue the queue + * @param queue the queue being recorded, tracked by identity * @param name the queue name (should match registration) */ public void recordTake(BlockingQueue queue, String name) { @@ -241,6 +241,8 @@ public static class BlockingQueueReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !silentFailures.isEmpty() || !emptyPolls.isEmpty() || diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/BoxedPrimitiveLockDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/BoxedPrimitiveLockDetector.java index 18ca1484..8de22195 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/BoxedPrimitiveLockDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/BoxedPrimitiveLockDetector.java @@ -171,7 +171,9 @@ public void recordLockAcquire(Object lockObject, Thread thread, String location) return null; } - /** {@return report of synchronizations on cached boxed primitives or value-based classes} */ + /** + * {@return report of synchronizations on cached boxed primitives or value-based classes} + */ public BoxedPrimitiveLockReport analyze() { BoxedPrimitiveLockReport r = new BoxedPrimitiveLockReport(); for (LockEvent e : events) { @@ -196,7 +198,9 @@ public static class BoxedPrimitiveLockReport { final List violations = new ArrayList<>(); private boolean hasValueBasedIssues; - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/BusyWaitDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/BusyWaitDetector.java index 6fe8fedc..60a24350 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/BusyWaitDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/BusyWaitDetector.java @@ -42,7 +42,6 @@ private static class SpinEvent { /** * Records loop iteration so it can be analysed at the end of the run. */ - public void recordLoopIteration() { if (!enabled) { return; @@ -64,7 +63,6 @@ public void recordLoopIteration() { /** * Records yield so it can be analysed at the end of the run. */ - public void recordYield() { if (!enabled) { return; @@ -92,10 +90,9 @@ public void recordYield() { /** * Report spin loop. * - * @param description the description - * @param iterations the iterations + * @param description free text describing the event, shown in the report + * @param iterations how many iterations the spin ran for */ - public void reportSpinLoop(String description, long iterations) { if (!enabled) { return; @@ -118,9 +115,8 @@ private String inferCallSite() { /** * Analyses what has been recorded about busy waiting and builds the report for it. * - * @return the analyze busy waiting + * @return the findings this detector collected during the run */ - public BusyWaitReport analyzeBusyWaiting() { BusyWaitReport report = new BusyWaitReport(); @@ -173,6 +169,8 @@ private static void addToReport(BusyWaitReport report, long threadId, SpinEvent /** * Standardized alias for {@link #analyzeBusyWaiting()}. + * + * @return the findings this detector collected during the run */ public BusyWaitReport analyze() { return analyzeBusyWaiting(); @@ -180,34 +178,33 @@ public BusyWaitReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { threadActivities.clear(); } /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class BusyWaitReport { - /** The busy wait loops. */ + /** Loops that spun waiting for a condition instead of blocking. */ public final Set busyWaitLoops = new HashSet<>(); - /** The tight loops. */ + /** Loops that spun with no back-off at all. */ public final Set tightLoops = new HashSet<>(); - /** The cpu wasted. */ + /** Nanoseconds of CPU time spent spinning rather than blocking. */ public long cpuWasted; - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !busyWaitLoops.isEmpty() || !tightLoops.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CacheConcurrencyDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CacheConcurrencyDetector.java index f4571cd1..587a694f 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CacheConcurrencyDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CacheConcurrencyDetector.java @@ -222,6 +222,8 @@ public static class CacheConcurrencyReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !concurrentReadWrite.isEmpty() || diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CalendarDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CalendarDetector.java index e5752ea5..8ed7f781 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CalendarDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CalendarDetector.java @@ -139,6 +139,8 @@ private void recordAccess(Calendar calendar, String name, String method) { /** * Analyse Calendar usage and return a report. + * + * @return the findings this detector collected during the run */ public CalendarReport analyze() { CalendarReport report = new CalendarReport(); @@ -190,7 +192,11 @@ public static class CalendarReport { final java.util.List calendarErrors = new java.util.ArrayList<>(); final Map calendarActivity = new ConcurrentHashMap<>(); - /** Returns {@code true} when shared-access or errors were detected. */ + /** + * Returns {@code true} when shared-access or errors were detected. + * + * @return {@code true} when this detector recorded something worth reporting + */ public boolean hasIssues() { return !sharedCalendars.isEmpty() || !calendarErrors.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureBlockingCallbackDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureBlockingCallbackDetector.java index e1e7c82e..02009fb0 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureBlockingCallbackDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureBlockingCallbackDetector.java @@ -37,6 +37,9 @@ private static final class State { /** * Record entry into a CompletableFuture callback. + * + * @param callbackName a label identifying the callback in the report + * @param thread the thread performing the operation */ public void recordEnterCallback(String callbackName, Thread thread) { if (thread == null) return; @@ -45,6 +48,8 @@ public void recordEnterCallback(String callbackName, Thread thread) { /** * Record exit from a CompletableFuture callback. + * + * @param thread the thread performing the operation */ public void recordExitCallback(Thread thread) { if (thread == null) return; @@ -53,6 +58,9 @@ public void recordExitCallback(Thread thread) { /** * Record a blocking call executed on a thread. + * + * @param thread the thread performing the operation + * @param blockingApiName the blocking API that was called, as it should appear in the report */ public void recordBlockingCall(Thread thread, String blockingApiName) { if (thread == null) return; @@ -65,9 +73,8 @@ public void recordBlockingCall(Thread thread, String blockingApiName) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : violations.values()) { @@ -92,12 +99,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureChainDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureChainDetector.java index 8d35e29c..05a6f842 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureChainDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureChainDetector.java @@ -243,6 +243,8 @@ public static class CompletableFutureChainReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !unjoinedFutures.isEmpty() || diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureCommonPoolBlockingDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureCommonPoolBlockingDetector.java index c1dc2385..865c9c89 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureCommonPoolBlockingDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureCommonPoolBlockingDetector.java @@ -70,7 +70,9 @@ public void recordBlockingCall(Object future, Thread thread, String callType) { thread.getName(), type, name)); } - /** {@return report of blocking calls in common-pool futures} */ + /** + * {@return report of blocking calls in common-pool futures} + */ public CompletableFutureCommonPoolBlockingReport analyze() { CompletableFutureCommonPoolBlockingReport r = new CompletableFutureCommonPoolBlockingReport(); r.violations.addAll(violations); @@ -81,7 +83,9 @@ public CompletableFutureCommonPoolBlockingReport analyze() { public static class CompletableFutureCommonPoolBlockingReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureExceptionDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureExceptionDetector.java index 4eac0fa1..1271c9a7 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureExceptionDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureExceptionDetector.java @@ -189,6 +189,8 @@ public static class CompletableFutureExceptionReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !unhandledExceptions.isEmpty() || !missingHandlers.isEmpty() || !swallowedExceptions.isEmpty(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureObtrudeDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureObtrudeDetector.java index 29e59bce..f231d658 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureObtrudeDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CompletableFutureObtrudeDetector.java @@ -39,6 +39,10 @@ private static final class State { /** * Record an obtrude action on a CompletableFuture. + * + * @param future the future being recorded, tracked by identity + * @param label a label identifying it in the report + * @param thread the thread performing the operation */ public void recordObtrude(CompletableFuture future, String label, Thread thread) { if (future == null || thread == null) return; @@ -51,9 +55,8 @@ public void recordObtrude(CompletableFuture future, String label, Thread thre /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : obtrudes.values()) { @@ -79,12 +82,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConcurrentMapComputeRecursionDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConcurrentMapComputeRecursionDetector.java index 991fb36a..9f2b03b2 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConcurrentMapComputeRecursionDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConcurrentMapComputeRecursionDetector.java @@ -61,13 +61,21 @@ public void recordComputeStart(Map map, Object key, Thread thread, String } } - /** Record exit from a {@code compute*} / {@code merge} mapping function. */ + /** + * Record exit from a {@code compute*} / {@code merge} mapping function. + * + * @param map the map the computation was running on, tracked by identity + * @param key the key the entry is stored under + * @param thread the thread performing the operation + */ public void recordComputeEnd(Map map, Object key, Thread thread) { if (map == null || key == null || thread == null) return; activeComputes.remove(slot(map, key, thread)); } - /** {@return report of recursive compute calls} */ + /** + * {@return report of recursive compute calls} + */ public ConcurrentMapComputeRecursionReport analyze() { ConcurrentMapComputeRecursionReport r = new ConcurrentMapComputeRecursionReport(); r.recursions.addAll(recursions); @@ -78,7 +86,9 @@ public ConcurrentMapComputeRecursionReport analyze() { public static class ConcurrentMapComputeRecursionReport { final List recursions = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !recursions.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConcurrentModificationDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConcurrentModificationDetector.java index 4611b3e0..f8ee0369 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConcurrentModificationDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConcurrentModificationDetector.java @@ -206,6 +206,8 @@ public static class ConcurrentModificationReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !concurrentModifications.isEmpty() || !concurrentIterations.isEmpty() || !concurrentMutations.isEmpty(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConditionVariableDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConditionVariableDetector.java index 5392300f..f83c3479 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConditionVariableDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConditionVariableDetector.java @@ -80,7 +80,7 @@ public void registerCondition(Condition condition, String name) { /** * Record an await() call. * - * @param condition the Condition + * @param condition the condition being awaited or signalled, tracked by identity * @param name the condition name (should match registration) */ public void recordAwait(Condition condition, String name) { @@ -98,7 +98,7 @@ public void recordAwait(Condition condition, String name) { /** * Record an await() exit (normal or timeout). * - * @param condition the Condition + * @param condition the condition being awaited or signalled, tracked by identity * @param name the condition name (should match registration) * @param timedOut true if await timed out, false if signaled */ @@ -116,7 +116,7 @@ public void recordAwaitExit(Condition condition, String name, boolean timedOut) /** * Record a signal() call. * - * @param condition the Condition + * @param condition the condition being awaited or signalled, tracked by identity * @param name the condition name (should match registration) * @param isSignalAll true if signalAll(), false if signal() */ @@ -201,6 +201,8 @@ public static class ConditionVariableReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !lostSignals.isEmpty() || !stuckWaiters.isEmpty() || !missingSignals.isEmpty(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConstructorSafetyValidator.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConstructorSafetyValidator.java index 307a2034..2701b0f0 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConstructorSafetyValidator.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ConstructorSafetyValidator.java @@ -51,6 +51,8 @@ private static final class FieldAccessInfo { /** * Mark the start of object construction. + * + * @param object the object the access is on, tracked by identity */ public void recordConstructionStart(Object object) { if (!enabled || object == null) return; @@ -62,6 +64,8 @@ public void recordConstructionStart(Object object) { /** * Mark the end of object construction. + * + * @param object the object the access is on, tracked by identity */ public void recordConstructionEnd(Object object) { if (!enabled) return; @@ -76,6 +80,10 @@ public void recordConstructionEnd(Object object) { /** * Record a field access to a partially constructed object. + * + * @param object the object the access is on, tracked by identity + * @param fieldName the field involved, as it should appear in the report + * @param timestamp when the event happened, in nanoseconds */ public void recordFieldAccess(Object object, String fieldName, long timestamp) { if (!enabled) return; @@ -107,6 +115,8 @@ public void recordFieldAccess(Object object, String fieldName, long timestamp) { /** * Validate constructor safety. + * + * @return the findings this detector collected during the run */ public ConstructorSafetyReport validateConstructorSafety() { ConstructorSafetyReport report = new ConstructorSafetyReport(); @@ -157,34 +167,33 @@ public ConstructorSafetyReport validateConstructorSafety() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { objects.clear(); } /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class ConstructorSafetyReport { - /** The unsafe objects. */ + /** Objects whose reference escaped their constructor. */ public final Set unsafeObjects = new HashSet<>(); - /** The possibly incomplete constructions. */ + /** Objects published before construction finished. */ public final Set possiblyIncompleteConstructions = new HashSet<>(); - /** The fields accessed during construction. */ + /** Fields read by another thread before the constructor returned. */ public final Set fieldsAccessedDuringConstruction = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !unsafeObjects.isEmpty() || !fieldsAccessedDuringConstruction.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CopyOnWriteCollectionDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CopyOnWriteCollectionDetector.java index 57788d8b..a1909b9d 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CopyOnWriteCollectionDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CopyOnWriteCollectionDetector.java @@ -117,6 +117,8 @@ private CoWState resolve(Object collection, String name) { /** * Analyse Copy-on-Write collection usage and return a report. + * + * @return the findings this detector collected during the run */ public CopyOnWriteReport analyze() { CopyOnWriteReport report = new CopyOnWriteReport(); @@ -158,7 +160,11 @@ public static class CopyOnWriteReport { final java.util.List writeHeavyViolations = new java.util.ArrayList<>(); final Map collectionActivity = new ConcurrentHashMap<>(); - /** Returns {@code true} when any write-heavy violations were detected. */ + /** + * Returns {@code true} when any write-heavy violations were detected. + * + * @return {@code true} when this detector recorded something worth reporting + */ public boolean hasIssues() { return !writeHeavyViolations.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CountDownLatchDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CountDownLatchDetector.java index 99dc896d..80637816 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CountDownLatchDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CountDownLatchDetector.java @@ -23,6 +23,10 @@ public class CountDownLatchDetector { /** * Register a CountDownLatch for monitoring. + * + * @param latch the latch being recorded, tracked by identity + * @param name a label identifying the latch in the report + * @param initialCount the count the latch was created with */ public void registerLatch(CountDownLatch latch, String name, int initialCount) { latchRegistry.put(latch, new LatchInfo(name, initialCount)); @@ -30,6 +34,8 @@ public void registerLatch(CountDownLatch latch, String name, int initialCount) { /** * Record a countDown() call. + * + * @param latch the latch being recorded, tracked by identity */ public void recordCountDown(CountDownLatch latch) { LatchInfo info = latchRegistry.get(latch); @@ -43,6 +49,8 @@ public void recordCountDown(CountDownLatch latch) { /** * Record an await() call that timed out. + * + * @param latch the latch being recorded, tracked by identity */ public void recordTimeout(CountDownLatch latch) { timedOutLatches.add(latch); @@ -50,6 +58,8 @@ public void recordTimeout(CountDownLatch latch) { /** * Record a successful await() call. + * + * @param latch the latch being recorded, tracked by identity */ public void recordAwaitSuccess(CountDownLatch latch) { LatchInfo info = latchRegistry.get(latch); @@ -60,6 +70,8 @@ public void recordAwaitSuccess(CountDownLatch latch) { /** * Analyze latch usage and return report. + * + * @return the findings this detector collected during the run */ public CountDownLatchReport analyze() { return new CountDownLatchReport( @@ -76,7 +88,13 @@ public static class CountDownLatchReport { private final Map latchRegistry; private final Set timedOutLatches; private final Set extraCountDownLatches; - + /** + * Creates a CountDownLatchReport. + * + * @param latchRegistry every registered latch and what was observed on it + * @param timedOutLatches the latches whose await timed out + * @param extraCountDownLatches the latches counted down more times than they were created for + */ public CountDownLatchReport( Map latchRegistry, Set timedOutLatches, @@ -87,7 +105,9 @@ public CountDownLatchReport( this.extraCountDownLatches = Collections.unmodifiableSet(new HashSet<>(extraCountDownLatches)); } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !timedOutLatches.isEmpty() || !extraCountDownLatches.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CyclicBarrierDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CyclicBarrierDetector.java index c6f762c4..a1610346 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CyclicBarrierDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/CyclicBarrierDetector.java @@ -24,6 +24,10 @@ public class CyclicBarrierDetector { /** * Register a CyclicBarrier for monitoring. + * + * @param barrier the barrier being recorded, tracked by identity + * @param name a label identifying the barrier in the report + * @param parties the number of parties the barrier was created for */ public void registerBarrier(CyclicBarrier barrier, String name, int parties) { barrierRegistry.put(barrier, new BarrierInfo(name, parties)); @@ -31,6 +35,8 @@ public void registerBarrier(CyclicBarrier barrier, String name, int parties) { /** * Record a thread arriving at the barrier. + * + * @param barrier the barrier being recorded, tracked by identity */ public void recordArrival(CyclicBarrier barrier) { BarrierInfo info = barrierRegistry.get(barrier); @@ -41,6 +47,8 @@ public void recordArrival(CyclicBarrier barrier) { /** * Record a barrier await() that timed out. + * + * @param barrier the barrier being recorded, tracked by identity */ public void recordTimeout(CyclicBarrier barrier) { timedOutBarriers.add(barrier); @@ -48,6 +56,8 @@ public void recordTimeout(CyclicBarrier barrier) { /** * Record a barrier that was broken. + * + * @param barrier the barrier being recorded, tracked by identity */ public void recordBroken(CyclicBarrier barrier) { brokenBarriers.add(barrier); @@ -56,6 +66,8 @@ public void recordBroken(CyclicBarrier barrier) { /** * Record a barrier that was reset, repairing it after it broke. * Subsequent await() calls are no longer considered reuse-after-broken. + * + * @param barrier the barrier being recorded, tracked by identity */ public void recordReset(CyclicBarrier barrier) { brokenBarriers.remove(barrier); @@ -65,6 +77,8 @@ public void recordReset(CyclicBarrier barrier) { * Record a thread calling await() on the barrier. If the barrier is * currently broken and has not been reset since, this is flagged as * reuse of a broken barrier without an intervening reset(). + * + * @param barrier the barrier being recorded, tracked by identity */ public void recordAwait(CyclicBarrier barrier) { if (brokenBarriers.contains(barrier)) { @@ -74,6 +88,8 @@ public void recordAwait(CyclicBarrier barrier) { /** * Record successful barrier completion. + * + * @param barrier the barrier being recorded, tracked by identity */ public void recordBarrierComplete(CyclicBarrier barrier) { BarrierInfo info = barrierRegistry.get(barrier); @@ -84,6 +100,8 @@ public void recordBarrierComplete(CyclicBarrier barrier) { /** * Analyze barrier usage and return report. + * + * @return the findings this detector collected during the run */ public CyclicBarrierReport analyze() { return new CyclicBarrierReport( @@ -102,7 +120,14 @@ public static class CyclicBarrierReport { private final Set timedOutBarriers; private final Set brokenBarriers; private final Set reuseAfterBrokenBarriers; - + /** + * Creates a CyclicBarrierReport. + * + * @param barrierRegistry every registered barrier and what was observed on it + * @param timedOutBarriers the barriers whose await timed out + * @param brokenBarriers the barriers left in a broken state + * @param reuseAfterBrokenBarriers the barriers used again after they had broken + */ public CyclicBarrierReport( Map barrierRegistry, Set timedOutBarriers, @@ -121,6 +146,10 @@ public CyclicBarrierReport( * * @deprecated since 1.7.0 — use the four-argument constructor; this overload * reports no reuse-after-broken barriers. + * + * @param barrierRegistry every registered barrier and what was observed on it + * @param timedOutBarriers the barriers whose await timed out + * @param brokenBarriers the barriers left in a broken state */ @Deprecated(since = "1.7.0") public CyclicBarrierReport( @@ -131,12 +160,16 @@ public CyclicBarrierReport( this(barrierRegistry, timedOutBarriers, brokenBarriers, Collections.emptySet()); } - /** {@return the reuse after broken barriers} */ + /** + * {@return the reuse after broken barriers} + */ public Set getReuseAfterBrokenBarriers() { return reuseAfterBrokenBarriers; } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !timedOutBarriers.isEmpty() || !brokenBarriers.isEmpty() || !reuseAfterBrokenBarriers.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DaemonThreadHygieneDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DaemonThreadHygieneDetector.java index 0cddfbe0..7815027f 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DaemonThreadHygieneDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DaemonThreadHygieneDetector.java @@ -99,6 +99,8 @@ public void recordThread(Thread thread, String label) { * Analyze: a thread is flagged when it (1) was not marked daemon at * registration time, and (2) is still alive (or never started) at analysis * time — i.e. has not cleanly terminated. + * + * @return the findings this detector collected during the run */ public Report analyze() { Report r = new Report(); @@ -166,14 +168,16 @@ public Report analyze() { /** Report produced by {@link #analyze()}. */ public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The flagged. */ + /** Threads whose daemon status does not match what the run expects. */ public final Set flagged = new LinkedHashSet<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DeadlockDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DeadlockDetector.java index e376626d..93ac90c8 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DeadlockDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DeadlockDetector.java @@ -27,7 +27,9 @@ public class DeadlockDetector { * remain valid exclusion keys for the detector's lifetime. */ private final Set preexistingDeadlockedThreads; - + /** + * Creates a DeadlockDetector. + */ public DeadlockDetector() { long[] existing = ManagementFactory.getThreadMXBean().findDeadlockedThreads(); if (existing == null || existing.length == 0) { @@ -43,7 +45,6 @@ public DeadlockDetector() { /** * Prints thread dump to the report output. */ - public static void printThreadDump() { ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); @@ -197,6 +198,8 @@ private static void printLockChain(ThreadInfo thread, Map thre * *

This is a JVM-wide snapshot: unlike {@link #analyze()}, it also reports deadlocks * that predate any particular detector instance. + * + * @return {@code true} when a lock-order cycle was observed */ public static boolean hasDeadlock() { ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); @@ -206,6 +209,8 @@ public static boolean hasDeadlock() { /** * Get a summary of current lock contention. + * + * @return a human-readable summary of lock contention, for the report */ @SuppressWarnings("PMD.AssignmentInOperand") // counter++ in switch arrow case is idiomatic public static String getLockContentionSummary() { @@ -243,6 +248,8 @@ public static void printLearningAndFix() { * *

Deadlocks that already existed when this detector was constructed are excluded, * so a report with issues always means the monitored test introduced a new deadlock. + * + * @return the findings this detector collected during the run */ public DeadlockReport analyze() { long[] current = ManagementFactory.getThreadMXBean().findDeadlockedThreads(); @@ -259,12 +266,18 @@ public DeadlockReport analyze() { public static class DeadlockReport { private final boolean deadlocked; - + /** + * Creates a DeadlockReport. + * + * @param deadlocked the {@code deadlocked} flag + */ public DeadlockReport(boolean deadlocked) { this.deadlocked = deadlocked; } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return deadlocked; } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DeprecatedThreadApiDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DeprecatedThreadApiDetector.java index 43ec47f9..b007c766 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DeprecatedThreadApiDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DeprecatedThreadApiDetector.java @@ -64,7 +64,9 @@ public void recordApiUse(String apiName, Thread thread) { events.add(new ApiUseEvent(apiName, thread.getName())); } - /** {@return report of deprecated Thread API usages} */ + /** + * {@return report of deprecated Thread API usages} + */ public DeprecatedThreadApiReport analyze() { DeprecatedThreadApiReport r = new DeprecatedThreadApiReport(); for (ApiUseEvent e : events) { @@ -80,7 +82,9 @@ public DeprecatedThreadApiReport analyze() { public static class DeprecatedThreadApiReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DoubleCheckedLockingDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DoubleCheckedLockingDetector.java index a07ae867..6e9744bf 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DoubleCheckedLockingDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/DoubleCheckedLockingDetector.java @@ -31,6 +31,12 @@ public class DoubleCheckedLockingDetector { /** * Register a double-checked locking pattern for monitoring. + * + * @param fieldName the field involved, as it should appear in the report + * @param isVolatile the {@code isVolatile} flag + * @param hasFirstCheck the {@code hasFirstCheck} flag + * @param hasSecondCheck the {@code hasSecondCheck} flag + * @param insideSynchronized the {@code insideSynchronized} flag */ public void registerDCL(String fieldName, boolean isVolatile, boolean hasFirstCheck, boolean hasSecondCheck, boolean insideSynchronized) { @@ -46,6 +52,10 @@ public void registerDCL(String fieldName, boolean isVolatile, boolean hasFirstCh /** * Record access to a field that might use DCL. + * + * @param fieldName the field involved, as it should appear in the report + * @param isRead the {@code isRead} flag + * @param isWrite the {@code isWrite} flag */ public void recordAccess(String fieldName, boolean isRead, boolean isWrite) { DCLInfo info = dclRegistry.get(fieldName); @@ -57,6 +67,8 @@ public void recordAccess(String fieldName, boolean isRead, boolean isWrite) { /** * Analyze DCL patterns and return report. + * + * @return the findings this detector collected during the run */ public DoubleCheckedLockingReport analyze() { return new DoubleCheckedLockingReport(dclRegistry, brokenDCLs); @@ -68,7 +80,12 @@ public DoubleCheckedLockingReport analyze() { public static class DoubleCheckedLockingReport { private final Map dclRegistry; private final Set brokenDCLs; - + /** + * Creates a DoubleCheckedLockingReport. + * + * @param dclRegistry every registered double-checked-locking site and what was observed on it + * @param brokenDCLs the double-checked-locking sites whose guard was not safe + */ public DoubleCheckedLockingReport( Map dclRegistry, Set brokenDCLs @@ -77,7 +94,9 @@ public DoubleCheckedLockingReport( this.brokenDCLs = Collections.unmodifiableSet(new HashSet<>(brokenDCLs)); } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !brokenDCLs.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExchangerDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExchangerDetector.java index 6fcb8bbf..fa571910 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExchangerDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExchangerDetector.java @@ -24,6 +24,9 @@ public class ExchangerDetector { /** * Register an Exchanger for monitoring. + * + * @param exchanger the exchanger being recorded, tracked by identity + * @param name a label identifying the exchanger in the report */ public void registerExchanger(Exchanger exchanger, String name) { exchangerRegistry.put(exchanger, new ExchangerInfo(name)); @@ -31,6 +34,9 @@ public void registerExchanger(Exchanger exchanger, String name) { /** * Record a thread starting an exchange. + * + * @param exchanger the exchanger being recorded, tracked by identity + * @param exchangerName a label identifying the exchanger in the report */ public void recordExchangeStart(Exchanger exchanger, String exchangerName) { ExchangerInfo info = exchangerRegistry.get(exchanger); @@ -41,6 +47,10 @@ public void recordExchangeStart(Exchanger exchanger, String exchangerName) { /** * Record a successful exchange completion. + * + * @param exchanger the exchanger being recorded, tracked by identity + * @param exchangerName a label identifying the exchanger in the report + * @param value the value handed to the partner in the exchange */ public void recordExchangeComplete(Exchanger exchanger, String exchangerName, Object value) { ExchangerInfo info = exchangerRegistry.get(exchanger); @@ -54,6 +64,8 @@ public void recordExchangeComplete(Exchanger exchanger, String exchangerName, /** * Record an exchange that timed out. + * + * @param exchanger the exchanger being recorded, tracked by identity */ public void recordTimeout(Exchanger exchanger) { timedOutExchangers.add(exchanger); @@ -61,6 +73,8 @@ public void recordTimeout(Exchanger exchanger) { /** * Record an exchange that was interrupted. + * + * @param exchanger the exchanger being recorded, tracked by identity */ public void recordInterrupted(Exchanger exchanger) { interruptedExchangers.add(exchanger); @@ -68,6 +82,8 @@ public void recordInterrupted(Exchanger exchanger) { /** * Analyze Exchanger usage and return report. + * + * @return the findings this detector collected during the run */ public ExchangerReport analyze() { return new ExchangerReport( @@ -86,7 +102,14 @@ public static class ExchangerReport { private final Set> timedOutExchangers; private final Set> interruptedExchangers; private final int nullValueExchanges; - + /** + * Creates a ExchangerReport. + * + * @param exchangerRegistry every registered exchanger and what was observed on it + * @param timedOutExchangers the exchangers whose exchange timed out + * @param interruptedExchangers the exchangers whose exchange was interrupted + * @param nullValueExchanges the exchanges that transferred {@code null} + */ public ExchangerReport( Map, ExchangerInfo> exchangerRegistry, Set> timedOutExchangers, @@ -99,7 +122,9 @@ public ExchangerReport( this.nullValueExchanges = nullValueExchanges; } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !timedOutExchangers.isEmpty() || !interruptedExchangers.isEmpty() diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExecutorDeadlockDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExecutorDeadlockDetector.java index 4e2dc2a1..557597ec 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExecutorDeadlockDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExecutorDeadlockDetector.java @@ -33,11 +33,10 @@ private static class ExecutorState { /** * Registers executor for tracking. * - * @param executor the executor - * @param name the name - * @param maxThreads the max threads + * @param executor the executor being recorded, tracked by identity + * @param name a label identifying the executor in the report + * @param maxThreads the configured maximum thread count */ - public void registerExecutor(Object executor, String name, int maxThreads) { if (executor == null) { return; @@ -48,9 +47,8 @@ public void registerExecutor(Object executor, String name, int maxThreads) { /** * Records task submitted so it can be analysed at the end of the run. * - * @param executor the executor + * @param executor the executor being recorded, tracked by identity */ - public void recordTaskSubmitted(Object executor) { ExecutorState state = stateFor(executor); if (state != null) { @@ -60,9 +58,8 @@ public void recordTaskSubmitted(Object executor) { /** * Records task started so it can be analysed at the end of the run. * - * @param executor the executor + * @param executor the executor being recorded, tracked by identity */ - public void recordTaskStarted(Object executor) { ExecutorState state = stateFor(executor); if (state != null) { @@ -72,9 +69,8 @@ public void recordTaskStarted(Object executor) { /** * Records waiting on sibling so it can be analysed at the end of the run. * - * @param executor the executor + * @param executor the executor being recorded, tracked by identity */ - public void recordWaitingOnSibling(Object executor) { ExecutorState state = stateFor(executor); if (state != null) { @@ -84,9 +80,8 @@ public void recordWaitingOnSibling(Object executor) { /** * Records task completed so it can be analysed at the end of the run. * - * @param executor the executor + * @param executor the executor being recorded, tracked by identity */ - public void recordTaskCompleted(Object executor) { ExecutorState state = stateFor(executor); if (state != null) { @@ -100,9 +95,8 @@ public void recordTaskCompleted(Object executor) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public ExecutorDeadlockReport analyze() { ExecutorDeadlockReport report = new ExecutorDeadlockReport(); @@ -122,10 +116,12 @@ public ExecutorDeadlockReport analyze() { } public static class ExecutorDeadlockReport { - /** The self deadlocks. */ + /** Tasks that blocked waiting for another task on the same single-threaded executor. */ public final Set selfDeadlocks = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !selfDeadlocks.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExecutorShutdownDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExecutorShutdownDetector.java index 92f91242..a3e463b0 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExecutorShutdownDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExecutorShutdownDetector.java @@ -126,7 +126,9 @@ public static class ExecutorShutdownReport { final List notShutDown = new ArrayList<>(); final List noAwaitTermination = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !notShutDown.isEmpty() || !noAwaitTermination.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExplicitGcDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExplicitGcDetector.java index 6752f8ee..c8d88bea 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExplicitGcDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ExplicitGcDetector.java @@ -50,7 +50,9 @@ public void recordGcInvocation(Thread thread, String location) { location != null ? location : "unknown")); } - /** {@return report of explicit GC invocations} */ + /** + * {@return report of explicit GC invocations} + */ public ExplicitGcReport analyze() { ExplicitGcReport r = new ExplicitGcReport(); for (GcEvent e : events) { @@ -67,7 +69,9 @@ public ExplicitGcReport analyze() { public static class ExplicitGcReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FalseSharingDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FalseSharingDetector.java index 2520372c..a46f5178 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FalseSharingDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FalseSharingDetector.java @@ -49,6 +49,10 @@ private static class AccessEvent { /** * Record a field access. Call this when a field is accessed in your test. + * + * @param object the object the access is on, tracked by identity + * @param fieldName the field involved, as it should appear in the report + * @param fieldType the declared type of the field */ public void recordFieldAccess(Object object, String fieldName, Class fieldType) { if (!enabled || object == null) return; @@ -72,6 +76,8 @@ public void recordFieldAccess(Object object, String fieldName, Class fieldTyp /** * Analyze for false sharing patterns. + * + * @return the findings this detector collected during the run */ public FalseSharingReport analyzeFalseSharing() { FalseSharingReport report = new FalseSharingReport(); @@ -115,6 +121,8 @@ public FalseSharingReport analyzeFalseSharing() { /** * Standardized alias for {@link #analyzeFalseSharing()}. + * + * @return the findings this detector collected during the run */ public FalseSharingReport analyze() { return analyzeFalseSharing(); @@ -168,7 +176,6 @@ private long getFieldSize(Class type) { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { fieldAccess.clear(); accessHistory.clear(); @@ -176,31 +183,37 @@ public void reset() { /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class FalseSharingReport { public static class ContentionPair { - /** The field 1. */ + /** First field of the contending pair. */ public final String field1; - /** The field 2. */ + /** Second field of the contending pair. */ public final String field2; - /** The distance in bytes. */ + /** Distance between the two fields; under a cache line means they share one. */ public final long distanceInBytes; - /** The accesses 1. */ + /** How many times the first field of the pair was accessed. */ public final long accesses1; - /** The accesses 2. */ + /** How many times the second field of the pair was accessed. */ public final long accesses2; - + /** + * Creates a ContentionPair. + * + * @param f1 the first field of the contending pair + * @param f2 the second field of the contending pair + * @param dist the distance between the two fields in bytes; under a cache line means they share one + * @param acc1 how many times the first field was accessed + * @param acc2 how many times the second field was accessed + */ public ContentionPair(String f1, String f2, long dist, long acc1, long acc2) { this.field1 = f1; this.field2 = f2; @@ -210,12 +223,14 @@ public ContentionPair(String f1, String f2, long dist, long acc1, long acc2) { } } - /** The false shared pairs. */ + /** Field pairs close enough to share a cache line and written from different threads. */ public final Set falseSharedPairs = new HashSet<>(); - /** The high contention fields. */ + /** Fields written often enough for cache-line sharing to matter. */ public final Set highContentionFields = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !falseSharedPairs.isEmpty() || !highContentionFields.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FileChannelPositionRaceDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FileChannelPositionRaceDetector.java index 60804088..182e1211 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FileChannelPositionRaceDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FileChannelPositionRaceDetector.java @@ -119,9 +119,8 @@ private State stateFor(Object channel) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -151,12 +150,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FinalFieldMutationDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FinalFieldMutationDetector.java index 4caabc85..bec5ed18 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FinalFieldMutationDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FinalFieldMutationDetector.java @@ -188,16 +188,24 @@ public static class FinalFieldMutationReport { this.concurrentWriteIssues = concurrentWriteIssues; } - /** {@return true if any reflective final-field mutation was detected} */ + /** + * {@return true if any reflective final-field mutation was detected} + */ public boolean hasIssues() { return !mutationIssues.isEmpty(); } - /** {@return the mutation issues} */ + /** + * {@return the mutation issues} + */ public List getMutationIssues() { return Collections.unmodifiableList(mutationIssues); } - /** {@return the racing reader issues} */ + /** + * {@return the racing reader issues} + */ public List getRacingReaderIssues() { return Collections.unmodifiableList(racingReaderIssues); } - /** {@return the concurrent write issues} */ + /** + * {@return the concurrent write issues} + */ public List getConcurrentWriteIssues() { return Collections.unmodifiableList(concurrentWriteIssues); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ForkJoinPoolDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ForkJoinPoolDetector.java index 7d204ff9..e060eaf1 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ForkJoinPoolDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ForkJoinPoolDetector.java @@ -23,6 +23,10 @@ public class ForkJoinPoolDetector { /** * Register a ForkJoinPool for monitoring. + * + * @param pool the pool being recorded, tracked by identity + * @param name a label identifying the pool in the report + * @param parallelism the configured parallelism of the pool */ public void registerPool(ForkJoinPool pool, String name, int parallelism) { poolRegistry.put(pool, new PoolInfo(name, parallelism)); @@ -30,6 +34,10 @@ public void registerPool(ForkJoinPool pool, String name, int parallelism) { /** * Record a task being forked. + * + * @param pool the pool being recorded, tracked by identity + * @param poolName a label identifying the pool in the report + * @param taskName a label identifying the task in the report */ public void recordFork(ForkJoinPool pool, String poolName, String taskName) { PoolInfo info = poolRegistry.get(pool); @@ -40,6 +48,10 @@ public void recordFork(ForkJoinPool pool, String poolName, String taskName) { /** * Record a task being joined. + * + * @param pool the pool being recorded, tracked by identity + * @param poolName a label identifying the pool in the report + * @param taskName a label identifying the task in the report */ public void recordJoin(ForkJoinPool pool, String poolName, String taskName) { PoolInfo info = poolRegistry.get(pool); @@ -50,6 +62,9 @@ public void recordJoin(ForkJoinPool pool, String poolName, String taskName) { /** * Record a task that was forked but never joined. + * + * @param poolName a label identifying the pool in the report + * @param taskName a label identifying the task in the report */ public void recordForkWithoutJoin(String poolName, String taskName) { forkedWithoutJoin.add(poolName + ":" + taskName); @@ -57,6 +72,10 @@ public void recordForkWithoutJoin(String poolName, String taskName) { /** * Record an exception in a forked task. + * + * @param poolName a label identifying the pool in the report + * @param taskName a label identifying the task in the report + * @param t the throwable the task failed with */ public void recordException(String poolName, String taskName, Throwable t) { exceptionsInTasks.add(poolName + ":" + taskName + " (" + t.getClass().getSimpleName() + ")"); @@ -64,6 +83,8 @@ public void recordException(String poolName, String taskName, Throwable t) { /** * Record work stealing event. + * + * @param pool the pool being recorded, tracked by identity */ public void recordWorkSteal(ForkJoinPool pool) { taskStealCount++; @@ -71,6 +92,10 @@ public void recordWorkSteal(ForkJoinPool pool) { /** * Record task execution time. + * + * @param pool the pool being recorded, tracked by identity + * @param poolName a label identifying the pool in the report + * @param timeMs the time in milliseconds */ public void recordTaskTime(ForkJoinPool pool, String poolName, long timeMs) { PoolInfo info = poolRegistry.get(pool); @@ -81,6 +106,8 @@ public void recordTaskTime(ForkJoinPool pool, String poolName, long timeMs) { /** * Analyze ForkJoinPool usage and return report. + * + * @return the findings this detector collected during the run */ public ForkJoinPoolReport analyze() { return new ForkJoinPoolReport( @@ -97,7 +124,13 @@ public static class ForkJoinPoolReport { private final Set forkedWithoutJoin; private final Set exceptionsInTasks; private final int taskStealCount; - + /** + * Creates a ForkJoinPoolReport. + * + * @param forkedWithoutJoin the tasks forked but never joined + * @param exceptionsInTasks the exceptions thrown inside pool tasks + * @param taskStealCount how many tasks were stolen between workers + */ public ForkJoinPoolReport( Set forkedWithoutJoin, Set exceptionsInTasks, @@ -108,7 +141,9 @@ public ForkJoinPoolReport( this.taskStealCount = taskStealCount; } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !forkedWithoutJoin.isEmpty() || !exceptionsInTasks.isEmpty(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ForkJoinTaskBlockingDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ForkJoinTaskBlockingDetector.java index e67d474e..d943d805 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ForkJoinTaskBlockingDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ForkJoinTaskBlockingDetector.java @@ -32,13 +32,21 @@ public class ForkJoinTaskBlockingDetector { private final Set activeForkJoinThreads = ConcurrentHashMap.newKeySet(); private final List blockingCalls = new CopyOnWriteArrayList<>(); - /** Call at the start of a {@code ForkJoinTask.compute()} or {@code exec()} body. */ + /** + * Call at the start of a {@code ForkJoinTask.compute()} or {@code exec()} body. + * + * @param thread the thread performing the operation + */ public void recordForkJoinTaskEntered(Thread thread) { if (thread == null) return; activeForkJoinThreads.add(thread.threadId()); } - /** Call at the end of a {@code ForkJoinTask.compute()} or {@code exec()} body. */ + /** + * Call at the end of a {@code ForkJoinTask.compute()} or {@code exec()} body. + * + * @param thread the thread performing the operation + */ public void recordForkJoinTaskExited(Thread thread) { if (thread == null) return; activeForkJoinThreads.remove(thread.threadId()); @@ -61,7 +69,9 @@ public void recordBlockingCallAttempted(Thread thread, String callType) { thread.getName(), type)); } - /** {@return report of blocking calls inside ForkJoin tasks} */ + /** + * {@return report of blocking calls inside ForkJoin tasks} + */ public ForkJoinTaskBlockingReport analyze() { ForkJoinTaskBlockingReport r = new ForkJoinTaskBlockingReport(); r.blockingCalls.addAll(blockingCalls); @@ -72,7 +82,9 @@ public ForkJoinTaskBlockingReport analyze() { public static class ForkJoinTaskBlockingReport { final List blockingCalls = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !blockingCalls.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FutureBlockingDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FutureBlockingDetector.java index 6b042d7c..1f1a44fa 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FutureBlockingDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FutureBlockingDetector.java @@ -34,7 +34,6 @@ private static class ExecutorState { /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. @@ -43,11 +42,10 @@ private static class ExecutorState { /** * Registers executor for tracking. * - * @param executor the executor - * @param name the name - * @param maxThreads the max threads + * @param executor the executor being recorded, tracked by identity + * @param name a label identifying the executor in the report + * @param maxThreads the configured maximum thread count */ - public void registerExecutor(Object executor, String name, int maxThreads) { if (!enabled || executor == null) { return; @@ -58,9 +56,8 @@ public void registerExecutor(Object executor, String name, int maxThreads) { /** * Records task submitted so it can be analysed at the end of the run. * - * @param executor the executor + * @param executor the executor being recorded, tracked by identity */ - public void recordTaskSubmitted(Object executor) { ExecutorState state = stateFor(executor); if (state != null) { @@ -70,9 +67,8 @@ public void recordTaskSubmitted(Object executor) { /** * Records task started so it can be analysed at the end of the run. * - * @param executor the executor + * @param executor the executor being recorded, tracked by identity */ - public void recordTaskStarted(Object executor) { ExecutorState state = stateFor(executor); if (state != null) { @@ -82,9 +78,8 @@ public void recordTaskStarted(Object executor) { /** * Records blocking wait so it can be analysed at the end of the run. * - * @param executor the executor + * @param executor the executor being recorded, tracked by identity */ - public void recordBlockingWait(Object executor) { ExecutorState state = stateFor(executor); if (state != null) { @@ -94,9 +89,8 @@ public void recordBlockingWait(Object executor) { /** * Records task completed so it can be analysed at the end of the run. * - * @param executor the executor + * @param executor the executor being recorded, tracked by identity */ - public void recordTaskCompleted(Object executor) { ExecutorState state = stateFor(executor); if (state != null) { @@ -113,9 +107,8 @@ public void recordTaskCompleted(Object executor) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public FutureBlockingReport analyze() { FutureBlockingReport report = new FutureBlockingReport(); @@ -136,10 +129,12 @@ public FutureBlockingReport analyze() { } public static class FutureBlockingReport { - /** The starvation risks. */ + /** Blocking calls made from a pool thread, which can exhaust the pool. */ public final Set starvationRisks = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !starvationRisks.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FutureIgnoredDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FutureIgnoredDetector.java index 69f23c93..653e4987 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FutureIgnoredDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/FutureIgnoredDetector.java @@ -69,7 +69,9 @@ public void recordInspect(Object future, Thread thread) { if (rec != null) rec.inspected = true; } - /** {@return report of Futures that were submitted but never inspected} */ + /** + * {@return report of Futures that were submitted but never inspected} + */ public FutureIgnoredReport analyze() { FutureIgnoredReport r = new FutureIgnoredReport(); for (SubmitRecord rec : submits.values()) { @@ -87,7 +89,9 @@ public FutureIgnoredReport analyze() { public static class FutureIgnoredReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/GathererConcurrencyMisuseDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/GathererConcurrencyMisuseDetector.java index 6c91452d..4a9371a1 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/GathererConcurrencyMisuseDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/GathererConcurrencyMisuseDetector.java @@ -165,18 +165,28 @@ public static class GathererConcurrencyMisuseReport { this.totalIntegrations = totalIntegrations; } - /** {@return true if any unsafe parallel-gatherer usage was detected} */ + /** + * {@return true if any unsafe parallel-gatherer usage was detected} + */ public boolean hasIssues() { return !missingCombinerIssues.isEmpty() || !sharedStateIssues.isEmpty(); } - /** {@return the missing combiner issues} */ + /** + * {@return the missing combiner issues} + */ public List getMissingCombinerIssues() { return Collections.unmodifiableList(missingCombinerIssues); } - /** {@return the shared state issues} */ + /** + * {@return the shared state issues} + */ public List getSharedStateIssues() { return Collections.unmodifiableList(sharedStateIssues); } - /** {@return the total gatherers} */ + /** + * {@return the total gatherers} + */ public int getTotalGatherers() { return totalGatherers; } - /** {@return the total integrations} */ + /** + * {@return the total integrations} + */ public int getTotalIntegrations() { return totalIntegrations; } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/HighContentionAtomicDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/HighContentionAtomicDetector.java index e2fa3c98..e370158e 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/HighContentionAtomicDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/HighContentionAtomicDetector.java @@ -191,12 +191,14 @@ public Report analyze() { /** Report produced by {@link #analyze()}. */ public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/HttpClientConcurrencyDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/HttpClientConcurrencyDetector.java index 37f4f7ef..5b4ffa8b 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/HttpClientConcurrencyDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/HttpClientConcurrencyDetector.java @@ -211,6 +211,8 @@ public static class HttpClientConcurrencyReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !pendingRequests.isEmpty() || diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/InheritableThreadLocalMisuseDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/InheritableThreadLocalMisuseDetector.java index 68b2a4ad..284d704e 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/InheritableThreadLocalMisuseDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/InheritableThreadLocalMisuseDetector.java @@ -143,7 +143,9 @@ public static class InheritableThreadLocalReport { final List pooledSetIssues = new ArrayList<>(); final List multiThreadAccess = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !pooledGetIssues.isEmpty() || !pooledSetIssues.isEmpty() diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/InterruptMonitor.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/InterruptMonitor.java index 667e333a..21d5e494 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/InterruptMonitor.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/InterruptMonitor.java @@ -34,9 +34,8 @@ private static class InterruptEvent { /** * Records interrupt exception so it can be analysed at the end of the run. * - * @param ex the ex + * @param ex the interruption that ended the wait */ - public void recordInterruptException(InterruptedException ex) { if (!enabled) { return; @@ -57,7 +56,6 @@ public void recordInterruptException(InterruptedException ex) { /** * Records interrupt restored so it can be analysed at the end of the run. */ - public void recordInterruptRestored() { if (!enabled) { return; @@ -77,9 +75,8 @@ public void recordInterruptRestored() { /** * Records ignored exception so it can be analysed at the end of the run. * - * @param description the description + * @param description free text describing the event, shown in the report */ - public void recordIgnoredException(String description) { if (!enabled) { return; @@ -95,9 +92,8 @@ public void recordIgnoredException(String description) { /** * Records blocking operation without interrupt handling so it can be analysed at the end of the run. * - * @param operationName the operation name + * @param operationName a label identifying the operation in the report */ - public void recordBlockingOperationWithoutInterruptHandling(String operationName) { if (!enabled) { return; @@ -118,9 +114,8 @@ private String inferCallSite() { /** * Analyses what has been recorded about interrupt handling and builds the report for it. * - * @return the analyze interrupt handling + * @return the findings this detector collected during the run */ - public InterruptReport analyzeInterruptHandling() { InterruptReport report = new InterruptReport(); @@ -165,6 +160,8 @@ public InterruptReport analyzeInterruptHandling() { /** * Standardized alias for {@link #analyzeInterruptHandling()}. + * + * @return the findings this detector collected during the run */ public InterruptReport analyze() { return analyzeInterruptHandling(); @@ -172,7 +169,6 @@ public InterruptReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { synchronized (interruptEvents) { interruptEvents.clear(); @@ -183,27 +179,27 @@ public void reset() { /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class InterruptReport { - /** The ignored interrupts. */ + /** Interrupts caught and discarded without restoring the flag. */ public final Set ignoredInterrupts = new HashSet<>(); - /** The repeated ignored interrupts. */ + /** Threads that ignored an interrupt more than once. */ public final Set repeatedIgnoredInterrupts = new HashSet<>(); - /** The blocking without handling. */ + /** Blocking calls made without handling {@code InterruptedException}. */ public final Set blockingWithoutHandling = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !ignoredInterrupts.isEmpty() || !repeatedIgnoredInterrupts.isEmpty() diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/InterruptSwallowingDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/InterruptSwallowingDetector.java index 29e03527..0cb95e28 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/InterruptSwallowingDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/InterruptSwallowingDetector.java @@ -59,7 +59,9 @@ public void recordCatch(Thread thread, String location, boolean restored) { location != null ? location : "unknown", restored)); } - /** {@return report of threads that swallowed an InterruptedException} */ + /** + * {@return report of threads that swallowed an InterruptedException} + */ public InterruptSwallowingReport analyze() { InterruptSwallowingReport r = new InterruptSwallowingReport(); for (CatchEvent e : events) { @@ -78,7 +80,9 @@ public InterruptSwallowingReport analyze() { public static class InterruptSwallowingReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/IssueDeduplicator.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/IssueDeduplicator.java index 3d4edcb3..3bece552 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/IssueDeduplicator.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/IssueDeduplicator.java @@ -158,32 +158,44 @@ void addEvent(T event) { threadIds.add(event.getThreadId()); } - /** {@return the fingerprint identifying this group} */ + /** + * {@return the fingerprint identifying this group} + */ public String getFingerprint() { return fingerprint; } - /** {@return number of occurrences in this group} */ + /** + * {@return number of occurrences in this group} + */ public int getCount() { return events.size(); } - /** {@return number of unique threads affected} */ + /** + * {@return number of unique threads affected} + */ public int getAffectedThreadCount() { return threadIds.size(); } - /** {@return set of affected thread IDs} */ + /** + * {@return set of affected thread IDs} + */ public Set getAffectedThreadIds() { return Collections.unmodifiableSet(threadIds); } - /** {@return the first event in this group (representative)} */ + /** + * {@return the first event in this group (representative)} + */ public @Nullable T getFirstEvent() { return events.isEmpty() ? null : events.get(0); } - /** {@return all events in this group} */ + /** + * {@return all events in this group} + */ public List getEvents() { return Collections.unmodifiableList(events); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/IssueSeverity.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/IssueSeverity.java index d47a2329..9a74dd2d 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/IssueSeverity.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/IssueSeverity.java @@ -53,17 +53,23 @@ public enum IssueSeverity { this.description = description; } - /** {@return the display label with emoji indicator} */ + /** + * {@return the display label with emoji indicator} + */ public String getLabel() { return label; } - /** {@return a brief description of what this severity means} */ + /** + * {@return a brief description of what this severity means} + */ public String getDescription() { return description; } - /** {@return ANSI color code for terminal output} */ + /** + * {@return ANSI color code for terminal output} + */ public String getAnsiColor() { switch (this) { case CRITICAL: return "\u001B[31m"; // Red @@ -74,7 +80,9 @@ public String getAnsiColor() { } } - /** {@return ANSI reset code} */ + /** + * {@return ANSI reset code} + */ public String getAnsiReset() { return "\u001B[0m"; } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/JdbcConnectionSharedDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/JdbcConnectionSharedDetector.java index 81ada98f..b43254f2 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/JdbcConnectionSharedDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/JdbcConnectionSharedDetector.java @@ -106,9 +106,8 @@ public void recordAccess(Object resource, String name, Thread thread) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -149,12 +148,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LatchMisuseDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LatchMisuseDetector.java index 2cec6412..0cf6bed4 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LatchMisuseDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LatchMisuseDetector.java @@ -32,11 +32,10 @@ private static class LatchState { /** * Registers latch for tracking. * - * @param latch the latch - * @param name the name - * @param initialCount the initial count + * @param latch the latch being recorded, tracked by identity + * @param name a label identifying the latch in the report + * @param initialCount the count the latch was created with */ - public void registerLatch(Object latch, String name, int initialCount) { if (latch == null) { return; @@ -47,9 +46,8 @@ public void registerLatch(Object latch, String name, int initialCount) { /** * Records await so it can be analysed at the end of the run. * - * @param latch the latch + * @param latch the latch being recorded, tracked by identity */ - public void recordAwait(Object latch) { LatchState state = stateFor(latch); if (state != null) { @@ -59,9 +57,8 @@ public void recordAwait(Object latch) { /** * Records count down so it can be analysed at the end of the run. * - * @param latch the latch + * @param latch the latch being recorded, tracked by identity */ - public void recordCountDown(Object latch) { LatchState state = stateFor(latch); if (state != null) { @@ -75,9 +72,8 @@ public void recordCountDown(Object latch) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public LatchMisuseReport analyze() { LatchMisuseReport report = new LatchMisuseReport(); @@ -105,12 +101,14 @@ public LatchMisuseReport analyze() { } public static class LatchMisuseReport { - /** The missing count downs. */ + /** Latches never counted down to zero. */ public final Set missingCountDowns = new HashSet<>(); - /** The extra count downs. */ + /** Latches counted down more times than they were created for. */ public final Set extraCountDowns = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !missingCountDowns.isEmpty() || !extraCountDowns.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LazyConstantMisuseDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LazyConstantMisuseDetector.java index 277429fc..bfadfd2f 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LazyConstantMisuseDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LazyConstantMisuseDetector.java @@ -283,7 +283,9 @@ public static class LazyConstantMisuseReport { this.totalComputes = totalComputes; } - /** {@return true if any correctness-affecting LazyConstant misuse was detected} */ + /** + * {@return true if any correctness-affecting LazyConstant misuse was detected} + */ public boolean hasIssues() { return !reentrantIssues.isEmpty() || !nullValueIssues.isEmpty() @@ -291,19 +293,33 @@ public boolean hasIssues() { || !nonDeterministicIssues.isEmpty(); } - /** {@return the reentrant issues} */ + /** + * {@return the reentrant issues} + */ public List getReentrantIssues() { return Collections.unmodifiableList(reentrantIssues); } - /** {@return the null value issues} */ + /** + * {@return the null value issues} + */ public List getNullValueIssues() { return Collections.unmodifiableList(nullValueIssues); } - /** {@return the multiple compute issues} */ + /** + * {@return the multiple compute issues} + */ public List getMultipleComputeIssues() { return Collections.unmodifiableList(multipleComputeIssues); } - /** {@return the non deterministic issues} */ + /** + * {@return the non deterministic issues} + */ public List getNonDeterministicIssues() { return Collections.unmodifiableList(nonDeterministicIssues); } - /** {@return the convoy warnings} */ + /** + * {@return the convoy warnings} + */ public List getConvoyWarnings() { return Collections.unmodifiableList(convoyWarnings); } - /** {@return the total gets} */ + /** + * {@return the total gets} + */ public int getTotalGets() { return totalGets; } - /** {@return the total computes} */ + /** + * {@return the total computes} + */ public int getTotalComputes() { return totalComputes; } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LazyInitRaceDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LazyInitRaceDetector.java index 5152fcad..0ef9abc9 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LazyInitRaceDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LazyInitRaceDetector.java @@ -108,6 +108,8 @@ public void recordInitialization(String fieldId) { /** * Analyses recorded initialization data and returns a report of fields * where duplicate initialization was detected. + * + * @return the findings this detector collected during the run */ public LazyInitRaceReport analyze() { LazyInitRaceReport report = new LazyInitRaceReport(); @@ -153,7 +155,11 @@ public static class LazyInitRaceReport { final List races = new ArrayList<>(); final List visibilityRisks = new ArrayList<>(); - /** Returns {@code true} when any lazy-init race or visibility risk was detected. */ + /** + * Returns {@code true} when any lazy-init race or visibility risk was detected. + * + * @return {@code true} when this detector recorded something worth reporting + */ public boolean hasIssues() { return !races.isEmpty() || !visibilityRisks.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LazyInitValidator.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LazyInitValidator.java index 12e45653..c136fc7c 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LazyInitValidator.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LazyInitValidator.java @@ -30,13 +30,12 @@ private static class LazyFieldState { /** * Records access so it can be analysed at the end of the run. * - * @param fieldName the field name - * @param observedNull the observed null - * @param initializedValue the initialized value - * @param synchronizedAccess the synchronized access - * @param volatileField the volatile field + * @param fieldName the field involved, as it should appear in the report + * @param observedNull {@code true} when the reading thread saw {@code null} + * @param initializedValue {@code true} when the value had already been initialised + * @param synchronizedAccess {@code true} when the access was made while holding the lock + * @param volatileField {@code true} when the field is declared {@code volatile} */ - public void recordAccess(String fieldName, boolean observedNull, boolean initializedValue, boolean synchronizedAccess, boolean volatileField) { if (!enabled || fieldName == null || fieldName.isBlank()) { @@ -68,9 +67,8 @@ public void recordAccess(String fieldName, boolean observedNull, boolean initial /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public LazyInitReport analyze() { LazyInitReport report = new LazyInitReport(); @@ -99,18 +97,19 @@ public LazyInitReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { fields.clear(); } public static class LazyInitReport { - /** The multiple initializations. */ + /** Fields initialised more than once because the guard was not atomic. */ public final Set multipleInitializations = new HashSet<>(); - /** The unsafe publication. */ + /** Fields published without the ordering a reader would need to see them fully. */ public final Set unsafePublication = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !multipleInitializations.isEmpty() || !unsafePublication.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LearningContent.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LearningContent.java index 90f58d78..18a7cad2 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LearningContent.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LearningContent.java @@ -9,7 +9,9 @@ public final class LearningContent { private LearningContent() { } - /** {@return the deadlock explanation} */ + /** + * {@return the deadlock explanation} + */ public static String getDeadlockExplanation() { return """ 📚 LEARNING: What is a Deadlock? @@ -31,7 +33,9 @@ The four conditions for deadlock (all must be present): """; } - /** {@return the race condition explanation} */ + /** + * {@return the race condition explanation} + */ public static String getRaceConditionExplanation() { return """ 📚 LEARNING: What is a Race Condition? @@ -49,7 +53,9 @@ Visual example (lost update): """; } - /** {@return the visibility explanation} */ + /** + * {@return the visibility explanation} + */ public static String getVisibilityExplanation() { return """ 📚 LEARNING: What is a Memory Visibility Issue? @@ -65,7 +71,9 @@ Thread B (Core 2): while (!sharedFlag) { } ← never sees the change! """; } - /** {@return the false sharing explanation} */ + /** + * {@return the false sharing explanation} + */ public static String getFalseSharingExplanation() { return """ 📚 LEARNING: What is False Sharing? @@ -83,7 +91,9 @@ writes writes (invalidates entire line!) """; } - /** {@return the completable future leak explanation} */ + /** + * {@return the completable future leak explanation} + */ public static String getCompletableFutureLeakExplanation() { return """ 📚 LEARNING: What is a CompletableFuture Completion Leak? @@ -100,7 +110,9 @@ but never completed (neither successfully nor exceptionally). """; } - /** {@return the virtual thread pinning explanation} */ + /** + * {@return the virtual thread pinning explanation} + */ public static String getVirtualThreadPinningExplanation() { return """ 📚 LEARNING: What is Virtual Thread Pinning? @@ -117,7 +129,9 @@ public static String getVirtualThreadPinningExplanation() { """; } - /** {@return the thread pool deadlock explanation} */ + /** + * {@return the thread pool deadlock explanation} + */ public static String getThreadPoolDeadlockExplanation() { return """ 📚 LEARNING: What is a Thread Pool Deadlock? @@ -135,7 +149,9 @@ public static String getThreadPoolDeadlockExplanation() { """; } - /** {@return the busy waiting explanation} */ + /** + * {@return the busy waiting explanation} + */ public static String getBusyWaitingExplanation() { return """ 📚 LEARNING: What is Busy Waiting? @@ -156,7 +172,9 @@ public static String getBusyWaitingExplanation() { """; } - /** {@return the atomicity violation explanation} */ + /** + * {@return the atomicity violation explanation} + */ public static String getAtomicityViolationExplanation() { return """ 📚 LEARNING: What is an Atomicity Violation? @@ -178,7 +196,9 @@ An atomicity violation occurs when a compound operation (read-modify-write) """; } - /** {@return the lock leak explanation} */ + /** + * {@return the lock leak explanation} + */ public static String getLockLeakExplanation() { return """ 📚 LEARNING: What is a Lock Leak? diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LivelockDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LivelockDetector.java index 4cfcfd24..a8dfb9c7 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LivelockDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LivelockDetector.java @@ -85,6 +85,8 @@ public void captureSnapshot() { /** * Analyze captured snapshots for livelock and starvation patterns. + * + * @return the findings this detector collected during the run */ public LivelockReport analyzeLivelocks() { LivelockReport report = new LivelockReport(); @@ -116,6 +118,8 @@ public LivelockReport analyzeLivelocks() { /** * Standardized alias for {@link #analyzeLivelocks()}. + * + * @return the findings this detector collected during the run */ public LivelockReport analyze() { return analyzeLivelocks(); @@ -174,7 +178,6 @@ private boolean madeProgress(List snapshots) { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { threadHistory.clear(); observedThreads.clear(); @@ -182,27 +185,27 @@ public void reset() { /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class LivelockReport { - /** The starved threads. */ + /** Threads that never got to run during the observation window. */ public final Set starvedThreads = new HashSet<>(); - /** The livelock candidates. */ + /** Threads that kept running while making no progress. */ public final Set livelockCandidates = new HashSet<>(); - /** The no progress threads. */ + /** Threads that reported no progress for the whole observation window. */ public final Set noProgressThreads = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !starvedThreads.isEmpty() || !livelockCandidates.isEmpty() || !noProgressThreads.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockContentionDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockContentionDetector.java index de39efd8..44164bfa 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockContentionDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockContentionDetector.java @@ -119,6 +119,8 @@ public void recordReleased(Object monitor, String name) { /** * Analyses recorded data and returns a contention report. + * + * @return the findings this detector collected during the run */ public LockContentionReport analyze() { LockContentionReport report = new LockContentionReport(); @@ -158,7 +160,11 @@ public static class LockContentionReport { final List hotLocks = new ArrayList<>(); - /** Returns {@code true} when any monitor exceeds the contention threshold. */ + /** + * Returns {@code true} when any monitor exceeds the contention threshold. + * + * @return {@code true} when this detector recorded something worth reporting + */ public boolean hasIssues() { return !hotLocks.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockDowngradeDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockDowngradeDetector.java index 0a55d455..fb80996c 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockDowngradeDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockDowngradeDetector.java @@ -172,7 +172,9 @@ public LockDowngradeReport analyze() { public static class LockDowngradeReport { final List upgradeAttempts = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !upgradeAttempts.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockLeakDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockLeakDetector.java index e8ea2af4..a66380fd 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockLeakDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockLeakDetector.java @@ -79,7 +79,7 @@ public void registerLock(Lock lock, String name) { /** * Record that a lock was acquired. * - * @param lock the Lock + * @param lock the lock being recorded, tracked by identity rather than equality * @param name the lock name (should match registration) */ public void recordLockAcquired(Lock lock, String name) { @@ -102,7 +102,7 @@ public void recordLockAcquired(Lock lock, String name) { /** * Record that a lock was released. * - * @param lock the Lock + * @param lock the lock being recorded, tracked by identity rather than equality * @param name the lock name (should match registration) */ public void recordLockReleased(Lock lock, String name) { @@ -189,6 +189,8 @@ public static class LockLeakReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !lockLeaks.isEmpty() || !heldLocks.isEmpty() || !excessiveHoldTimes.isEmpty(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockOrderValidator.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockOrderValidator.java index decf2813..c85b61cd 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockOrderValidator.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockOrderValidator.java @@ -42,6 +42,8 @@ private static class LockSequence { /** * Record a lock acquisition. + * + * @param lock the lock being recorded, tracked by identity rather than equality */ public void recordLockAcquisition(Object lock) { if (!enabled || lock == null) return; @@ -67,6 +69,8 @@ public void recordLockAcquisition(Object lock) { /** * Record lock release. + * + * @param lock the lock being recorded, tracked by identity rather than equality */ public void recordLockRelease(Object lock) { if (!enabled || lock == null) return; @@ -85,6 +89,8 @@ public void recordLockRelease(Object lock) { /** * Validate lock ordering consistency. + * + * @return the findings this detector collected during the run */ public LockOrderReport validateLockOrder() { LockOrderReport report = new LockOrderReport(); @@ -165,32 +171,31 @@ private boolean hasCycle(String node, Map> graph, /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { threadLockOrders.clear(); } /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class LockOrderReport { - /** The inconsistent orderings. */ + /** Lock pairs acquired in one order by one thread and the reverse by another. */ public final Set inconsistentOrderings = new HashSet<>(); - /** The potential deadlock cycles. */ + /** Cycles in the observed lock-acquisition graph. */ public final Set potentialDeadlockCycles = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !inconsistentOrderings.isEmpty() || !potentialDeadlockCycles.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockUpgradeDeadlockDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockUpgradeDeadlockDetector.java index c95444b1..c6ce28e4 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockUpgradeDeadlockDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/LockUpgradeDeadlockDetector.java @@ -38,6 +38,10 @@ private static final class State { /** * Record acquisition of a read lock. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param lockName a label identifying the lock in the report + * @param thread the thread performing the operation */ public void recordReadLockAcquired(ReentrantReadWriteLock lock, String lockName, Thread thread) { if (lock == null || thread == null) return; @@ -47,6 +51,9 @@ public void recordReadLockAcquired(ReentrantReadWriteLock lock, String lockName, /** * Record release of a read lock. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param thread the thread performing the operation */ public void recordReadLockReleased(ReentrantReadWriteLock lock, Thread thread) { if (lock == null || thread == null) return; @@ -59,6 +66,10 @@ public void recordReadLockReleased(ReentrantReadWriteLock lock, Thread thread) { /** * Record attempt to acquire a write lock. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param lockName a label identifying the lock in the report + * @param thread the thread performing the operation */ public void recordWriteLockAcquisitionAttempt(ReentrantReadWriteLock lock, String lockName, Thread thread) { if (lock == null || thread == null) return; @@ -74,9 +85,8 @@ public void recordWriteLockAcquisitionAttempt(ReentrantReadWriteLock lock, Strin /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : violations.values()) { @@ -101,12 +111,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MdcContextLeakDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MdcContextLeakDetector.java index 61b014be..62d38cc1 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MdcContextLeakDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MdcContextLeakDetector.java @@ -79,7 +79,9 @@ public void recordTaskEnd(Thread thread, Map mdcSnapshot) { snap.endMdc = mdcSnapshot != null ? new LinkedHashMap<>(mdcSnapshot) : Collections.emptyMap(); } - /** {@return report of threads that left MDC entries behind after task completion} */ + /** + * {@return report of threads that left MDC entries behind after task completion} + */ public MdcContextLeakReport analyze() { MdcContextLeakReport r = new MdcContextLeakReport(); for (TaskSnapshot snap : snapshots.values()) { @@ -100,7 +102,9 @@ public MdcContextLeakReport analyze() { public static class MdcContextLeakReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MemoryModelValidator.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MemoryModelValidator.java index f377c4a2..d6f2c349 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MemoryModelValidator.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MemoryModelValidator.java @@ -25,6 +25,8 @@ public class MemoryModelValidator { /** * Run comprehensive JMM validation on the test framework. * This checks that all internal state transitions are properly synchronized. + * + * @return the findings this detector collected during the run */ public ValidationResult validate() { ValidationResult result = new ValidationResult(); @@ -167,19 +169,23 @@ private void testAtomicVisibility(ValidationResult result) { } public static class ValidationResult { - /** The tests run. */ + /** How many ordering checks were run. */ public int testsRun = 0; - /** The tests passed. */ + /** How many of the ordering checks held. */ public int testsPassed = 0; - /** The observations. */ + /** Every recorded observation, in the order it was made. */ public final List observations = Collections.synchronizedList(new ArrayList<>()); - /** {@return whether valid} */ + /** + * {@return whether valid} + */ public boolean isValid() { return testsRun > 0 && testsPassed == testsRun; } - /** {@return the pass rate} */ + /** + * {@return the pass rate} + */ public double getPassRate() { return testsRun == 0 ? 0 : 100.0 * testsPassed / testsRun; } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MemoryOrderingMonitor.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MemoryOrderingMonitor.java index 20f9848d..9e1fefbe 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MemoryOrderingMonitor.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MemoryOrderingMonitor.java @@ -38,6 +38,9 @@ private static class MemoryAccess { /** * Record a memory read. + * + * @param location where in the code this happened, shown in the report + * @param value the value read or written */ public void recordRead(String location, Object value) { if (!enabled) return; @@ -46,6 +49,9 @@ public void recordRead(String location, Object value) { /** * Record a memory write. + * + * @param location where in the code this happened, shown in the report + * @param value the value read or written */ public void recordWrite(String location, Object value) { if (!enabled) return; @@ -54,6 +60,8 @@ public void recordWrite(String location, Object value) { /** * Analyze for memory ordering violations. + * + * @return the findings this detector collected during the run */ public MemoryOrderingReport analyzeOrdering() { MemoryOrderingReport report = new MemoryOrderingReport(); @@ -101,6 +109,8 @@ public MemoryOrderingReport analyzeOrdering() { /** * Standardized alias for {@link #analyzeOrdering()}. + * + * @return the findings this detector collected during the run */ public MemoryOrderingReport analyze() { return analyzeOrdering(); @@ -108,21 +118,18 @@ public MemoryOrderingReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { accessLog.clear(); } /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } @@ -142,7 +149,9 @@ public static class MemoryOrderingReport { */ public final Set suspiciousReorderings = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !staleCoreads.isEmpty() || !suspiciousReorderings.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MissedSignalDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MissedSignalDetector.java index 47a8f0a9..50e89a84 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MissedSignalDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MissedSignalDetector.java @@ -118,6 +118,8 @@ public void recordNotifyAll(String conditionName) { /** * Analyses recorded signal/wait data and returns a report of conditions * that suffered missed signals. + * + * @return the findings this detector collected during the run */ public MissedSignalReport analyze() { MissedSignalReport report = new MissedSignalReport(); @@ -150,7 +152,11 @@ public static class MissedSignalReport { final List missedConditions = new ArrayList<>(); - /** Returns {@code true} when at least one condition suffered a missed signal. */ + /** + * Returns {@code true} when at least one condition suffered a missed signal. + * + * @return {@code true} when this detector recorded something worth reporting + */ public boolean hasIssues() { return !missedConditions.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MutableMapKeyDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MutableMapKeyDetector.java index 06e4fb32..2cf642f4 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MutableMapKeyDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/MutableMapKeyDetector.java @@ -119,7 +119,9 @@ public static class MutableMapKeyReport { final List mutatedKeys = new ArrayList<>(); final List mutationDetails = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !mutatedKeys.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NestedMonitorLockoutDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NestedMonitorLockoutDetector.java index 6bd96743..103d93f6 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NestedMonitorLockoutDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NestedMonitorLockoutDetector.java @@ -103,7 +103,9 @@ public NestedMonitorLockoutReport analyze() { public static class NestedMonitorLockoutReport { final List incidents = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !incidents.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NonAtomicConcurrentMapUpdateDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NonAtomicConcurrentMapUpdateDetector.java index ea19a1e5..2b46b48c 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NonAtomicConcurrentMapUpdateDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NonAtomicConcurrentMapUpdateDetector.java @@ -105,9 +105,8 @@ public void recordCheckThenAct(ConcurrentMap map, Object key, String opera /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : sites.values()) { @@ -138,12 +137,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NotifyAllValidator.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NotifyAllValidator.java index 1fef0bd4..e857d2e0 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NotifyAllValidator.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NotifyAllValidator.java @@ -35,10 +35,9 @@ private static class MonitorState { /** * Records waiter added so it can be analysed at the end of the run. * - * @param monitor the monitor - * @param monitorName the monitor name + * @param monitor the object being used as a monitor, tracked by identity + * @param monitorName a label identifying the monitor in the report */ - public void recordWaiterAdded(Object monitor, String monitorName) { if (!enabled || monitor == null) { return; @@ -56,9 +55,8 @@ public void recordWaiterAdded(Object monitor, String monitorName) { /** * Records waiter released so it can be analysed at the end of the run. * - * @param monitor the monitor + * @param monitor the object being used as a monitor, tracked by identity */ - public void recordWaiterReleased(Object monitor) { if (!enabled || monitor == null) { return; @@ -72,10 +70,9 @@ public void recordWaiterReleased(Object monitor) { /** * Records notify so it can be analysed at the end of the run. * - * @param monitor the monitor - * @param notifyAll the notify all + * @param monitor the object being used as a monitor, tracked by identity + * @param notifyAll {@code true} when {@code notifyAll} was called rather than {@code notify} */ - public void recordNotify(Object monitor, boolean notifyAll) { if (!enabled || monitor == null) { return; @@ -101,9 +98,8 @@ public void recordNotify(Object monitor, boolean notifyAll) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public NotifyAllReport analyze() { NotifyAllReport report = new NotifyAllReport(); @@ -132,30 +128,29 @@ public NotifyAllReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { monitors.clear(); } /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class NotifyAllReport { - /** The notify instead of notify all. */ + /** Monitors signalled with {@code notify} where waiters await different conditions. */ public final Set notifyInsteadOfNotifyAll = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !notifyInsteadOfNotifyAll.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NotifyWithoutMonitorDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NotifyWithoutMonitorDetector.java index 444aebce..41787ea1 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NotifyWithoutMonitorDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/NotifyWithoutMonitorDetector.java @@ -90,7 +90,11 @@ public void recordNotifyAttempt(Object monitor, String label) { } } - /** Report produced by {@link #analyze()}. */ + /** + * Report produced by {@link #analyze()}. + * + * @return the findings this detector collected during the run + */ public Report analyze() { Report r = new Report(); synchronized (attempts) { @@ -119,12 +123,14 @@ public Report analyze() { /** Report. */ public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/OptimisticReadValidationDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/OptimisticReadValidationDetector.java index de9558bc..96d1a6e3 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/OptimisticReadValidationDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/OptimisticReadValidationDetector.java @@ -53,7 +53,13 @@ private static String key(Object lock, Thread thread) { return System.identityHashCode(lock) + ":" + thread.threadId(); } - /** Call immediately after {@code StampedLock.tryOptimisticRead()}. */ + /** + * Call immediately after {@code StampedLock.tryOptimisticRead()}. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param stamp the stamp returned by the {@code StampedLock} operation + * @param thread the thread performing the operation + */ public void recordOptimisticReadStarted(Object lock, long stamp, Thread thread) { if (lock == null || thread == null) return; OptimisticRead replaced = @@ -71,6 +77,10 @@ public void recordOptimisticReadStarted(Object lock, long stamp, Thread thread) * Call when reading a field whose value was obtained during an optimistic read. * * @param fieldName descriptive name for the data being read (for reports) + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param stamp the stamp returned by the {@code StampedLock} operation + * @param thread the thread performing the operation */ public void recordDataAccessed(Object lock, long stamp, Thread thread, String fieldName) { if (lock == null || thread == null) return; @@ -84,6 +94,10 @@ public void recordDataAccessed(Object lock, long stamp, Thread thread, String fi * Call immediately after {@code lock.validate(stamp)}. * * @param result the boolean returned by {@code validate()} + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param stamp the stamp returned by the {@code StampedLock} operation + * @param thread the thread performing the operation */ public void recordValidateCalled(Object lock, long stamp, boolean result, Thread thread) { if (lock == null || thread == null) return; @@ -103,7 +117,9 @@ public void recordValidateCalled(Object lock, long stamp, boolean result, Thread } } - /** {@return report of optimistic read validation failures} */ + /** + * {@return report of optimistic read validation failures} + */ public OptimisticReadValidationReport analyze() { OptimisticReadValidationReport r = new OptimisticReadValidationReport(); // reads still pending at analysis time were never validated @@ -126,7 +142,9 @@ private static String neverValidatedViolation(OptimisticRead read) { public static class OptimisticReadValidationReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ParallelStreamDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ParallelStreamDetector.java index 48389c45..12c2bed3 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ParallelStreamDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ParallelStreamDetector.java @@ -74,7 +74,7 @@ public void recordParallelStream(String streamName) { /** * Record a stateful lambda operation (bug in parallel stream). * - * @param streamName the stream name + * @param streamName a label identifying the stream in the report * @param operation the operation type (forEach, map, filter, etc.) */ public void recordStatefulOperation(String streamName, String operation) { @@ -84,7 +84,7 @@ public void recordStatefulOperation(String streamName, String operation) { /** * Record a stateless operation (safe in parallel stream). * - * @param streamName the stream name + * @param streamName a label identifying the stream in the report * @param operation the operation type */ public void recordStatelessOperation(String streamName, String operation) { @@ -94,7 +94,7 @@ public void recordStatelessOperation(String streamName, String operation) { /** * Record use of a non-thread-safe collector. * - * @param streamName the stream name + * @param streamName a label identifying the stream in the report * @param collectorType the collector type (ArrayList, HashMap, etc.) */ public void recordNonThreadSafeCollector(String streamName, String collectorType) { @@ -111,7 +111,7 @@ public void recordNonThreadSafeCollector(String streamName, String collectorType /** * Record side effects in parallel stream (bug). * - * @param streamName the stream name + * @param streamName a label identifying the stream in the report * @param sideEffectType the type of side effect */ public void recordSideEffect(String streamName, String sideEffectType) { @@ -217,6 +217,8 @@ public static class ParallelStreamReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !statefulLambdas.isEmpty() || !nonThreadSafeCollectors.isEmpty() || !sideEffects.isEmpty(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/Phase1DetectorSet.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/Phase1DetectorSet.java index afcfcc83..6a4e31a5 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/Phase1DetectorSet.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/Phase1DetectorSet.java @@ -21,19 +21,19 @@ */ public final class Phase1DetectorSet { - /** The visibility. */ + /** The visibility monitor for this run, or {@code null} when it is disabled. */ public final @Nullable VisibilityMonitor visibility; - /** The livelock. */ + /** The livelock detector for this run, or {@code null} when it is disabled. */ public final @Nullable LivelockDetector livelock; - /** The race. */ + /** The race-condition detector for this run, or {@code null} when it is disabled. */ public final @Nullable RaceConditionDetector race; - /** The thread local. */ + /** The thread-local monitor for this run, or {@code null} when it is disabled. */ public final @Nullable ThreadLocalMonitor threadLocal; - /** The busy wait. */ + /** The busy-wait detector for this run, or {@code null} when it is disabled. */ public final @Nullable BusyWaitDetector busyWait; - /** The atomicity. */ + /** The atomicity validator for this run, or {@code null} when it is disabled. */ public final @Nullable AtomicityValidator atomicity; - /** The interrupt. */ + /** The interrupt monitor for this run, or {@code null} when it is disabled. */ public final @Nullable InterruptMonitor interrupt; /** @@ -77,6 +77,9 @@ private Phase1DetectorSet(@Nullable VisibilityMonitor visibility, *

Prefer {@link #from(AsyncTestConfig, AsyncTestContext)} whenever a context is * available (the runner always has one) — this overload always constructs fresh, * disconnected instances and is kept only for direct/unit-test construction. + * + * @param config the resolved configuration for this run + * @return the detector set matching that configuration */ public static Phase1DetectorSet from(AsyncTestConfig config) { return from(config, null); @@ -102,6 +105,10 @@ public static Phase1DetectorSet from(AsyncTestConfig config) { * disconnected-but-functional detector is safer than a {@code NullPointerException}. * * @since 1.7.0 + * + * @param config the resolved configuration for this run + * @param ctx the context this detector reports into + * @return the detector set matching that configuration */ public static Phase1DetectorSet from(AsyncTestConfig config, @Nullable AsyncTestContext ctx) { return new Phase1DetectorSet( @@ -162,6 +169,8 @@ public void printReports() { * so reporting them here too would double-count and double-print every finding. * * @since 1.7.0 + * + * @return the reports collected from each detector, keyed by detector name */ public Map collectReports() { Map out = new LinkedHashMap<>(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/PhaserDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/PhaserDetector.java index febe9d7c..2879f8fc 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/PhaserDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/PhaserDetector.java @@ -23,6 +23,10 @@ public class PhaserDetector { /** * Register a Phaser for monitoring. + * + * @param phaser the phaser being recorded, tracked by identity + * @param name a label identifying the phaser in the report + * @param parties the number of parties the barrier was created for */ public void registerPhaser(Phaser phaser, String name, int parties) { phaserRegistry.put(phaser, new PhaserInfo(name, parties)); @@ -30,6 +34,8 @@ public void registerPhaser(Phaser phaser, String name, int parties) { /** * Record a thread arriving at the phaser. + * + * @param phaser the phaser being recorded, tracked by identity */ public void recordArrive(Phaser phaser) { PhaserInfo info = phaserRegistry.get(phaser); @@ -40,6 +46,8 @@ public void recordArrive(Phaser phaser) { /** * Record a thread arriving and awaiting advance. + * + * @param phaser the phaser being recorded, tracked by identity */ public void recordArriveAwaitAdvance(Phaser phaser) { PhaserInfo info = phaserRegistry.get(phaser); @@ -51,6 +59,8 @@ public void recordArriveAwaitAdvance(Phaser phaser) { /** * Record a phaser await that timed out. + * + * @param phaser the phaser being recorded, tracked by identity */ public void recordTimeout(Phaser phaser) { timedOutPhasers.add(phaser); @@ -58,6 +68,8 @@ public void recordTimeout(Phaser phaser) { /** * Record a phaser that was terminated. + * + * @param phaser the phaser being recorded, tracked by identity */ public void recordTermination(Phaser phaser) { terminatedPhasers.add(phaser); @@ -65,6 +77,9 @@ public void recordTermination(Phaser phaser) { /** * Record successful phaser phase completion. + * + * @param phaser the phaser being recorded, tracked by identity + * @param phase the phase number this event belongs to */ public void recordPhaseComplete(Phaser phaser, int phase) { PhaserInfo info = phaserRegistry.get(phaser); @@ -75,6 +90,8 @@ public void recordPhaseComplete(Phaser phaser, int phase) { /** * Analyze phaser usage and return report. + * + * @return the findings this detector collected during the run */ public PhaserReport analyze() { return new PhaserReport( @@ -91,7 +108,13 @@ public static class PhaserReport { private final Map phaserRegistry; private final Set timedOutPhasers; private final Set terminatedPhasers; - + /** + * Creates a PhaserReport. + * + * @param phaserRegistry every registered phaser and what was observed on it + * @param timedOutPhasers the phasers whose await timed out + * @param terminatedPhasers the phasers that reached termination + */ public PhaserReport( Map phaserRegistry, Set timedOutPhasers, @@ -102,7 +125,9 @@ public PhaserReport( this.terminatedPhasers = Collections.unmodifiableSet(new HashSet<>(terminatedPhasers)); } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !timedOutPhasers.isEmpty() || !terminatedPhasers.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/PipelineMonitor.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/PipelineMonitor.java index a6ed9f28..1439d88e 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/PipelineMonitor.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/PipelineMonitor.java @@ -38,6 +38,8 @@ private static class PipelineStage { /** * Register a pipeline stage. + * + * @param stageName a label identifying the stage in the report */ public void registerStage(String stageName) { stages.putIfAbsent(stageName, new PipelineStage(stageName)); @@ -45,6 +47,9 @@ public void registerStage(String stageName) { /** * Record event published to a stage. + * + * @param stageName a label identifying the stage in the report + * @param eventId correlates the calls belonging to one event */ public void recordEventPublished(String stageName, String eventId) { if (!enabled) return; @@ -56,6 +61,9 @@ public void recordEventPublished(String stageName, String eventId) { /** * Record event processed by a stage. + * + * @param stageName a label identifying the stage in the report + * @param eventId correlates the calls belonging to one event */ public void recordEventProcessed(String stageName, String eventId) { if (!enabled) return; @@ -69,6 +77,10 @@ public void recordEventProcessed(String stageName, String eventId) { /** * Record event failure. + * + * @param stageName a label identifying the stage in the report + * @param eventId correlates the calls belonging to one event + * @param reason why the event was recorded, shown in the report */ public void recordEventFailed(String stageName, String eventId, String reason) { if (!enabled) return; @@ -83,6 +95,8 @@ public void recordEventFailed(String stageName, String eventId, String reason) { /** * Analyze pipeline for signal loss. + * + * @return the findings this detector collected during the run */ public PipelineReport analyzePipeline() { PipelineReport report = new PipelineReport(); @@ -117,6 +131,8 @@ public PipelineReport analyzePipeline() { /** * Standardized alias for {@link #analyzePipeline()}. + * + * @return the findings this detector collected during the run */ public PipelineReport analyze() { return analyzePipeline(); @@ -124,7 +140,6 @@ public PipelineReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { stages.clear(); eventLog.clear(); @@ -132,27 +147,27 @@ public void reset() { /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class PipelineReport { - /** The missing events. */ + /** Events entering a stage but never leaving it. */ public final Set missingEvents = new HashSet<>(); - /** The failed events. */ + /** Events that failed, keyed by the stage they failed in. */ public final Map> failedEvents = new HashMap<>(); - /** The low processing rate. */ + /** Stages that processed events more slowly than the threshold. */ public final Set lowProcessingRate = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !missingEvents.isEmpty() || !failedEvents.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/PublicLockExposureDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/PublicLockExposureDetector.java index 1a4ae1e8..f7dc90ce 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/PublicLockExposureDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/PublicLockExposureDetector.java @@ -63,7 +63,9 @@ public void recordObjectPublished(Object obj, String context) { if (context != null) publishContexts.put(id, context); } - /** {@return report of publicly exposed internal locks} */ + /** + * {@return report of publicly exposed internal locks} + */ public PublicLockExposureReport analyze() { PublicLockExposureReport r = new PublicLockExposureReport(); for (int id : synchronizedObjects) { @@ -83,7 +85,9 @@ public PublicLockExposureReport analyze() { public static class PublicLockExposureReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/RaceConditionDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/RaceConditionDetector.java index b1c7a600..d3fe0d5b 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/RaceConditionDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/RaceConditionDetector.java @@ -43,10 +43,9 @@ private static class ObjectFieldState { /** * Records field read so it can be analysed at the end of the run. * - * @param object the object - * @param fieldName the field name + * @param object the object the access is on, tracked by identity + * @param fieldName the field involved, as it should appear in the report */ - public void recordFieldRead(Object object, String fieldName) { if (!enabled || object == null || fieldName == null || fieldName.isBlank()) { return; @@ -56,10 +55,9 @@ public void recordFieldRead(Object object, String fieldName) { /** * Records field write so it can be analysed at the end of the run. * - * @param object the object - * @param fieldName the field name + * @param object the object the access is on, tracked by identity + * @param fieldName the field involved, as it should appear in the report */ - public void recordFieldWrite(Object object, String fieldName) { if (!enabled || object == null || fieldName == null || fieldName.isBlank()) { return; @@ -80,9 +78,8 @@ private void recordAccess(Object object, String fieldName, boolean write) { /** * Analyses what has been recorded about race conditions and builds the report for it. * - * @return the analyze race conditions + * @return the findings this detector collected during the run */ - public RaceConditionReport analyzeRaceConditions() { RaceConditionReport report = new RaceConditionReport(); @@ -161,6 +158,8 @@ public RaceConditionReport analyzeRaceConditions() { /** * Standardized alias for {@link #analyzeRaceConditions()}. + * + * @return the findings this detector collected during the run */ public RaceConditionReport analyze() { return analyzeRaceConditions(); @@ -168,7 +167,6 @@ public RaceConditionReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { objects.clear(); deduplicator.clear(); @@ -176,14 +174,12 @@ public void reset() { /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } @@ -206,7 +202,14 @@ public static class RaceConditionEvent implements DeduplicatableEvent { private final String location; private final int lineNumber; private final long threadId; - + /** + * Creates a RaceConditionEvent. + * + * @param type the kind of event being recorded, shown in the report + * @param location where in the code this happened, shown in the report + * @param lineNumber the source line the access came from + * @param threadId the id of the thread performing the operation + */ public RaceConditionEvent(String type, String location, int lineNumber, long threadId) { this.type = type; this.location = location; @@ -242,12 +245,14 @@ public String getType() { } public static class RaceConditionReport { - /** The unsafe accesses. */ + /** Individual accesses that took part in a suspected race. */ public final Set unsafeAccesses = new HashSet<>(); - /** The potential races. */ + /** Fields accessed from more than one thread without synchronization. */ public final Set potentialRaces = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !unsafeAccesses.isEmpty() || !potentialRaces.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ReadWriteLockMonitor.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ReadWriteLockMonitor.java index cc66d4c7..82fb3b79 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ReadWriteLockMonitor.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ReadWriteLockMonitor.java @@ -38,6 +38,9 @@ private static class LockState { /** * Register a read-write lock for monitoring. + * + * @param rwLock the read-write lock being recorded, tracked by identity + * @param name a label identifying the rw lock in the report */ public void registerLock(Object rwLock, String name) { if (!enabled) return; @@ -48,6 +51,9 @@ public void registerLock(Object rwLock, String name) { /** * Record read lock acquisition. + * + * @param rwLock the read-write lock being recorded, tracked by identity + * @param waitTimeMs the wait time in milliseconds */ public void recordReadLockAcquired(Object rwLock, long waitTimeMs) { if (!enabled) return; @@ -63,6 +69,8 @@ public void recordReadLockAcquired(Object rwLock, long waitTimeMs) { /** * Record read lock release. + * + * @param rwLock the read-write lock being recorded, tracked by identity */ public void recordReadLockReleased(Object rwLock) { if (!enabled) return; @@ -76,6 +84,9 @@ public void recordReadLockReleased(Object rwLock) { /** * Record write lock acquisition. + * + * @param rwLock the read-write lock being recorded, tracked by identity + * @param waitTimeMs the wait time in milliseconds */ public void recordWriteLockAcquired(Object rwLock, long waitTimeMs) { if (!enabled) return; @@ -97,6 +108,8 @@ public void recordWriteLockAcquired(Object rwLock, long waitTimeMs) { /** * Record write lock release. + * + * @param rwLock the read-write lock being recorded, tracked by identity */ public void recordWriteLockReleased(Object rwLock) { if (!enabled) return; @@ -110,6 +123,8 @@ public void recordWriteLockReleased(Object rwLock) { /** * Analyze read-write lock fairness. + * + * @return the findings this detector collected during the run */ public ReadWriteLockReport analyzeFairness() { ReadWriteLockReport report = new ReadWriteLockReport(); @@ -160,6 +175,8 @@ public ReadWriteLockReport analyzeFairness() { /** * Standardized alias for {@link #analyzeFairness()}. + * + * @return the findings this detector collected during the run */ public ReadWriteLockReport analyze() { return analyzeFairness(); @@ -167,38 +184,37 @@ public ReadWriteLockReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { locks.clear(); } /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class ReadWriteLockReport { - /** The reader dominated locks. */ + /** Locks where readers arrived often enough to hold writers off. */ public final Set readerDominatedLocks = new HashSet<>(); - /** The starved writers. */ + /** Writers that never acquired the lock during the run. */ public final Set starvedWriters = new HashSet<>(); - /** The long write waits. */ + /** Writers that waited longer than the reporting threshold. */ public final Set longWriteWaits = new HashSet<>(); - /** The current write holders. */ + /** Threads currently holding the write lock. */ public final Set currentWriteHolders = new HashSet<>(); - /** The current read holders. */ + /** Threads currently holding the read lock. */ public final Set currentReadHolders = new HashSet<>(); - /** {@return whether there are fairness issues} */ + /** + * {@return whether there are fairness issues} + */ public boolean hasFairnessIssues() { return !readerDominatedLocks.isEmpty() || !starvedWriters.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ReentrantLockDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ReentrantLockDetector.java index 478354a4..f495ffdb 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ReentrantLockDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ReentrantLockDetector.java @@ -25,6 +25,9 @@ public class ReentrantLockDetector { /** * Register a ReentrantLock for monitoring. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param name a label identifying the lock in the report */ public void registerLock(ReentrantLock lock, String name) { lockRegistry.put(lock, new LockInfo(name)); @@ -32,6 +35,9 @@ public void registerLock(ReentrantLock lock, String name) { /** * Record a successful lock acquisition. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param threadName a label identifying the thread in the report */ public void recordLockAcquired(ReentrantLock lock, String threadName) { LockInfo info = lockRegistry.get(lock); @@ -42,6 +48,9 @@ public void recordLockAcquired(ReentrantLock lock, String threadName) { /** * Record a lock release. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param threadName a label identifying the thread in the report */ public void recordLockReleased(ReentrantLock lock, String threadName) { LockInfo info = lockRegistry.get(lock); @@ -52,6 +61,8 @@ public void recordLockReleased(ReentrantLock lock, String threadName) { /** * Record a tryLock() that timed out. + * + * @param lock the lock being recorded, tracked by identity rather than equality */ public void recordLockTimeout(ReentrantLock lock) { timeoutLocks.add(lock); @@ -59,6 +70,9 @@ public void recordLockTimeout(ReentrantLock lock) { /** * Record potential lock starvation (wait time exceeds threshold). + * + * @param threadName a label identifying the thread in the report + * @param waitTimeMs the wait time in milliseconds */ public void recordStarvation(String threadName, long waitTimeMs) { starvationThreads.add(threadName + " (waited " + waitTimeMs + "ms)"); @@ -66,6 +80,8 @@ public void recordStarvation(String threadName, long waitTimeMs) { /** * Analyze lock usage and return report. + * + * @return the findings this detector collected during the run */ public ReentrantLockReport analyze() { return new ReentrantLockReport( @@ -82,7 +98,13 @@ public static class ReentrantLockReport { private final Map lockRegistry; private final Set timeoutLocks; private final Set starvationThreads; - + /** + * Creates a ReentrantLockReport. + * + * @param lockRegistry every registered lock and what was observed on it + * @param timeoutLocks the locks whose timed acquisition failed + * @param starvationThreads the threads that waited long enough to count as starved + */ public ReentrantLockReport( Map lockRegistry, Set timeoutLocks, @@ -93,7 +115,9 @@ public ReentrantLockReport( this.starvationThreads = Collections.unmodifiableSet(new HashSet<>(starvationThreads)); } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !timeoutLocks.isEmpty() || !starvationThreads.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ResourceLeakDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ResourceLeakDetector.java index ba309616..456c31f4 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ResourceLeakDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ResourceLeakDetector.java @@ -79,7 +79,7 @@ public void registerResource(Object resource, String name, String resourceType) /** * Record that a resource was opened/acquired. * - * @param resource the resource + * @param resource the resource being recorded, tracked by identity * @param name the resource name (should match registration) */ public void recordResourceOpened(Object resource, String name) { @@ -98,7 +98,7 @@ public void recordResourceOpened(Object resource, String name) { /** * Record that a resource was closed/released. * - * @param resource the resource + * @param resource the resource being recorded, tracked by identity * @param name the resource name (should match registration) */ public void recordResourceClosed(Object resource, String name) { @@ -168,6 +168,8 @@ public static class ResourceLeakReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !resourceLeaks.isEmpty() || !openResources.isEmpty(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ScheduledExecutorDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ScheduledExecutorDetector.java index 5430c92d..d0b5830d 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ScheduledExecutorDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ScheduledExecutorDetector.java @@ -24,6 +24,10 @@ public class ScheduledExecutorDetector { /** * Register a ScheduledExecutorService for monitoring. + * + * @param executor the executor being recorded, tracked by identity + * @param name a label identifying the executor in the report + * @param corePoolSize the configured core pool size */ public void registerExecutor(ScheduledExecutorService executor, String name, int corePoolSize) { executorRegistry.put(executor, new ExecutorInfo(name, corePoolSize)); @@ -31,6 +35,10 @@ public void registerExecutor(ScheduledExecutorService executor, String name, int /** * Record a task being scheduled. + * + * @param executor the executor being recorded, tracked by identity + * @param executorName a label identifying the executor in the report + * @param taskName a label identifying the task in the report */ public void recordSchedule(ScheduledExecutorService executor, String executorName, String taskName) { ExecutorInfo info = executorRegistry.get(executor); @@ -41,6 +49,10 @@ public void recordSchedule(ScheduledExecutorService executor, String executorNam /** * Record a task starting execution. + * + * @param executor the executor being recorded, tracked by identity + * @param executorName a label identifying the executor in the report + * @param taskName a label identifying the task in the report */ public void recordTaskStart(ScheduledExecutorService executor, String executorName, String taskName) { ExecutorInfo info = executorRegistry.get(executor); @@ -51,6 +63,11 @@ public void recordTaskStart(ScheduledExecutorService executor, String executorNa /** * Record a task completing execution. + * + * @param executor the executor being recorded, tracked by identity + * @param executorName a label identifying the executor in the report + * @param taskName a label identifying the task in the report + * @param durationMs the duration in milliseconds */ public void recordTaskComplete(ScheduledExecutorService executor, String executorName, String taskName, long durationMs) { ExecutorInfo info = executorRegistry.get(executor); @@ -64,6 +81,9 @@ public void recordTaskComplete(ScheduledExecutorService executor, String executo /** * Record an exception in a scheduled task. + * + * @param executor the executor being recorded, tracked by identity + * @param executorName a label identifying the executor in the report */ public void recordException(ScheduledExecutorService executor, String executorName) { exceptionInTasks++; @@ -71,6 +91,8 @@ public void recordException(ScheduledExecutorService executor, String executorNa /** * Record executor shutdown. + * + * @param executor the executor being recorded, tracked by identity */ public void recordShutdown(ScheduledExecutorService executor) { ExecutorInfo info = executorRegistry.get(executor); @@ -92,6 +114,8 @@ public void checkShutdown() { /** * Analyze ScheduledExecutorService usage and return report. + * + * @return the findings this detector collected during the run */ public ScheduledExecutorReport analyze() { checkShutdown(); @@ -111,7 +135,14 @@ public static class ScheduledExecutorReport { private final Set notShutdownExecutors; private final Set longRunningTasks; private final int exceptionInTasks; - + /** + * Creates a ScheduledExecutorReport. + * + * @param executorRegistry every registered executor and what was observed on it + * @param notShutdownExecutors the executors never shut down + * @param longRunningTasks the tasks that ran past the reporting threshold + * @param exceptionInTasks the exceptions thrown inside scheduled tasks + */ public ScheduledExecutorReport( Map executorRegistry, Set notShutdownExecutors, @@ -124,7 +155,9 @@ public ScheduledExecutorReport( this.exceptionInTasks = exceptionInTasks; } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !notShutdownExecutors.isEmpty() || !longRunningTasks.isEmpty() diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ScopedValueMisuseDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ScopedValueMisuseDetector.java index 0092f546..12ae9f03 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ScopedValueMisuseDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ScopedValueMisuseDetector.java @@ -182,22 +182,36 @@ public static class ScopedValueMisuseReport { this.unboundGetCount = unboundGetCount; } - /** {@return true if any ScopedValue misuse issues were detected} */ + /** + * {@return true if any ScopedValue misuse issues were detected} + */ public boolean hasIssues() { return !unboundGetIssues.isEmpty() || !rebindIssues.isEmpty(); } - /** {@return the unbound get issues} */ + /** + * {@return the unbound get issues} + */ public List getUnboundGetIssues() { return Collections.unmodifiableList(unboundGetIssues); } - /** {@return the rebind issues} */ + /** + * {@return the rebind issues} + */ public List getRebindIssues() { return Collections.unmodifiableList(rebindIssues); } - /** {@return the high binding warnings} */ + /** + * {@return the high binding warnings} + */ public List getHighBindingWarnings() { return Collections.unmodifiableList(highBindingWarnings); } - /** {@return the total bindings} */ + /** + * {@return the total bindings} + */ public int getTotalBindings() { return totalBindings; } - /** {@return the total get calls} */ + /** + * {@return the total get calls} + */ public int getTotalGetCalls() { return totalGetCalls; } - /** {@return the unbound get count} */ + /** + * {@return the unbound get count} + */ public int getUnboundGetCount() { return unboundGetCount; } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SemaphoreMisuseDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SemaphoreMisuseDetector.java index 7485648e..6a969d17 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SemaphoreMisuseDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SemaphoreMisuseDetector.java @@ -73,7 +73,7 @@ public void registerSemaphore(Semaphore semaphore, String name, int initialPermi /** * Record a permit acquisition. * - * @param semaphore the semaphore + * @param semaphore the semaphore being recorded, tracked by identity * @param name the semaphore name (should match registration) */ public void recordAcquire(Semaphore semaphore, String name) { @@ -96,7 +96,7 @@ public void recordAcquire(Semaphore semaphore, String name) { /** * Record a permit release. * - * @param semaphore the semaphore + * @param semaphore the semaphore being recorded, tracked by identity * @param name the semaphore name (should match registration) */ public void recordRelease(Semaphore semaphore, String name) { @@ -172,6 +172,8 @@ public static class SemaphoreMisuseReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !permitLeaks.isEmpty() || !overReleases.isEmpty() || !unreleasedPermits.isEmpty(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedByteBufferDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedByteBufferDetector.java index ecc962e7..004fc48b 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedByteBufferDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedByteBufferDetector.java @@ -124,9 +124,8 @@ private State resolve(Object buffer) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -168,12 +167,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedCharsetCoderDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedCharsetCoderDetector.java index dfbe3abd..1a7813c9 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedCharsetCoderDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedCharsetCoderDetector.java @@ -107,9 +107,8 @@ private void record(int id, String operation, String kind, Thread thread) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -140,12 +139,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedChecksumDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedChecksumDetector.java index 999a12f0..97741c9e 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedChecksumDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedChecksumDetector.java @@ -84,9 +84,8 @@ public void recordAccess(Checksum checksum, String operation, Thread thread) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -115,12 +114,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedCollectionDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedCollectionDetector.java index c8b0fea5..01abbb0f 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedCollectionDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedCollectionDetector.java @@ -122,6 +122,8 @@ private CollectionState resolveState(Object collection, String name) { /** * Analyse collection usage and return a report. + * + * @return the findings this detector collected during the run */ public SharedCollectionReport analyze() { SharedCollectionReport report = new SharedCollectionReport(); @@ -165,7 +167,11 @@ public static class SharedCollectionReport { final java.util.List mixedAccessViolations = new java.util.ArrayList<>(); final Map collectionActivity = new ConcurrentHashMap<>(); - /** Returns {@code true} when any concurrent-access violations were detected. */ + /** + * Returns {@code true} when any concurrent-access violations were detected. + * + * @return {@code true} when this detector recorded something worth reporting + */ public boolean hasIssues() { return !concurrentWriteViolations.isEmpty() || !mixedAccessViolations.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedDecimalFormatDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedDecimalFormatDetector.java index 37bb8271..4834881b 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedDecimalFormatDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedDecimalFormatDetector.java @@ -52,7 +52,9 @@ public void recordAccess(Object format, String name, Thread thread) { s.accessingThreadNames.add(thread.getName()); } - /** {@return report of DecimalFormat/NumberFormat instances accessed from multiple threads} */ + /** + * {@return report of DecimalFormat/NumberFormat instances accessed from multiple threads} + */ public SharedDecimalFormatReport analyze() { SharedDecimalFormatReport r = new SharedDecimalFormatReport(); for (FormatState s : formats.values()) { @@ -70,7 +72,9 @@ public SharedDecimalFormatReport analyze() { public static class SharedDecimalFormatReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedDeflaterDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedDeflaterDetector.java index 121de0b4..47bdb28c 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedDeflaterDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedDeflaterDetector.java @@ -101,9 +101,8 @@ private void record(int id, String name, String kind, Thread thread) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -133,12 +132,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedFormatterDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedFormatterDetector.java index e26d4a6f..6a6bbe45 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedFormatterDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedFormatterDetector.java @@ -50,7 +50,9 @@ public void recordAccess(Object formatter, String name, Thread thread) { s.accessingThreadNames.add(thread.getName()); } - /** {@return report of formatters accessed from multiple threads} */ + /** + * {@return report of formatters accessed from multiple threads} + */ public SharedFormatterReport analyze() { SharedFormatterReport r = new SharedFormatterReport(); for (FormatterState s : formatters.values()) { @@ -68,7 +70,9 @@ public SharedFormatterReport analyze() { public static class SharedFormatterReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedIteratorDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedIteratorDetector.java index 292e0c56..0985c526 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedIteratorDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedIteratorDetector.java @@ -115,9 +115,8 @@ private static String kindOf(Object iterator) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -149,12 +148,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedJsonMapperReconfigDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedJsonMapperReconfigDetector.java index d5b58a65..1f3687ca 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedJsonMapperReconfigDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedJsonMapperReconfigDetector.java @@ -137,9 +137,8 @@ private State stateFor(Object mapper) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -180,12 +179,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedKdfDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedKdfDetector.java index efbd389c..7d31ff98 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedKdfDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedKdfDetector.java @@ -91,9 +91,8 @@ public void recordAccess(Object kdf, String algorithm, String operation, Thread /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -125,12 +124,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedMatcherDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedMatcherDetector.java index 5ffda789..8ec3f1ed 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedMatcherDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedMatcherDetector.java @@ -51,7 +51,9 @@ public void recordAccess(Object matcher, String name, Thread thread) { s.accessingThreadNames.add(thread.getName()); } - /** {@return report of Matchers accessed from multiple threads} */ + /** + * {@return report of Matchers accessed from multiple threads} + */ public SharedMatcherReport analyze() { SharedMatcherReport r = new SharedMatcherReport(); for (MatcherState s : matchers.values()) { @@ -70,7 +72,9 @@ public SharedMatcherReport analyze() { public static class SharedMatcherReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedMessageDigestDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedMessageDigestDetector.java index 0f3c0827..b536c927 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedMessageDigestDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedMessageDigestDetector.java @@ -96,7 +96,9 @@ public void recordAccess(Object digest, String name, Thread thread) { SiteCapture.capture().ifPresent(s.accessSites::add); } - /** {@return report of JCA instances accessed from multiple threads} */ + /** + * {@return report of JCA instances accessed from multiple threads} + */ public SharedMessageDigestReport analyze() { SharedMessageDigestReport r = new SharedMessageDigestReport(); for (DigestState s : digests.values()) { @@ -160,14 +162,16 @@ public SharedMessageDigestReport analyze() { /** Report produced by {@link #analyze()}. */ public static class SharedMessageDigestReport { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The violated types. */ + /** Algorithm names whose shared instance was used from more than one thread. */ public final Set violatedTypes = new LinkedHashSet<>(); /** Structured mirror of {@link #violations} for {@link se.deversity.asynctest.report.Formatter}s. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedRandomDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedRandomDetector.java index 5202684f..13c886d6 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedRandomDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedRandomDetector.java @@ -156,6 +156,8 @@ public static class SharedRandomReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !sharedRandoms.isEmpty() || !highContention.isEmpty(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedSecureRandomDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedSecureRandomDetector.java index 9324e4ce..4675e0bb 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedSecureRandomDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedSecureRandomDetector.java @@ -101,9 +101,8 @@ public void recordAccess(SecureRandom random, String name, Thread thread) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -138,12 +137,14 @@ private static String safeString(java.util.concurrent.Callable c) { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedStatefulCryptoDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedStatefulCryptoDetector.java index 69ce477c..cccba9d4 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedStatefulCryptoDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedStatefulCryptoDetector.java @@ -129,9 +129,8 @@ private void record(int id, String name, String kind, Class type, String algo /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -167,12 +166,14 @@ private static String safeString(java.util.concurrent.Callable c) { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedTimeZoneDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedTimeZoneDetector.java index e71a4a16..e1924646 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedTimeZoneDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedTimeZoneDetector.java @@ -55,7 +55,9 @@ public void recordMutation(Object timeZone, String operation, Thread thread) { s.mutatingThreadNames.add(thread.getName()); } - /** {@return report of TimeZone instances mutated from multiple threads} */ + /** + * {@return report of TimeZone instances mutated from multiple threads} + */ public SharedTimeZoneReport analyze() { SharedTimeZoneReport r = new SharedTimeZoneReport(); for (TzState s : timezones.values()) { @@ -75,7 +77,9 @@ public SharedTimeZoneReport analyze() { public static class SharedTimeZoneReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedXmlParserDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedXmlParserDetector.java index c1a2d8a8..0f22b593 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedXmlParserDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SharedXmlParserDetector.java @@ -61,7 +61,9 @@ public void recordAccess(Object parser, String parserType, Thread thread) { s.accessingThreadNames.add(thread.getName()); } - /** {@return report of XML parser instances accessed from multiple threads} */ + /** + * {@return report of XML parser instances accessed from multiple threads} + */ public SharedXmlParserReport analyze() { SharedXmlParserReport r = new SharedXmlParserReport(); for (ParserState s : parsers.values()) { @@ -81,7 +83,9 @@ public SharedXmlParserReport analyze() { public static class SharedXmlParserReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SimpleDateFormatDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SimpleDateFormatDetector.java index fb488ff0..534f1ffc 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SimpleDateFormatDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SimpleDateFormatDetector.java @@ -200,6 +200,8 @@ public static class SimpleDateFormatReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !sharedFormatters.isEmpty() || !formattingErrors.isEmpty(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SiteCapture.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SiteCapture.java index ef2ec8c2..b7e43320 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SiteCapture.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SiteCapture.java @@ -67,6 +67,8 @@ private SiteCapture() {} /** * Returns the first stack frame outside the framework / JDK reflection / * JUnit, or {@link Optional#empty()} if none could be identified (rare). + * + * @return the first stack frame in user code, or empty when none could be attributed */ public static Optional capture() { return WALKER.walk(stream -> stream @@ -115,7 +117,11 @@ static Site of(StackFrame f) { f.getLineNumber()); } - /** Human-readable {@code Class.method(File.java:42)} form. */ + /** + * Human-readable {@code Class.method(File.java:42)} form. + * + * @return the site rendered as {@code Class.method(File:line)} for the report + */ public String render() { String shortClass = className.contains(".") ? className.substring(className.lastIndexOf('.') + 1) diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SleepInLockDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SleepInLockDetector.java index 26dc02c4..6043ef33 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SleepInLockDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SleepInLockDetector.java @@ -191,7 +191,6 @@ public void clear() { /** * Disable. */ - public void disable() { this.enabled = false; } @@ -200,15 +199,15 @@ public void disable() { * Immutable snapshot of a sleep-in-lock event. */ public static class SleepInLockEventSnapshot { - /** The lock name. */ + /** Label identifying the lock that was held while sleeping. */ public final @Nullable String lockName; - /** The thread name. */ + /** Label identifying the sleeping thread in the report. */ public final String threadName; - /** The sleep duration. */ + /** How long the thread slept while holding the lock, in nanoseconds. */ public final long sleepDuration; - /** The stack trace. */ + /** Where the sleep happened. */ public final StackTraceElement[] stackTrace; - /** The lock type. */ + /** Whether the lock held was {@code synchronized} or a {@code ReentrantLock}. */ public final @Nullable String lockType; SleepInLockEventSnapshot(@Nullable String lockName, String threadName, @@ -234,12 +233,16 @@ public static class SleepInLockReport { this.totalCount = totalCount; } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !events.isEmpty(); } - /** {@return the events} */ + /** + * {@return the events} + */ public List getEvents() { return List.copyOf(events); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SpuriousWakeupDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SpuriousWakeupDetector.java index 3f813e3c..b0eb016c 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SpuriousWakeupDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SpuriousWakeupDetector.java @@ -36,6 +36,11 @@ private static final class State { /** * Record a wait/await operation. + * + * @param monitor the object being used as a monitor, tracked by identity + * @param monitorName a label identifying the monitor in the report + * @param insideLoop the {@code insideLoop} flag + * @param thread the thread performing the operation */ public void recordWait(Object monitor, String monitorName, boolean insideLoop, Thread thread) { if (monitor == null || thread == null) return; @@ -50,9 +55,8 @@ public void recordWait(Object monitor, String monitorName, boolean insideLoop, T /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : monitors.values()) { @@ -79,12 +83,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StableValueMisuseDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StableValueMisuseDetector.java index 42b8205a..59764545 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StableValueMisuseDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StableValueMisuseDetector.java @@ -239,24 +239,38 @@ public static class StableValueMisuseReport { this.totalSets = totalSets; } - /** {@return true if any correctness-affecting StableValue misuse was detected} */ + /** + * {@return true if any correctness-affecting StableValue misuse was detected} + */ public boolean hasIssues() { return !readBeforeSetIssues.isEmpty() || !doubleSetIssues.isEmpty() || !reentrantIssues.isEmpty(); } - /** {@return the read before set issues} */ + /** + * {@return the read before set issues} + */ public List getReadBeforeSetIssues() { return Collections.unmodifiableList(readBeforeSetIssues); } - /** {@return the double set issues} */ + /** + * {@return the double set issues} + */ public List getDoubleSetIssues() { return Collections.unmodifiableList(doubleSetIssues); } - /** {@return the reentrant issues} */ + /** + * {@return the reentrant issues} + */ public List getReentrantIssues() { return Collections.unmodifiableList(reentrantIssues); } - /** {@return the contention warnings} */ + /** + * {@return the contention warnings} + */ public List getContentionWarnings() { return Collections.unmodifiableList(contentionWarnings); } - /** {@return the total reads} */ + /** + * {@return the total reads} + */ public int getTotalReads() { return totalReads; } - /** {@return the total sets} */ + /** + * {@return the total sets} + */ public int getTotalSets() { return totalSets; } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StampedLockDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StampedLockDetector.java index 74963447..0e8e109a 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StampedLockDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StampedLockDetector.java @@ -22,6 +22,9 @@ public class StampedLockDetector { /** * Register a StampedLock for monitoring. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param name a label identifying the lock in the report */ public void registerLock(StampedLock lock, String name) { lockRegistry.put(lock, new LockInfo(name)); @@ -29,6 +32,10 @@ public void registerLock(StampedLock lock, String name) { /** * Record an optimistic read stamp acquisition. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param lockName a label identifying the lock in the report + * @param stamp the stamp returned by the {@code StampedLock} operation */ public void recordOptimisticRead(StampedLock lock, String lockName, long stamp) { LockInfo info = lockRegistry.get(lock); @@ -39,6 +46,11 @@ public void recordOptimisticRead(StampedLock lock, String lockName, long stamp) /** * Record validation of optimistic read. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param lockName a label identifying the lock in the report + * @param stamp the stamp returned by the {@code StampedLock} operation + * @param validated the {@code validated} flag */ public void recordOptimisticValidation(StampedLock lock, String lockName, long stamp, boolean validated) { if (!validated) { @@ -48,6 +60,10 @@ public void recordOptimisticValidation(StampedLock lock, String lockName, long s /** * Record a read lock acquisition. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param lockName a label identifying the lock in the report + * @param stamp the stamp returned by the {@code StampedLock} operation */ public void recordReadLock(StampedLock lock, String lockName, long stamp) { LockInfo info = lockRegistry.get(lock); @@ -58,6 +74,10 @@ public void recordReadLock(StampedLock lock, String lockName, long stamp) { /** * Record a write lock acquisition. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param lockName a label identifying the lock in the report + * @param stamp the stamp returned by the {@code StampedLock} operation */ public void recordWriteLock(StampedLock lock, String lockName, long stamp) { LockInfo info = lockRegistry.get(lock); @@ -68,6 +88,10 @@ public void recordWriteLock(StampedLock lock, String lockName, long stamp) { /** * Record a lock release. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param lockName a label identifying the lock in the report + * @param stamp the stamp returned by the {@code StampedLock} operation */ public void recordUnlock(StampedLock lock, String lockName, long stamp) { LockInfo info = lockRegistry.get(lock); @@ -78,6 +102,9 @@ public void recordUnlock(StampedLock lock, String lockName, long stamp) { /** * Record a stamp that was not released. + * + * @param lockName a label identifying the lock in the report + * @param stamp the stamp returned by the {@code StampedLock} operation */ public void recordStampNotReleased(String lockName, long stamp) { stampNotReleased.add(lockName + " (stamp: " + stamp + ")"); @@ -85,6 +112,8 @@ public void recordStampNotReleased(String lockName, long stamp) { /** * Analyze StampedLock usage and return report. + * + * @return the findings this detector collected during the run */ public StampedLockReport analyze() { return new StampedLockReport( @@ -99,7 +128,12 @@ public StampedLockReport analyze() { public static class StampedLockReport { private final Set unvalidatedOptimisticReads; private final Set stampNotReleased; - + /** + * Creates a StampedLockReport. + * + * @param unvalidatedOptimisticReads the optimistic reads whose stamp was never validated + * @param stampNotReleased the stamps acquired but never released + */ public StampedLockReport( Set unvalidatedOptimisticReads, Set stampNotReleased @@ -108,7 +142,9 @@ public StampedLockReport( this.stampNotReleased = Collections.unmodifiableSet(new HashSet<>(stampNotReleased)); } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !unvalidatedOptimisticReads.isEmpty() || !stampNotReleased.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StatefulLambdaDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StatefulLambdaDetector.java index 6f938573..a1d8fd6b 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StatefulLambdaDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StatefulLambdaDetector.java @@ -79,7 +79,9 @@ public void recordCapturedMutation(Object lambda, String capturedName, Thread th s.mutationEvents.add(thread.getName() + " → " + label); } - /** {@return report of lambdas with concurrent captured-state mutations} */ + /** + * {@return report of lambdas with concurrent captured-state mutations} + */ public StatefulLambdaReport analyze() { StatefulLambdaReport r = new StatefulLambdaReport(); for (LambdaState s : lambdas.values()) { @@ -98,7 +100,9 @@ public StatefulLambdaReport analyze() { public static class StatefulLambdaReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StreamClosingDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StreamClosingDetector.java index 4c552667..88573654 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StreamClosingDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StreamClosingDetector.java @@ -181,6 +181,8 @@ public static class StreamClosingReport { /** * Check if any issues were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !unclosedStreams.isEmpty() || diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StringBuilderDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StringBuilderDetector.java index 5dc15915..380d2629 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StringBuilderDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StringBuilderDetector.java @@ -161,6 +161,8 @@ private BuilderState resolve(StringBuilder builder, String name) { /** * Analyse StringBuilder usage and return a report. + * + * @return the findings this detector collected during the run */ public StringBuilderReport analyze() { StringBuilderReport report = new StringBuilderReport(); @@ -210,7 +212,11 @@ public static class StringBuilderReport { final java.util.List builderErrors = new java.util.ArrayList<>(); final Map builderActivity = new ConcurrentHashMap<>(); - /** Returns {@code true} when shared-mutation or errors were detected. */ + /** + * Returns {@code true} when shared-mutation or errors were detected. + * + * @return {@code true} when this detector recorded something worth reporting + */ public boolean hasIssues() { return !sharedBuilderViolations.isEmpty() || !builderErrors.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StructuredConcurrencyMisuseDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StructuredConcurrencyMisuseDetector.java index b770acb1..5aed5935 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StructuredConcurrencyMisuseDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StructuredConcurrencyMisuseDetector.java @@ -211,7 +211,9 @@ public static class StructuredConcurrencyReport { this.maxNestingDepth = maxNestingDepth; } - /** {@return true if any structured concurrency issues were detected} */ + /** + * {@return true if any structured concurrency issues were detected} + */ public boolean hasIssues() { return !unclosedScopes.isEmpty() || !closedWithoutJoin.isEmpty() @@ -219,15 +221,25 @@ public boolean hasIssues() { || !emptyScopes.isEmpty(); } - /** {@return the unclosed scopes} */ + /** + * {@return the unclosed scopes} + */ public List getUnclosedScopes() { return Collections.unmodifiableList(unclosedScopes); } - /** {@return the closed without join} */ + /** + * {@return the closed without join} + */ public List getClosedWithoutJoin() { return Collections.unmodifiableList(closedWithoutJoin); } - /** {@return the result accessed before join} */ + /** + * {@return the result accessed before join} + */ public List getResultAccessedBeforeJoin() { return Collections.unmodifiableList(resultAccessedBeforeJoin); } - /** {@return the empty scopes} */ + /** + * {@return the empty scopes} + */ public List getEmptyScopes() { return Collections.unmodifiableList(emptyScopes); } - /** {@return the max nesting depth} */ + /** + * {@return the max nesting depth} + */ public int getMaxNestingDepth() { return maxNestingDepth; } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StructuredTaskScopeMisuseDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StructuredTaskScopeMisuseDetector.java index 678ec18f..aaa8bef8 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StructuredTaskScopeMisuseDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/StructuredTaskScopeMisuseDetector.java @@ -108,6 +108,9 @@ private static final class ScopeState { /** * Record that a {@code StructuredTaskScope.open(...)} returned a new scope, * confined to the opening (owner) thread. + * + * @param scopeId correlates the calls belonging to one scope across its lifecycle + * @param owner the thread currently holding it */ public void recordScopeOpened(String scopeId, Thread owner) { if (scopeId == null || owner == null) return; @@ -118,6 +121,10 @@ public void recordScopeOpened(String scopeId, Thread owner) { /** * Record a {@code scope.fork(task)} call. Flags fork-after-join and * owner-confinement violations. + * + * @param scopeId correlates the calls belonging to one scope across its lifecycle + * @param subtaskId correlates the calls belonging to one subtask + * @param thread the thread performing the operation */ public void recordFork(String scopeId, String subtaskId, Thread thread) { if (scopeId == null || subtaskId == null || thread == null) return; @@ -147,6 +154,9 @@ public void recordFork(String scopeId, String subtaskId, Thread thread) { /** * Record a {@code scope.join()} call. Flags owner-confinement violations and * marks the scope as joined. + * + * @param scopeId correlates the calls belonging to one scope across its lifecycle + * @param thread the thread performing the operation */ public void recordJoin(String scopeId, Thread thread) { if (scopeId == null || thread == null) return; @@ -168,6 +178,10 @@ public void recordJoin(String scopeId, Thread thread) { * Record a {@code Subtask.get()} call. Flags reads that happen before the * scope has been joined, and reads that happen after {@code join()} timed out * (the subtask state is not {@code SUCCESS}, so {@code get()} throws). + * + * @param scopeId correlates the calls belonging to one scope across its lifecycle + * @param subtaskId correlates the calls belonging to one subtask + * @param thread the thread performing the operation */ public void recordResultRead(String scopeId, String subtaskId, Thread thread) { if (scopeId == null || subtaskId == null || thread == null) return; @@ -199,6 +213,9 @@ public void recordResultRead(String scopeId, String subtaskId, Thread thread) { * cancelled. Marks the scope as no longer accepting result reads. * * @since 1.7.0 + * + * @param scopeId correlates the calls belonging to one scope across its lifecycle + * @param thread the thread performing the operation */ public void recordJoinTimeout(String scopeId, Thread thread) { if (scopeId == null || thread == null) return; @@ -224,6 +241,9 @@ public void recordJoinTimeout(String scopeId, Thread thread) { * be half-applied, and the fallback must not depend on their state. * * @since 1.7.0 + * + * @param scopeId correlates the calls belonging to one scope across its lifecycle + * @param thread the thread performing the operation */ public void recordTimeoutSwallowed(String scopeId, Thread thread) { if (scopeId == null || thread == null) return; @@ -247,6 +267,9 @@ public void recordTimeoutSwallowed(String scopeId, Thread thread) { /** * Record that the scope was closed (the try-with-resources block ended). * Flags a scope that forked subtasks but was never joined. + * + * @param scopeId correlates the calls belonging to one scope across its lifecycle + * @param thread the thread performing the operation */ public void recordScopeClosed(String scopeId, Thread thread) { if (scopeId == null || thread == null) return; @@ -313,7 +336,9 @@ public static class StructuredTaskScopeMisuseReport { this.totalForks = totalForks; } - /** {@return true if any StructuredTaskScope misuse was detected} */ + /** + * {@return true if any StructuredTaskScope misuse was detected} + */ public boolean hasIssues() { return !forkAfterJoinIssues.isEmpty() || !resultBeforeJoinIssues.isEmpty() @@ -322,21 +347,45 @@ public boolean hasIssues() { || !resultAfterTimeoutIssues.isEmpty(); } - /** {@return the fork after join issues} */ + /** + * {@return the fork after join issues} + */ public List getForkAfterJoinIssues() { return Collections.unmodifiableList(forkAfterJoinIssues); } - /** {@return the result before join issues} */ + /** + * {@return the result before join issues} + */ public List getResultBeforeJoinIssues() { return Collections.unmodifiableList(resultBeforeJoinIssues); } - /** {@return the confinement issues} */ + /** + * {@return the confinement issues} + */ public List getConfinementIssues() { return Collections.unmodifiableList(confinementIssues); } - /** {@return the missing join issues} */ + /** + * {@return the missing join issues} + */ public List getMissingJoinIssues() { return Collections.unmodifiableList(missingJoinIssues); } - /** @since 1.7.0 */ + /** + * Get result after timeout issues. + * + * @since 1.7.0 + * + * @return the recorded cases where a subtask result was read after the scope timed out + */ public List getResultAfterTimeoutIssues() { return Collections.unmodifiableList(resultAfterTimeoutIssues); } - /** @since 1.7.0 */ + /** + * Get timeout swallowed warnings. + * + * @since 1.7.0 + * + * @return the recorded cases where a scope timeout was caught and discarded + */ public List getTimeoutSwallowedWarnings() { return Collections.unmodifiableList(timeoutSwallowedWarnings); } - /** {@return the total scopes} */ + /** + * {@return the total scopes} + */ public int getTotalScopes() { return totalScopes; } - /** {@return the total forks} */ + /** + * {@return the total forks} + */ public int getTotalForks() { return totalForks; } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizedCollectionIterationDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizedCollectionIterationDetector.java index 9b4f7974..5356b03d 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizedCollectionIterationDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizedCollectionIterationDetector.java @@ -45,7 +45,12 @@ private static class WrapperInfo { private final Map wrappers = new ConcurrentHashMap<>(); - /** Register a synchronized wrapper created by {@code Collections.synchronized*(collection)}. */ + /** + * Register a synchronized wrapper created by {@code Collections.synchronized*(collection)}. + * + * @param wrapper the wrapper object being recorded, tracked by identity + * @param name a label identifying the wrapper in the report + */ public void recordWrapperCreated(Object wrapper, String name) { if (wrapper == null) return; String label = name != null ? name : "collection@" + System.identityHashCode(wrapper); @@ -70,7 +75,9 @@ public void recordIterationStarted(Object wrapper, Thread thread, boolean holdin thread.getName(), info.name, info.name)); } - /** {@return report of unsafe iterations} */ + /** + * {@return report of unsafe iterations} + */ public SynchronizedCollectionIterationReport analyze() { SynchronizedCollectionIterationReport r = new SynchronizedCollectionIterationReport(); for (WrapperInfo w : wrappers.values()) { @@ -88,7 +95,9 @@ public static class SynchronizedCollectionIterationReport { final List violations = new ArrayList<>(); final List details = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizedNonFinalDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizedNonFinalDetector.java index fc37cdab..a042b107 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizedNonFinalDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizedNonFinalDetector.java @@ -77,6 +77,8 @@ public void recordLockObject(Object lockObject, String fieldId, Class ownerCl /** * Analyses recorded lock objects and returns a report of slots where the * monitor reference changed across invocations. + * + * @return the findings this detector collected during the run */ public SynchronizedNonFinalReport analyze() { SynchronizedNonFinalReport report = new SynchronizedNonFinalReport(); @@ -101,7 +103,11 @@ public static class SynchronizedNonFinalReport { final List violations = new ArrayList<>(); - /** Returns {@code true} when any reassignable-lock violation was detected. */ + /** + * Returns {@code true} when any reassignable-lock violation was detected. + * + * @return {@code true} when this detector recorded something worth reporting + */ public boolean hasIssues() { return !violations.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizedOnLiteralDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizedOnLiteralDetector.java index fd2a580c..989ebc27 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizedOnLiteralDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizedOnLiteralDetector.java @@ -78,7 +78,9 @@ public void recordMonitorAcquired(Object monitor, Thread thread, String context) return null; } - /** {@return report of synchronized-on-literal usages} */ + /** + * {@return report of synchronized-on-literal usages} + */ public SynchronizedOnLiteralReport analyze() { SynchronizedOnLiteralReport r = new SynchronizedOnLiteralReport(); for (LiteralUsage u : literals.values()) { @@ -95,7 +97,9 @@ public SynchronizedOnLiteralReport analyze() { public static class SynchronizedOnLiteralReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizerMonitor.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizerMonitor.java index 1e423a30..f142d056 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizerMonitor.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SynchronizerMonitor.java @@ -38,6 +38,9 @@ private static class BarrierState { /** * Register a synchronizer for monitoring. + * + * @param synchronizer the synchronizer being recorded, tracked by identity + * @param expectedParties the number of parties expected to arrive */ public void registerSynchronizer(Object synchronizer, int expectedParties) { if (!enabled) return; @@ -51,6 +54,8 @@ public void registerSynchronizer(Object synchronizer, int expectedParties) { /** * Record thread arriving at barrier. + * + * @param synchronizer the synchronizer being recorded, tracked by identity */ public void recordBarrierArrival(Object synchronizer) { if (!enabled) return; @@ -68,6 +73,8 @@ public void recordBarrierArrival(Object synchronizer) { /** * Record thread advancing past barrier. + * + * @param synchronizer the synchronizer being recorded, tracked by identity */ public void recordBarrierAdvance(Object synchronizer) { if (!enabled) return; @@ -82,6 +89,8 @@ public void recordBarrierAdvance(Object synchronizer) { /** * Record barrier reset. + * + * @param synchronizer the synchronizer being recorded, tracked by identity */ public void recordBarrierReset(Object synchronizer) { if (!enabled) return; @@ -97,6 +106,8 @@ public void recordBarrierReset(Object synchronizer) { /** * Analyze synchronizer behavior. + * + * @return the findings this detector collected during the run */ public SynchronizerReport analyzeSynchronizers() { SynchronizerReport report = new SynchronizerReport(); @@ -126,6 +137,8 @@ public SynchronizerReport analyzeSynchronizers() { /** * Standardized alias for {@link #analyzeSynchronizers()}. + * + * @return the findings this detector collected during the run */ public SynchronizerReport analyze() { return analyzeSynchronizers(); @@ -133,32 +146,31 @@ public SynchronizerReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { synchronizers.clear(); } /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class SynchronizerReport { - /** The incomplete barriers. */ + /** Barriers that never had all their parties arrive. */ public final Set incompleteBarriers = new HashSet<>(); - /** The duplicate arrivals. */ + /** Parties that arrived at a synchronizer more than once in a cycle. */ public final Set duplicateArrivals = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !incompleteBarriers.isEmpty() || !duplicateArrivals.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SystemPropertyMutationDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SystemPropertyMutationDetector.java index d8e6de12..96ff8dab 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SystemPropertyMutationDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/SystemPropertyMutationDetector.java @@ -72,7 +72,9 @@ public void recordClear(String key, Thread thread) { events.add(new MutationEvent(key, null, thread.threadId(), thread.getName(), "clear")); } - /** {@return report of concurrent system property mutations} */ + /** + * {@return report of concurrent system property mutations} + */ public SystemPropertyMutationReport analyze() { SystemPropertyMutationReport r = new SystemPropertyMutationReport(); @@ -118,7 +120,9 @@ public static class SystemPropertyMutationReport { final List violations = new ArrayList<>(); final List singleThreadMutations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThisEscapeDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThisEscapeDetector.java index 7a9f7698..97ff8647 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThisEscapeDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThisEscapeDetector.java @@ -125,9 +125,8 @@ public void recordConstructionComplete(Object instance) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -160,12 +159,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadFactoryDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadFactoryDetector.java index 9eb4866a..38a03820 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadFactoryDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadFactoryDetector.java @@ -23,6 +23,9 @@ public class ThreadFactoryDetector { /** * Register a ThreadFactory for monitoring. + * + * @param factory the thread factory being recorded, tracked by identity + * @param name a label identifying the factory in the report */ public void registerFactory(ThreadFactory factory, String name) { factoryRegistry.put(factory, new FactoryInfo(name)); @@ -30,6 +33,10 @@ public void registerFactory(ThreadFactory factory, String name) { /** * Record a thread created by factory. + * + * @param factory the thread factory being recorded, tracked by identity + * @param factoryName a label identifying the thread factory in the report + * @param thread the thread performing the operation */ public void recordThreadCreated(ThreadFactory factory, String factoryName, Thread thread) { FactoryInfo info = factoryRegistry.get(factory); @@ -55,6 +62,8 @@ public void recordThreadCreated(ThreadFactory factory, String factoryName, Threa /** * Analyze ThreadFactory usage and return report. + * + * @return the findings this detector collected during the run */ public ThreadFactoryReport analyze() { return new ThreadFactoryReport( @@ -71,7 +80,13 @@ public static class ThreadFactoryReport { private final Set missingExceptionHandler; private final Set nonDaemonThreads; private final Set unnamedThreads; - + /** + * Creates a ThreadFactoryReport. + * + * @param missingExceptionHandler the threads created without an uncaught-exception handler + * @param nonDaemonThreads the non-daemon threads created, which can keep the JVM alive + * @param unnamedThreads the threads created without a name, which are hard to attribute in a dump + */ public ThreadFactoryReport( Set missingExceptionHandler, Set nonDaemonThreads, @@ -82,7 +97,9 @@ public ThreadFactoryReport( this.unnamedThreads = Collections.unmodifiableSet(new HashSet<>(unnamedThreads)); } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !missingExceptionHandler.isEmpty() || !nonDaemonThreads.isEmpty() diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLeakDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLeakDetector.java index 78449a03..305e2689 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLeakDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLeakDetector.java @@ -195,7 +195,6 @@ public void clear() { /** * Disable. */ - public void disable() { this.enabled = false; } @@ -204,14 +203,14 @@ public void disable() { * A thread leak event. */ public static class ThreadLeakEvent { - /** The thread name. */ + /** Label identifying the sleeping thread in the report. */ public final String threadName; - /** The thread. */ + /** The thread being tracked; cleared once it terminates. */ public final @Nullable Thread thread; - /** The start time. */ + /** When the thread was started, in nanoseconds. */ public final long startTime; public final StackTraceElement @Nullable [] creationStack; - /** The reason. */ + /** Why the thread was flagged, shown in the report. */ public final String reason; ThreadLeakEvent(String threadName, @Nullable Thread thread, long startTime, @@ -243,12 +242,16 @@ public static class ThreadLeakReport { this.autoMode = autoMode; } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !leaks.isEmpty(); } - /** {@return the leaks} */ + /** + * {@return the leaks} + */ public List getLeaks() { return List.copyOf(leaks); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLocalContaminationDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLocalContaminationDetector.java index c42f1b96..ae08b28a 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLocalContaminationDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLocalContaminationDetector.java @@ -39,7 +39,12 @@ private static final class ThreadState { private final Map threadStates = new ConcurrentHashMap<>(); private final List contaminations = new CopyOnWriteArrayList<>(); - /** Call at the start of each task submitted to a thread pool. */ + /** + * Call at the start of each task submitted to a thread pool. + * + * @param thread the thread performing the operation + * @param taskName a label identifying the task in the report + */ public void recordNewTask(Thread thread, String taskName) { if (thread == null) return; ThreadState s = threadStates.computeIfAbsent(thread.threadId(), id -> new ThreadState()); @@ -47,7 +52,13 @@ public void recordNewTask(Thread thread, String taskName) { s.currentTaskName = taskName != null ? taskName : "task-" + s.taskCount; } - /** Call after each {@code ThreadLocal.set()} inside a task. */ + /** + * Call after each {@code ThreadLocal.set()} inside a task. + * + * @param thread the thread performing the operation + * @param tl the thread-local being recorded, tracked by identity + * @param name a label identifying the tl in the report + */ public void recordSet(Thread thread, Object tl, String name) { if (thread == null || tl == null) return; ThreadState s = threadStates.get(thread.threadId()); @@ -61,6 +72,10 @@ public void recordSet(Thread thread, Object tl, String name) { * Call after each {@code ThreadLocal.get()} inside a task. * * @param hasValue {@code true} if the get returned a non-null value + * + * @param thread the thread performing the operation + * @param tl the thread-local being recorded, tracked by identity + * @param name a label identifying the tl in the report */ public void recordGet(Thread thread, Object tl, String name, boolean hasValue) { if (thread == null || tl == null || !hasValue) return; @@ -76,7 +91,9 @@ public void recordGet(Thread thread, Object tl, String name, boolean hasValue) { } } - /** {@return report of cross-task ThreadLocal contaminations} */ + /** + * {@return report of cross-task ThreadLocal contaminations} + */ public ThreadLocalContaminationReport analyze() { ThreadLocalContaminationReport r = new ThreadLocalContaminationReport(); r.contaminations.addAll(contaminations); @@ -87,7 +104,9 @@ public ThreadLocalContaminationReport analyze() { public static class ThreadLocalContaminationReport { final List contaminations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !contaminations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLocalMonitor.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLocalMonitor.java index 88b1f0c0..bbb38a81 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLocalMonitor.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLocalMonitor.java @@ -29,10 +29,9 @@ private static class ThreadLocalState { /** * Records thread local init so it can be analysed at the end of the run. * - * @param threadLocal the thread local - * @param name the name + * @param threadLocal the thread-local being recorded, tracked by identity + * @param name a label identifying the thread local in the report */ - public void recordThreadLocalInit(ThreadLocal threadLocal, String name) { if (!enabled || threadLocal == null) { return; @@ -47,9 +46,8 @@ public void recordThreadLocalInit(ThreadLocal threadLocal, String name) { /** * Records thread local access so it can be analysed at the end of the run. * - * @param threadLocal the thread local + * @param threadLocal the thread-local being recorded, tracked by identity */ - public void recordThreadLocalAccess(ThreadLocal threadLocal) { if (!enabled || threadLocal == null) { return; @@ -62,9 +60,8 @@ public void recordThreadLocalAccess(ThreadLocal threadLocal) { /** * Records thread local cleanup so it can be analysed at the end of the run. * - * @param threadLocal the thread local + * @param threadLocal the thread-local being recorded, tracked by identity */ - public void recordThreadLocalCleanup(ThreadLocal threadLocal) { if (!enabled || threadLocal == null) { return; @@ -84,9 +81,8 @@ private void recordThreadUsage(ThreadLocalState state, long threadId) { /** * Analyses what has been recorded about thread local leaks and builds the report for it. * - * @return the analyze thread local leaks + * @return the findings this detector collected during the run */ - public ThreadLocalReport analyzeThreadLocalLeaks() { ThreadLocalReport report = new ThreadLocalReport(); @@ -122,6 +118,8 @@ public ThreadLocalReport analyzeThreadLocalLeaks() { /** * Standardized alias for {@link #analyzeThreadLocalLeaks()}. + * + * @return the findings this detector collected during the run */ public ThreadLocalReport analyze() { return analyzeThreadLocalLeaks(); @@ -129,7 +127,6 @@ public ThreadLocalReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { threadLocals.clear(); threadLocalsByThread.clear(); @@ -137,27 +134,27 @@ public void reset() { /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class ThreadLocalReport { - /** The uncleaned thread locals. */ + /** Thread-locals never removed before the thread was returned to its pool. */ public final Set uncleanedThreadLocals = new HashSet<>(); - /** The likely leaks. */ + /** Thread-locals still set on pooled threads after the task finished. */ public final Set likelyLeaks = new HashSet<>(); - /** The thread local accumulation. */ + /** Thread-locals whose stored value grew across reused threads. */ public final Set threadLocalAccumulation = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !uncleanedThreadLocals.isEmpty() || !likelyLeaks.isEmpty() diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLocalRandomMisuseDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLocalRandomMisuseDetector.java index 7b9c17a8..6b216c85 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLocalRandomMisuseDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadLocalRandomMisuseDetector.java @@ -105,9 +105,8 @@ public void recordUse(ThreadLocalRandom rng, Thread thread) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -136,12 +135,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadPoolMonitor.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadPoolMonitor.java index 530e9ec1..9383aea8 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadPoolMonitor.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadPoolMonitor.java @@ -45,6 +45,12 @@ private static class PoolState { /** * Register a thread pool for monitoring. + * + * @param executor the executor being recorded, tracked by identity + * @param name a label identifying the executor in the report + * @param coreSize the configured core pool size + * @param maxSize the configured maximum pool size + * @param queueCapacity the configured work-queue capacity */ public void registerPool(Object executor, String name, int coreSize, int maxSize, int queueCapacity) { if (!enabled) return; @@ -55,6 +61,8 @@ public void registerPool(Object executor, String name, int coreSize, int maxSize /** * Record task submission. + * + * @param executor the executor being recorded, tracked by identity */ public void recordTaskSubmitted(Object executor) { if (!enabled) return; @@ -69,6 +77,8 @@ public void recordTaskSubmitted(Object executor) { /** * Record task execution start. + * + * @param executor the executor being recorded, tracked by identity */ public void recordTaskStarted(Object executor) { if (!enabled) return; @@ -83,6 +93,9 @@ public void recordTaskStarted(Object executor) { /** * Record task completion. + * + * @param executor the executor being recorded, tracked by identity + * @param durationMs the duration in milliseconds */ public void recordTaskCompleted(Object executor, long durationMs) { if (!enabled) return; @@ -98,6 +111,9 @@ public void recordTaskCompleted(Object executor, long durationMs) { /** * Record task rejection. + * + * @param executor the executor being recorded, tracked by identity + * @param reason why the event was recorded, shown in the report */ public void recordTaskRejected(Object executor, String reason) { if (!enabled) return; @@ -113,6 +129,8 @@ public void recordTaskRejected(Object executor, String reason) { /** * Analyze pool health. + * + * @return the findings this detector collected during the run */ public ThreadPoolReport analyzePoolHealth() { ThreadPoolReport report = new ThreadPoolReport(); @@ -152,6 +170,8 @@ public ThreadPoolReport analyzePoolHealth() { /** * Standardized alias for {@link #analyzePoolHealth()}. + * + * @return the findings this detector collected during the run */ public ThreadPoolReport analyze() { return analyzePoolHealth(); @@ -159,36 +179,35 @@ public ThreadPoolReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { pools.clear(); } /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class ThreadPoolReport { - /** The pools with rejections. */ + /** Pools that rejected at least one submission. */ public final Set poolsWithRejections = new HashSet<>(); - /** The saturated queues. */ + /** Work queues observed at their capacity. */ public final Set saturatedQueues = new HashSet<>(); - /** The long running tasks. */ + /** Tasks that ran past the reporting threshold. */ public final Set longRunningTasks = new HashSet<>(); - /** The thread starvation. */ + /** Pools where work waited because no worker was free. */ public final Set threadStarvation = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !poolsWithRejections.isEmpty() || !saturatedQueues.isEmpty() || !longRunningTasks.isEmpty() || !threadStarvation.isEmpty(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadStarvationDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadStarvationDetector.java index b7750c3e..a24f8ae2 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadStarvationDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/ThreadStarvationDetector.java @@ -85,7 +85,7 @@ private static class TaskEvent { /** * Register an executor for starvation monitoring. * - * @param executor the executor + * @param executor the executor being recorded, tracked by identity * @param name a descriptive name * @param poolSize the number of threads in the pool */ @@ -99,7 +99,7 @@ public void registerExecutor(ExecutorService executor, String name, int poolSize /** * Record a task being submitted to an executor. * - * @param executor the executor + * @param executor the executor being recorded, tracked by identity * @return the submission timestamp in nanoseconds */ public long recordTaskSubmission(ExecutorService executor) { @@ -119,7 +119,7 @@ public long recordTaskSubmission(ExecutorService executor) { /** * Record a task starting execution. * - * @param executorName the executor name + * @param executorName a label identifying the executor in the report * @param submitTimeNanos the submission time (from recordTaskSubmission) */ public void recordTaskStart(String executorName, long submitTimeNanos) { @@ -162,7 +162,7 @@ public void recordTaskStart(String executorName, long submitTimeNanos) { /** * Record a task completing execution. * - * @param executorName the executor name + * @param executorName a label identifying the executor in the report */ public void recordTaskEnd(String executorName) { if (!enabled) return; @@ -227,13 +227,14 @@ public void clear() { /** * Disable. */ - public void disable() { this.enabled = false; } /** * Set the starvation threshold in milliseconds. + * + * @param thresholdMs the threshold in milliseconds */ public void setStarvationThresholdMs(long thresholdMs) { this.starvationThresholdMs = thresholdMs; @@ -243,13 +244,13 @@ public void setStarvationThresholdMs(long thresholdMs) { * Immutable snapshot of a starvation event. */ public static class StarvationEventSnapshot { - /** The executor name. */ + /** Label identifying the executor in the report. */ public final String executorName; /** The wait time in milliseconds. */ public final long waitTimeMs; /** The execution time in milliseconds. */ public final long executionTimeMs; - /** The thread name. */ + /** Label identifying the sleeping thread in the report. */ public final String threadName; StarvationEventSnapshot(String executorName, long waitTimeMs, @@ -278,12 +279,16 @@ public static class ThreadStarvationReport { this.maxWaitTimeMs = maxWaitTimeMs; } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !events.isEmpty(); } - /** {@return the events} */ + /** + * {@return the events} + */ public List getEvents() { return List.copyOf(events); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/TimerDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/TimerDetector.java index bdb89580..7ea0162d 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/TimerDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/TimerDetector.java @@ -161,6 +161,8 @@ private TimerState resolve(java.util.Timer timer, String name) { /** * Analyse Timer usage and return a report. + * + * @return the findings this detector collected during the run */ public TimerReport analyze() { TimerReport report = new TimerReport(); @@ -216,7 +218,11 @@ public static class TimerReport { final java.util.List usageWarnings = new java.util.ArrayList<>(); final Map timerActivity = new ConcurrentHashMap<>(); - /** Returns {@code true} when timer thread failures or long-running tasks were detected. */ + /** + * Returns {@code true} when timer thread failures or long-running tasks were detected. + * + * @return {@code true} when this detector recorded something worth reporting + */ public boolean hasIssues() { return !timerThreadFailures.isEmpty() || !longRunningTaskWarnings.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/TryLockMisuseDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/TryLockMisuseDetector.java index 7df20e41..6f13dd0d 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/TryLockMisuseDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/TryLockMisuseDetector.java @@ -37,6 +37,11 @@ private static final class State { /** * Record the result of a tryLock() call. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param lockName a label identifying the lock in the report + * @param acquired the {@code acquired} flag + * @param thread the thread performing the operation */ public void recordTryLockResult(Object lock, String lockName, boolean acquired, Thread thread) { if (lock == null || thread == null) return; @@ -46,6 +51,10 @@ public void recordTryLockResult(Object lock, String lockName, boolean acquired, /** * Record an unlock() call. + * + * @param lock the lock being recorded, tracked by identity rather than equality + * @param lockName a label identifying the lock in the report + * @param thread the thread performing the operation */ public void recordUnlock(Object lock, String lockName, Thread thread) { if (lock == null || thread == null) return; @@ -65,9 +74,8 @@ public void recordUnlock(Object lock, String lockName, Thread thread) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : violations.values()) { @@ -92,12 +100,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/UnboundedQueueDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/UnboundedQueueDetector.java index 9d8cd2d6..680cde24 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/UnboundedQueueDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/UnboundedQueueDetector.java @@ -92,7 +92,7 @@ public void recordQueueCreation(BlockingQueue queue, String name, int capacit /** * Record an enqueue operation. * - * @param queue the queue + * @param queue the queue being recorded, tracked by identity */ public void recordEnqueue(BlockingQueue queue) { if (!enabled || queue == null) return; @@ -131,7 +131,7 @@ public void recordEnqueue(BlockingQueue queue) { /** * Record a dequeue operation. * - * @param queue the queue + * @param queue the queue being recorded, tracked by identity */ public void recordDequeue(BlockingQueue queue) { if (!enabled || queue == null) return; @@ -198,13 +198,14 @@ public void clear() { /** * Disable. */ - public void disable() { this.enabled = false; } /** * Set the warning threshold for queue size. + * + * @param threshold the value above which this detector reports */ public void setWarningThreshold(int threshold) { this.warningThreshold = threshold; @@ -214,14 +215,14 @@ public void setWarningThreshold(int threshold) { * An unbounded queue event. */ public static class UnboundedQueueEvent { - /** The queue name. */ + /** Label identifying the queue in the report. */ public final String queueName; - /** The description. */ + /** What was observed, in the wording used by the report. */ public final String description; - /** The capacity. */ + /** Declared capacity of the queue, or unbounded when none was given. */ public final int capacity; public final StackTraceElement @Nullable [] creationStack; - /** The fix suggestion. */ + /** The change suggested to the reader of the report. */ public final String fixSuggestion; UnboundedQueueEvent(String queueName, String description, int capacity, @@ -249,12 +250,16 @@ public static class UnboundedQueueReport { this.totalTracked = totalTracked; } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !events.isEmpty(); } - /** {@return the events} */ + /** + * {@return the events} + */ public List getEvents() { return List.copyOf(events); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/UncaughtExceptionHandlerDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/UncaughtExceptionHandlerDetector.java index f63c32e8..4867caf5 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/UncaughtExceptionHandlerDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/UncaughtExceptionHandlerDetector.java @@ -71,7 +71,9 @@ public void recordUncaughtException(Thread thread, Throwable throwable) { if (rec != null) rec.uncaughtException = throwable; } - /** {@return report of threads that threw without a custom UncaughtExceptionHandler} */ + /** + * {@return report of threads that threw without a custom UncaughtExceptionHandler} + */ public UncaughtExceptionHandlerReport analyze() { UncaughtExceptionHandlerReport r = new UncaughtExceptionHandlerReport(); for (ThreadRecord rec : threads.values()) { @@ -89,7 +91,9 @@ public UncaughtExceptionHandlerReport analyze() { public static class UncaughtExceptionHandlerReport { final List violations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/UncommittedChangesDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/UncommittedChangesDetector.java index ef6c9db9..f5c80ed4 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/UncommittedChangesDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/UncommittedChangesDetector.java @@ -154,6 +154,8 @@ private UncommittedChangesReport(GitStatus status) { /** * Check if any untracked or uncommitted changes were detected. + * + * @return {@code true} when this detector recorded something worth reporting */ public boolean hasIssues() { return !uncommittedFiles.isEmpty() || !untrackedFiles.isEmpty() || error != null; diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadCarrierExhaustionDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadCarrierExhaustionDetector.java index f0363d4a..b7d24df3 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadCarrierExhaustionDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadCarrierExhaustionDetector.java @@ -55,7 +55,9 @@ public class VirtualThreadCarrierExhaustionDetector { private final AtomicInteger exhaustionEvents = new AtomicInteger(0); private final List exhaustionDetails = Collections.synchronizedList(new ArrayList<>()); private final Map activeBlocksByThread = new ConcurrentHashMap<>(); - + /** + * Creates a VirtualThreadCarrierExhaustionDetector. + */ public VirtualThreadCarrierExhaustionDetector() { this(availableCarriers()); } @@ -174,18 +176,28 @@ public static class CarrierExhaustionReport { this.carrierCount = carrierCount; } - /** {@return true if carrier exhaustion was reached or approached} */ + /** + * {@return true if carrier exhaustion was reached or approached} + */ public boolean hasIssues() { return exhaustionEventCount > 0; } - /** {@return the exhaustion details} */ + /** + * {@return the exhaustion details} + */ public List getExhaustionDetails() { return Collections.unmodifiableList(exhaustionDetails); } - /** {@return the peak concurrently blocked} */ + /** + * {@return the peak concurrently blocked} + */ public int getPeakConcurrentlyBlocked() { return peakConcurrentlyBlocked; } - /** {@return the exhaustion event count} */ + /** + * {@return the exhaustion event count} + */ public int getExhaustionEventCount() { return exhaustionEventCount; } - /** {@return the carrier count} */ + /** + * {@return the carrier count} + */ public int getCarrierCount() { return carrierCount; } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadContextLeakDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadContextLeakDetector.java index cac16366..5f649ce2 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadContextLeakDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadContextLeakDetector.java @@ -211,20 +211,32 @@ public static class VirtualThreadContextLeakReport { this.totalRemoves = totalRemoves; } - /** {@return true if any context leak issues were detected} */ + /** + * {@return true if any context leak issues were detected} + */ public boolean hasIssues() { return !leaks.isEmpty() || !inheritableInVirtualIssues.isEmpty(); } - /** {@return the leaks} */ + /** + * {@return the leaks} + */ public List getLeaks() { return Collections.unmodifiableList(leaks); } - /** {@return the inheritable in virtual issues} */ + /** + * {@return the inheritable in virtual issues} + */ public List getInheritableInVirtualIssues() { return Collections.unmodifiableList(inheritableInVirtualIssues); } - /** {@return the high count warnings} */ + /** + * {@return the high count warnings} + */ public List getHighCountWarnings() { return Collections.unmodifiableList(highCountWarnings); } - /** {@return the total sets} */ + /** + * {@return the total sets} + */ public int getTotalSets() { return totalSets; } - /** {@return the total removes} */ + /** + * {@return the total removes} + */ public int getTotalRemoves() { return totalRemoves; } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadCpuBoundTaskDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadCpuBoundTaskDetector.java index 13e67247..79f986c7 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadCpuBoundTaskDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadCpuBoundTaskDetector.java @@ -84,11 +84,17 @@ long maxSegmentMs() { private final AtomicInteger totalTasks = new AtomicInteger(0); private final AtomicLong totalDurationMs = new AtomicLong(0); private final AtomicLong maxObservedMs = new AtomicLong(0); - + /** + * Creates a VirtualThreadCpuBoundTaskDetector. + */ public VirtualThreadCpuBoundTaskDetector() { this(DEFAULT_CPU_THRESHOLD_MS); } - + /** + * Creates a VirtualThreadCpuBoundTaskDetector. + * + * @param cpuThresholdMs the cpu threshold in milliseconds + */ public VirtualThreadCpuBoundTaskDetector(long cpuThresholdMs) { this.cpuThresholdMs = cpuThresholdMs; } @@ -217,20 +223,32 @@ public static class CpuBoundTaskReport { this.thresholdMs = thresholdMs; } - /** {@return true if any CPU-bound tasks were detected on virtual threads} */ + /** + * {@return true if any CPU-bound tasks were detected on virtual threads} + */ public boolean hasIssues() { return !violations.isEmpty(); } - /** {@return the violations} */ + /** + * {@return the violations} + */ public List getViolations() { return Collections.unmodifiableList(violations); } - /** {@return the total tasks} */ + /** + * {@return the total tasks} + */ public int getTotalTasks() { return totalTasks; } - /** {@return the average duration in milliseconds} */ + /** + * {@return the average duration in milliseconds} + */ public long getAverageDurationMs() { return averageDurationMs; } - /** {@return the max duration in milliseconds} */ + /** + * {@return the max duration in milliseconds} + */ public long getMaxDurationMs() { return maxDurationMs; } - /** {@return the threshold in milliseconds} */ + /** + * {@return the threshold in milliseconds} + */ public long getThresholdMs() { return thresholdMs; } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadPinningDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadPinningDetector.java index d9cb3790..70c8f6af 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadPinningDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadPinningDetector.java @@ -80,6 +80,9 @@ public enum PinningCause { * Classifies a caller-supplied blocking-operation description. * * @since 1.7.0 + * + * @param blockingOperation the blocking operation that was called, as it should appear in the report + * @return the pinning cause that operation falls under */ public static PinningCause classifyOperation(String blockingOperation) { if (blockingOperation == null) return PinningCause.OTHER; @@ -104,6 +107,10 @@ public static PinningCause classifyOperation(String blockingOperation) { * version (e.g. {@code 21}, {@code 24}, {@code 26}). * * @since 1.7.0 + * + * @param cause what pinned the virtual thread to its carrier + * @param jdkFeatureVersion the JDK feature version to evaluate the finding against + * @return {@code true} when that cause still pins a virtual thread on the given JDK */ public static boolean stillPinsOn(PinningCause cause, int jdkFeatureVersion) { return switch (cause) { @@ -355,6 +362,8 @@ public boolean hasEffectivePinningIssues() { * Returns how many recorded events no longer pin on the running JDK. * * @since 1.7.0 + * + * @return the number of recorded events that no longer pin on the running JDK */ public long getObsoleteEventCount() { return events.stream().filter(PinningEventSnapshot::isObsoleteOnCurrentJdk).count(); @@ -447,6 +456,8 @@ public static class PinningEventSnapshot { * Returns the classified cause of this pinning event. * * @since 1.7.0 + * + * @return what pinned the virtual thread to its carrier */ public PinningCause getCause() { return cause; @@ -458,6 +469,8 @@ public PinningCause getCause() { * waits on JDK 26+). * * @since 1.7.0 + * + * @return {@code true} when this event no longer pins on the running JDK */ public boolean isObsoleteOnCurrentJdk() { return obsoleteOnCurrentJdk; diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadStressConfig.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadStressConfig.java index 58867028..c3d460a9 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadStressConfig.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VirtualThreadStressConfig.java @@ -25,7 +25,7 @@ public enum StressLevel { /** 100,000+ virtual threads - extreme stress (may require -Xmx settings) */ EXTREME(100000); - /** The thread count. */ + /** How many virtual threads the stress run starts. */ public final int threadCount; StressLevel(int threadCount) { @@ -37,7 +37,14 @@ public enum StressLevel { private final boolean detectThreadPinning; private final boolean enableVirtualThreadEvents; private final long timeoutMs; - + /** + * Creates a VirtualThreadStressConfig. + * + * @param stressLevel the stress level to apply + * @param detectThreadPinning the {@code detectThreadPinning} flag + * @param enableVirtualThreadEvents the {@code enableVirtualThreadEvents} flag + * @param timeoutMs the timeout in milliseconds + */ public VirtualThreadStressConfig(StressLevel stressLevel, boolean detectThreadPinning, boolean enableVirtualThreadEvents, @@ -47,33 +54,44 @@ public VirtualThreadStressConfig(StressLevel stressLevel, this.enableVirtualThreadEvents = enableVirtualThreadEvents; this.timeoutMs = timeoutMs; } - /** {@return the builder} */ - + /** + * {@return the builder} + */ public static Builder builder() { return new Builder(); } - /** {@return the stress level} */ + /** + * {@return the stress level} + */ public StressLevel getStressLevel() { return stressLevel; } - /** {@return the thread count} */ + /** + * {@return the thread count} + */ public int getThreadCount() { return stressLevel.threadCount; } - /** {@return whether detect thread pinning} */ + /** + * {@return whether detect thread pinning} + */ public boolean isDetectThreadPinning() { return detectThreadPinning; } - /** {@return whether enable virtual thread events} */ + /** + * {@return whether enable virtual thread events} + */ public boolean isEnableVirtualThreadEvents() { return enableVirtualThreadEvents; } - /** {@return the timeout in milliseconds} */ + /** + * {@return the timeout in milliseconds} + */ public long getTimeoutMs() { return timeoutMs; } @@ -86,10 +104,9 @@ public static class Builder { /** * Stress level. * - * @param level the level - * @return the stress level + * @param level the stress level to apply + * @return this builder */ - public Builder stressLevel(StressLevel level) { this.stressLevel = level; return this; @@ -97,10 +114,9 @@ public Builder stressLevel(StressLevel level) { /** * Detect thread pinning. * - * @param detect the detect - * @return the detect thread pinning + * @param detect {@code true} to enable this detector for the run + * @return this builder */ - public Builder detectThreadPinning(boolean detect) { this.detectThreadPinning = detect; return this; @@ -108,10 +124,9 @@ public Builder detectThreadPinning(boolean detect) { /** * Enable virtual thread events. * - * @param enable the enable - * @return the enable virtual thread events + * @param enable {@code true} to enable this detector for the run + * @return this builder */ - public Builder enableVirtualThreadEvents(boolean enable) { this.enableVirtualThreadEvents = enable; return this; @@ -119,16 +134,16 @@ public Builder enableVirtualThreadEvents(boolean enable) { /** * Timeout in milliseconds. * - * @param timeout the timeout + * @param timeout the timeout supplied by the caller * @return the timeout in milliseconds */ - public Builder timeoutMs(long timeout) { this.timeoutMs = timeout; return this; } - /** {@return the build} */ - + /** + * {@return the build} + */ public VirtualThreadStressConfig build() { return new VirtualThreadStressConfig(stressLevel, detectThreadPinning, enableVirtualThreadEvents, timeoutMs); @@ -137,6 +152,8 @@ public VirtualThreadStressConfig build() { /** * Helper to check if this JVM supports virtual threads (Java 21+). + * + * @return {@code true} when the running JDK provides virtual threads */ public static boolean isVirtualThreadSupported() { try { @@ -150,6 +167,8 @@ public static boolean isVirtualThreadSupported() { /** * Utility to create a virtual thread executor with potential pinning detection. * Returns executor class name if virtual threads are available. + * + * @return the class name of the virtual-thread executor, for reporting */ public static String getVirtualThreadExecutorClass() { if (isVirtualThreadSupported()) { diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VisibilityMonitor.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VisibilityMonitor.java index 390f02ce..b3a70a09 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VisibilityMonitor.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VisibilityMonitor.java @@ -38,6 +38,9 @@ private static final class FieldSnapshot { /** * Record a field access. Call this from test code to track when a field is read/written. * Format: className.fieldName + * + * @param fieldIdentifier the {@code Type.field} the access was on + * @param value the value read or written */ public void recordFieldAccess(String fieldIdentifier, Object value) { if (!enabled) return; @@ -64,6 +67,8 @@ public void markInvocationStart() { /** * Analyze visibility issues. Returns a report of suspected visibility issues. + * + * @return the findings this detector collected during the run */ public VisibilityReport analyzeVisibility() { VisibilityReport report = new VisibilityReport(); @@ -95,6 +100,8 @@ public VisibilityReport analyzeVisibility() { /** * Standardized alias for {@link #analyzeVisibility()}. + * + * @return the findings this detector collected during the run */ public VisibilityReport analyze() { return analyzeVisibility(); @@ -102,7 +109,6 @@ public VisibilityReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { fieldSnapshots.clear(); seenValues.clear(); @@ -111,25 +117,25 @@ public void reset() { /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class VisibilityReport { - /** The suspected fields. */ + /** Fields where two threads observed different values at the same time. */ public final Set suspectedFields = new HashSet<>(); - /** The field value variations. */ + /** Values each thread observed per field, used to spot stale reads. */ public final Map>> fieldValueVariations = new HashMap<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !suspectedFields.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VolatileArrayDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VolatileArrayDetector.java index d4e39bc4..36c30700 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VolatileArrayDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/VolatileArrayDetector.java @@ -28,6 +28,10 @@ public class VolatileArrayDetector { /** * Register a volatile array for monitoring. + * + * @param array the array being recorded, tracked by identity + * @param name a label identifying the array in the report + * @param componentType the component type of the array */ public void registerArray(Object array, String name, Class componentType) { ArrayInfo info = new ArrayInfo(name, array, componentType); @@ -36,6 +40,10 @@ public void registerArray(Object array, String name, Class componentType) { /** * Record a write to an array element. + * + * @param array the array being recorded, tracked by identity + * @param index the array index being accessed + * @param arrayName a label identifying the array in the report */ public void recordElementWrite(Object array, int index, String arrayName) { ArrayInfo info = findArrayInfo(array, arrayName); @@ -61,6 +69,10 @@ public void recordElementWrite(Object array, int index, String arrayName) { /** * Record a read from an array element. + * + * @param array the array being recorded, tracked by identity + * @param index the array index being accessed + * @param arrayName a label identifying the array in the report */ public void recordElementRead(Object array, int index, String arrayName) { ArrayInfo info = findArrayInfo(array, arrayName); @@ -84,6 +96,8 @@ public void recordElementRead(Object array, int index, String arrayName) { /** * Analyze array access patterns and return report. + * + * @return the findings this detector collected during the run */ public VolatileArrayReport analyze() { return new VolatileArrayReport( @@ -96,14 +110,20 @@ public VolatileArrayReport analyze() { */ public static class VolatileArrayReport { private final Set problematicArrays; - + /** + * Creates a VolatileArrayReport. + * + * @param problematicArrays the arrays whose elements were accessed without the ordering the code assumes + */ public VolatileArrayReport( Set problematicArrays ) { this.problematicArrays = Collections.unmodifiableSet(new HashSet<>(problematicArrays)); } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !problematicArrays.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WaitTimeoutDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WaitTimeoutDetector.java index e54b83ab..90c42e39 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WaitTimeoutDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WaitTimeoutDetector.java @@ -34,6 +34,10 @@ public class WaitTimeoutDetector { /** * Register a wait() call without timeout. + * + * @param monitor the object being used as a monitor, tracked by identity + * @param monitorName a label identifying the monitor in the report + * @param threadName a label identifying the thread in the report */ public void recordInfiniteWait(Object monitor, String monitorName, String threadName) { WaitInfo info = new WaitInfo(monitor, monitorName); @@ -44,6 +48,11 @@ public void recordInfiniteWait(Object monitor, String monitorName, String thread /** * Register a wait() call with timeout. + * + * @param monitor the object being used as a monitor, tracked by identity + * @param monitorName a label identifying the monitor in the report + * @param threadName a label identifying the thread in the report + * @param timeoutMs the timeout in milliseconds */ public void recordTimedWait(Object monitor, String monitorName, String threadName, long timeoutMs) { WaitInfo info = new WaitInfo(monitor, monitorName); @@ -53,6 +62,9 @@ public void recordTimedWait(Object monitor, String monitorName, String threadNam /** * Record a notify() call. + * + * @param monitor the object being used as a monitor, tracked by identity + * @param monitorName a label identifying the monitor in the report */ public void recordNotify(Object monitor, String monitorName) { WaitInfo info = new WaitInfo(monitor, monitorName); @@ -62,6 +74,9 @@ public void recordNotify(Object monitor, String monitorName) { /** * Record a notifyAll() call. + * + * @param monitor the object being used as a monitor, tracked by identity + * @param monitorName a label identifying the monitor in the report */ public void recordNotifyAll(Object monitor, String monitorName) { WaitInfo info = new WaitInfo(monitor, monitorName); @@ -71,6 +86,8 @@ public void recordNotifyAll(Object monitor, String monitorName) { /** * Analyze wait patterns and return report. + * + * @return the findings this detector collected during the run */ public WaitTimeoutReport analyze() { return new WaitTimeoutReport(waitEvents, infiniteWaits); @@ -82,7 +99,12 @@ public WaitTimeoutReport analyze() { public static class WaitTimeoutReport { private final Map> waitEvents; private final Set infiniteWaits; - + /** + * Creates a WaitTimeoutReport. + * + * @param waitEvents every recorded wait and what was observed on it + * @param infiniteWaits the waits entered without a timeout + */ public WaitTimeoutReport( Map> waitEvents, Set infiniteWaits @@ -91,7 +113,9 @@ public WaitTimeoutReport( this.infiniteWaits = Collections.unmodifiableSet(new HashSet<>(infiniteWaits)); } - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !infiniteWaits.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WakeupDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WakeupDetector.java index 3171b354..334ea426 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WakeupDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WakeupDetector.java @@ -38,6 +38,8 @@ private static class MonitorState { /** * Record that a thread is about to wait on a monitor. + * + * @param monitor the object being used as a monitor, tracked by identity */ public void recordWaitEnter(Object monitor) { if (!enabled) return; @@ -56,6 +58,9 @@ public void recordWaitEnter(Object monitor) { /** * Record that a thread has exited wait (either notified or spurious). + * + * @param monitor the object being used as a monitor, tracked by identity + * @param wasNotified the {@code wasNotified} flag */ public void recordWaitExit(Object monitor, boolean wasNotified) { if (!enabled) return; @@ -80,6 +85,9 @@ public void recordWaitExit(Object monitor, boolean wasNotified) { /** * Record a notify call on a monitor. + * + * @param monitor the object being used as a monitor, tracked by identity + * @param notifyAll the {@code notifyAll} flag */ public void recordNotify(Object monitor, boolean notifyAll) { if (!enabled) return; @@ -106,6 +114,8 @@ public void recordNotify(Object monitor, boolean notifyAll) { /** * Analyze wakeup patterns for issues. + * + * @return the findings this detector collected during the run */ public WakeupReport analyzeWakeups() { WakeupReport report = new WakeupReport(); @@ -139,6 +149,8 @@ public WakeupReport analyzeWakeups() { /** * Standardized alias for {@link #analyzeWakeups()}. + * + * @return the findings this detector collected during the run */ public WakeupReport analyze() { return analyzeWakeups(); @@ -146,34 +158,33 @@ public WakeupReport analyze() { /** * Clears recorded the observation so this instance can be reused for the next run. */ - public void reset() { monitors.clear(); } /** * Disable. */ - public void disable() { enabled = false; } /** * Enable. */ - public void enable() { enabled = true; } public static class WakeupReport { - /** The monitors with spurious wakeups. */ + /** Monitors whose waiters woke without a matching notification. */ public final Set monitorsWithSpuriousWakeups = new HashSet<>(); - /** The monitors with lost notifications. */ + /** Monitors where a notification arrived before the waiter blocked. */ public final Set monitorsWithLostNotifications = new HashSet<>(); - /** The always notify without wait. */ + /** Monitors notified while nothing was waiting, so the signal was lost. */ public final Set alwaysNotifyWithoutWait = new HashSet<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !monitorsWithSpuriousWakeups.isEmpty() || !monitorsWithLostNotifications.isEmpty(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WeakHashMapSharedDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WeakHashMapSharedDetector.java index fd7f78f5..76a50c2a 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WeakHashMapSharedDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WeakHashMapSharedDetector.java @@ -91,9 +91,8 @@ public void recordAccess(Map map, String name, Thread thread) { /** * Analyses what has been recorded about the observation and builds the report for it. * - * @return the analyze + * @return the findings this detector collected during the run */ - public Report analyze() { Report r = new Report(); for (State s : instances.values()) { @@ -128,12 +127,14 @@ public Report analyze() { } public static final class Report { - /** The violations. */ + /** Findings as human-readable lines, for the text report. */ public final List violations = new ArrayList<>(); - /** The structured violations. */ + /** The same findings as {@link se.deversity.asynctest.report.Violation} objects, for machine-readable reports. */ public final List structuredViolations = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WeakReferenceRaceDetector.java b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WeakReferenceRaceDetector.java index 7c61754f..677dd180 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WeakReferenceRaceDetector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/diagnostics/WeakReferenceRaceDetector.java @@ -93,7 +93,9 @@ public void recordNullDereference(Object ref, String name, Thread thread) { s.nullDerefs.add(thread.getName()); } - /** {@return report of weak-reference race and null-dereference issues} */ + /** + * {@return report of weak-reference race and null-dereference issues} + */ public WeakReferenceRaceReport analyze() { WeakReferenceRaceReport r = new WeakReferenceRaceReport(); for (RefState s : refs.values()) { @@ -119,7 +121,9 @@ public static class WeakReferenceRaceReport { final List violations = new ArrayList<>(); final List warnings = new ArrayList<>(); - /** {@return whether there are issues} */ + /** + * {@return whether there are issues} + */ public boolean hasIssues() { return !violations.isEmpty() || !warnings.isEmpty(); } @Override diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/extension/AsyncTestInvocationInterceptor.java b/async-test-lib/src/main/java/se/deversity/asynctest/extension/AsyncTestInvocationInterceptor.java index 1a221f0c..e02df7bf 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/extension/AsyncTestInvocationInterceptor.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/extension/AsyncTestInvocationInterceptor.java @@ -23,7 +23,11 @@ public class AsyncTestInvocationInterceptor implements InvocationInterceptor { private final AsyncTest asyncTest; private final int threadCount; - + /** + * Creates a AsyncTestInvocationInterceptor. + * + * @param asyncTest the annotation on the test method, supplying the run configuration + */ public AsyncTestInvocationInterceptor(AsyncTest asyncTest) { this(asyncTest, asyncTest.threads()); } @@ -34,6 +38,9 @@ public AsyncTestInvocationInterceptor(AsyncTest asyncTest) { * its own count. * * @since 1.6.0 + * + * @param asyncTest the annotation on the test method, supplying the run configuration + * @param threadCount thread count to use instead of the annotation value, for a parameterised template */ public AsyncTestInvocationInterceptor(AsyncTest asyncTest, int threadCount) { this.asyncTest = asyncTest; diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/report/Baseline.java b/async-test-lib/src/main/java/se/deversity/asynctest/report/Baseline.java index 4a452ad8..9b298283 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/report/Baseline.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/report/Baseline.java @@ -67,6 +67,8 @@ private Baseline(Set entries) { * Resolves the active baseline from {@value #PATH_PROPERTY}; returns an empty * baseline when the property is unset. A missing file is treated as empty in * update mode (it will be created) and logged once otherwise. + * + * @return the baseline named by the system properties, or an empty one when none is configured */ public static Baseline fromSystemProperties() { String prop = System.getProperty(PATH_PROPERTY); @@ -76,12 +78,21 @@ public static Baseline fromSystemProperties() { return load(Path.of(prop)); } - /** Whether {@value #UPDATE_PROPERTY} is set, switching the gate to record mode. */ + /** + * Whether {@value #UPDATE_PROPERTY} is set, switching the gate to record mode. + * + * @return {@code true} when findings are being recorded into the baseline rather than gated on + */ public static boolean updateMode() { return Boolean.getBoolean(UPDATE_PROPERTY); } - /** Loads (with caching by last-modified time) the baseline at {@code path}. */ + /** + * Loads (with caching by last-modified time) the baseline at {@code path}. + * + * @param path the baseline file to read; a missing file yields an empty baseline rather than an error + * @return the baseline read from that file, or an empty baseline when the file does not exist + */ public static Baseline load(Path path) { if (!Files.exists(path)) { if (!updateMode()) { @@ -111,12 +122,22 @@ public static Baseline load(Path path) { } } - /** Returns {@code true} when the (test, detector) finding is baselined. */ + /** + * Returns {@code true} when the (test, detector) finding is baselined. + * + * @param testId the test the finding was raised against + * @param detectorName the detector that raised the finding + * @return {@code true} when that finding is already baselined and must not fail the build + */ public boolean contains(String testId, String detectorName) { return entries.contains(key(testId, detectorName)); } - /** Number of entries in this baseline. */ + /** + * Number of entries in this baseline. + * + * @return the number of baselined findings + */ public int size() { return entries.size(); } @@ -127,6 +148,9 @@ public int size() { * present. Thread-safe across concurrently-running tests in the same JVM. * * @return the number of entries actually added + * + * @param testId the test the finding was raised against + * @param detectorNames the detectors to baseline for this test; entries already present are skipped */ public static int record(String testId, Collection detectorNames) { String prop = System.getProperty(PATH_PROPERTY); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/report/DetectorFinding.java b/async-test-lib/src/main/java/se/deversity/asynctest/report/DetectorFinding.java index 8f12031f..a91f0d61 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/report/DetectorFinding.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/report/DetectorFinding.java @@ -7,15 +7,22 @@ */ public final class DetectorFinding { - /** The detector name. */ + /** Detector that raised this finding. */ public final String detectorName; - /** The severity. */ + /** How the finding is weighed by the {@code failOn} gate. */ public final IssueSeverity severity; - /** The report. */ + /** Human-readable detail shown in the report. */ public final String report; /** The timestamp in milliseconds. */ public final long timestampMs; - + /** + * Creates a DetectorFinding. + * + * @param detectorName the detector that raised this finding + * @param severity how the finding is weighed by the {@code failOn} gate + * @param report the human-readable detail shown in the report + * @param timestampMs the timestamp in milliseconds + */ public DetectorFinding(String detectorName, IssueSeverity severity, String report, long timestampMs) { this.detectorName = detectorName; this.severity = severity != null ? severity : IssueSeverity.HIGH; diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/report/Formatter.java b/async-test-lib/src/main/java/se/deversity/asynctest/report/Formatter.java index 8e0a92f4..c8f9667f 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/report/Formatter.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/report/Formatter.java @@ -27,6 +27,9 @@ public interface Formatter { /** * Render the violations. Empty lists must produce a non-null result * (typically an empty string, or a "no violations" marker — formatter's choice). + * + * @param violations the findings to render, possibly empty but never {@code null} + * @return the rendered report, never {@code null} */ String format(List violations); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/report/JUnitXmlReportListener.java b/async-test-lib/src/main/java/se/deversity/asynctest/report/JUnitXmlReportListener.java index 2803d60f..9bfb6ba3 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/report/JUnitXmlReportListener.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/report/JUnitXmlReportListener.java @@ -78,6 +78,8 @@ public JUnitXmlReportListener(String outputDir) { } /** + * Creates a JUnitXmlReportListener. + * * @param outputDir the directory to write the XML report into * @param registerShutdownHook whether to register a JVM shutdown hook for auto-flush */ @@ -119,6 +121,8 @@ public void onStructuredReport(String detectorName, IssueSeverity severity, /** * Returns the number of accumulated findings (useful for assertions in tests of this listener). + * + * @return the number of findings written so far */ public int getFindingCount() { return findings.size(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/report/JsonReportListener.java b/async-test-lib/src/main/java/se/deversity/asynctest/report/JsonReportListener.java index 9d0c5fe0..1d6da7db 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/report/JsonReportListener.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/report/JsonReportListener.java @@ -85,6 +85,8 @@ public JsonReportListener(String outputDir) { } /** + * Creates a JsonReportListener. + * * @param outputDir the directory to write the JSON report into * @param registerShutdownHook whether to register a JVM shutdown hook for auto-flush */ @@ -126,6 +128,8 @@ public void onStructuredReport(String detectorName, IssueSeverity severity, /** * Returns the number of accumulated findings. + * + * @return the number of findings written so far */ public int getFindingCount() { return findings.size(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/runner/ConcurrencyRunner.java b/async-test-lib/src/main/java/se/deversity/asynctest/runner/ConcurrencyRunner.java index 7700a817..2a61aa8d 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/runner/ConcurrencyRunner.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/runner/ConcurrencyRunner.java @@ -85,6 +85,12 @@ public class ConcurrencyRunner { * transitively, since both are derived from the round timeout — the {@code CyclicBarrier} * await and the async-body {@code CompletionStage} wait. See * {@link #resolveTimeoutMultiplier()} for the CI-scaling mechanism itself. + * + * @param invocationContext the JUnit invocation context carrying the test instance and method to run + * @param config the resolved configuration deciding threads, rounds, timeout and detectors + * + * @throws Throwable the failure from the test body, unwrapped, or an {@link AssertionError} + * raised by the timeout path or the {@code failOn} gate */ @AILoadBearing( invariant = "The timeoutAlreadyReported flag, and the per-step guarded cleanup in the " diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/runner/LicenseGuard.java b/async-test-lib/src/main/java/se/deversity/asynctest/runner/LicenseGuard.java index 2fe77214..5a932b7a 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/runner/LicenseGuard.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/runner/LicenseGuard.java @@ -47,6 +47,8 @@ private LicenseGuard() {} /** * Validates the license for the given config. Throws {@link SecurityException} * if denied. Subsequent calls with the same fingerprint return immediately. + * + * @param config the configuration whose fingerprint keys the cached decision */ @AIIdempotent(reason = "ConcurrentHashMap.computeIfAbsent guarantees the underlying gate.check fires at most once per Fingerprint; repeat calls return immediately. Denied results consistently throw SecurityException for the same fingerprint.") public static void check(AsyncTestConfig config) { diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/runner/SpinContentionBarrier.java b/async-test-lib/src/main/java/se/deversity/asynctest/runner/SpinContentionBarrier.java index a2c23432..41fbf21e 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/runner/SpinContentionBarrier.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/runner/SpinContentionBarrier.java @@ -66,6 +66,16 @@ public final class SpinContentionBarrier { @SuppressWarnings("unused") private long pad11; @SuppressWarnings("unused") private long pad12; + /** + * Creates a barrier that releases once {@code totalThreads} threads have arrived. + * + *

Unlike {@link java.util.concurrent.CyclicBarrier}, arriving threads spin rather than + * park, so they resume within nanoseconds of the last arrival instead of waiting to be + * scheduled. That is what makes the collision tight enough for a race to reproduce. + * + * @param totalThreads how many threads must arrive before any is released; must be at least 1 + * @throws IllegalArgumentException if {@code totalThreads} is less than 1 + */ public SpinContentionBarrier(int totalThreads) { if (totalThreads < 1) { throw new IllegalArgumentException("totalThreads must be >= 1, got: " + totalThreads); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/spi/Detector.java b/async-test-lib/src/main/java/se/deversity/asynctest/spi/Detector.java index cacda2f3..f4dd0738 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/spi/Detector.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/spi/Detector.java @@ -54,6 +54,8 @@ public interface Detector { * Identity of this detector. Must be a value from the {@link DetectorType} * enum so that {@code @AsyncTest(excludes = {...})} and * {@code Preset.enabled()} can address it. + * + * @return the constant identifying this detector */ DetectorType type(); diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/spi/DetectorFactory.java b/async-test-lib/src/main/java/se/deversity/asynctest/spi/DetectorFactory.java index 1f20adf3..eae846ce 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/spi/DetectorFactory.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/spi/DetectorFactory.java @@ -28,6 +28,8 @@ public interface DetectorFactory { /** * Identity of the detector this factory produces. Must match * {@link Detector#type()} of the instances returned by {@link #create(AsyncTestConfig)}. + * + * @return the constant identifying the detector this factory produces */ DetectorType type(); @@ -35,16 +37,22 @@ public interface DetectorFactory { * Whether this detector is active for the given test configuration. * *

{@code AsyncTestConfig} carries one boolean field per legacy detector - * (~85 fields) — each factory's adapter consults its own flag here. There is + * (132 fields) — each factory's adapter consults its own flag here. There is * no automatic mapping from {@link DetectorType} to a boolean field, so this * method must be implemented by every concrete factory. Returning {@code true} * unconditionally is acceptable for detectors that should always run. + * + * @param config the resolved configuration for the test about to run + * @return {@code true} to build and install this detector for that test */ boolean isEnabledFor(AsyncTestConfig config); /** * Construct a fresh detector instance for one {@code @AsyncTest} method's * invocation rounds. + * + * @param config the resolved configuration for the test about to run + * @return a new detector, not shared with any other test */ Detector create(AsyncTestConfig config); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/spi/DetectorRegistry.java b/async-test-lib/src/main/java/se/deversity/asynctest/spi/DetectorRegistry.java index 5c1c96a7..2b67d7cf 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/spi/DetectorRegistry.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/spi/DetectorRegistry.java @@ -70,6 +70,9 @@ private DetectorRegistry(Map detectors) { *

This is the addressability view, used to prove every {@link DetectorType} is reachable * through the SPI. It is not the path the runner takes: see * {@link #buildExternal(AsyncTestConfig)}. + * + * @param config the configuration deciding which factories report themselves as enabled + * @return a registry holding every enabled detector, built-in and third-party */ public static DetectorRegistry build(AsyncTestConfig config) { Map detectors = new EnumMap<>(DetectorType.class); @@ -99,6 +102,9 @@ public static DetectorRegistry build(AsyncTestConfig config) { * #build(AsyncTestConfig)} reads, so runtime discovery sees only genuine third-party providers. * * @since 1.7.0 + * + * @param config the configuration deciding which factories report themselves as enabled + * @return a registry holding only the enabled third-party detectors */ public static DetectorRegistry buildExternal(AsyncTestConfig config) { Map detectors = new EnumMap<>(DetectorType.class); @@ -175,7 +181,9 @@ private static DetectorFactory instantiate(String className) { } } - /** {@return {@code true} when no detector is active in this registry} */ + /** + * {@return {@code true} when no detector is active in this registry} + */ public boolean isEmpty() { return byType.isEmpty(); } @@ -186,6 +194,10 @@ public boolean isEmpty() { *

Calls into user code should treat null as "feature off" rather than an * error — matches the behavior of the legacy {@code AsyncTestContext.require} * accessors but without the exception. + * + * @param the detector type being looked up + * @param detectorClass the class to match against the active detectors + * @return the active detector of that class, or {@code null} when it is not enabled */ @SuppressWarnings("unchecked") public @Nullable T get(Class detectorClass) { @@ -195,17 +207,30 @@ public boolean isEmpty() { return null; } - /** Type-keyed lookup. */ + /** + * Type-keyed lookup. + * + * @param type the detector to look up + * @return the active detector for that type, or {@code null} when it is not enabled + */ public @Nullable Detector get(DetectorType type) { return byType.get(type); } - /** All active detectors (snapshot). */ + /** + * All active detectors (snapshot). + * + * @return the active detectors, in {@link DetectorType} order + */ public List all() { return new ArrayList<>(byType.values()); } - /** Aggregated violations from every active detector for the current round. */ + /** + * Aggregated violations from every active detector for the current round. + * + * @return the violations reported by every active detector + */ @AIIdempotent(reason = "Each Detector.analyze() must return the same violations for the same observed state (the SPI contract). Calling analyzeAll() N times on a quiescent registry yields N identical lists; do not introduce stateful side-effects in analyze().") public List analyzeAll() { List out = new ArrayList<>(); @@ -227,14 +252,12 @@ public List analyzeAll() { /** * Fire on test start. */ - public void fireOnTestStart() { for (Detector d : byType.values()) d.onTestStart(); } /** * Fire on test end. */ - public void fireOnTestEnd() { for (Detector d : byType.values()) d.onTestEnd(); } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/spi/adapters/LegacyDetectorAdapter.java b/async-test-lib/src/main/java/se/deversity/asynctest/spi/adapters/LegacyDetectorAdapter.java index 9a69e9d1..e24e6cdd 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/spi/adapters/LegacyDetectorAdapter.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/spi/adapters/LegacyDetectorAdapter.java @@ -47,7 +47,13 @@ public final class LegacyDetectorAdapter implements Detector { private final @Nullable Method analyzeMethod; private final @Nullable NoSuchMethodException analyzeMethodLookupFailure; private final @Nullable Method hasIssuesMethod; - + /** + * Creates a LegacyDetectorAdapter. + * + * @param delegate the legacy detector to expose through the SPI; findings are read from this instance + * @param type the constant this detector answers to + * @param detectorName the name this detector reports under + */ public LegacyDetectorAdapter(D delegate, DetectorType type, String detectorName) { this.delegate = delegate; this.type = type; @@ -100,7 +106,11 @@ public List analyze() { } } - /** Exposed for callers that need direct access to the wrapped legacy detector. */ + /** + * Exposed for callers that need direct access to the wrapped legacy detector. + * + * @return the wrapped legacy detector + */ public D delegate() { return delegate; } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/spi/adapters/SharedMessageDigestDetectorFactory.java b/async-test-lib/src/main/java/se/deversity/asynctest/spi/adapters/SharedMessageDigestDetectorFactory.java index 588da597..dc626e49 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/spi/adapters/SharedMessageDigestDetectorFactory.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/spi/adapters/SharedMessageDigestDetectorFactory.java @@ -44,7 +44,11 @@ public Detector create(AsyncTestConfig config) { */ public static final class Adapter implements Detector { private final SharedMessageDigestDetector delegate; - + /** + * Creates a Adapter. + * + * @param delegate the legacy detector whose findings this adapter republishes + */ public Adapter(SharedMessageDigestDetector delegate) { this.delegate = delegate; } @@ -59,7 +63,11 @@ public List analyze() { return List.copyOf(delegate.analyze().structuredViolations); } - /** Exposed for legacy users that need direct access to the wrapped detector. */ + /** + * Exposed for legacy users that need direct access to the wrapped detector. + * + * @return the wrapped legacy detector + */ public SharedMessageDigestDetector delegate() { return delegate; } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/telemetry/TelemetryEventBuffer.java b/async-test-lib/src/main/java/se/deversity/asynctest/telemetry/TelemetryEventBuffer.java index 02728ce5..1b157183 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/telemetry/TelemetryEventBuffer.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/telemetry/TelemetryEventBuffer.java @@ -76,6 +76,8 @@ static final class AccessEvent { private volatile long consumerCursor = -1; /** + * Creates a TelemetryEventBuffer. + * * @param capacityPowerOfTwo ring-buffer capacity; must be a power of two (e.g. 1024, 4096) */ public TelemetryEventBuffer(int capacityPowerOfTwo) { @@ -154,7 +156,11 @@ public int drain(DrainCallback callback) { return count; } - /** Returns the number of events published so far (monotonically increasing). */ + /** + * Returns the number of events published so far (monotonically increasing). + * + * @return the number of events published since this buffer was created + */ public long publishedCount() { return producerCursor.get() + 1; } diff --git a/async-test-lib/src/main/java/se/deversity/asynctest/telemetry/TelemetryRegistry.java b/async-test-lib/src/main/java/se/deversity/asynctest/telemetry/TelemetryRegistry.java index 917fc3e3..f6fda5ea 100644 --- a/async-test-lib/src/main/java/se/deversity/asynctest/telemetry/TelemetryRegistry.java +++ b/async-test-lib/src/main/java/se/deversity/asynctest/telemetry/TelemetryRegistry.java @@ -111,7 +111,9 @@ public static void start(TelemetryEventBuffer.@Nullable DrainCallback callback) Runtime.getRuntime().addShutdownHook(shutdownHook); } - /** Starts the registry with a no-op drain callback (events counted but not forwarded). */ + /** + * Starts the registry with a no-op drain callback (events counted but not forwarded). + */ public static void start() { start(null); } @@ -224,7 +226,11 @@ public static void stop() { drainOnce(); // final flush } - /** Exposes the shared buffer for testing and advanced consumers. */ + /** + * Exposes the shared buffer for testing and advanced consumers. + * + * @return the buffer producers publish into + */ public static TelemetryEventBuffer buffer() { return BUFFER; } diff --git a/async-test-lib/src/test/java/se/deversity/asynctest/architecture/JavadocDescribesRatherThanRestatesTest.java b/async-test-lib/src/test/java/se/deversity/asynctest/architecture/JavadocDescribesRatherThanRestatesTest.java new file mode 100644 index 00000000..0bffbe68 --- /dev/null +++ b/async-test-lib/src/test/java/se/deversity/asynctest/architecture/JavadocDescribesRatherThanRestatesTest.java @@ -0,0 +1,221 @@ +package se.deversity.asynctest.architecture; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Pins that published javadoc says something the signature does not already say. + * + *

Doclint answers one question: is the tag present. It cannot tell {@code @param timeout the + * timeout} from a description, so a mechanical pass that adds a tag per parameter turns a build + * with hundreds of warnings into a green one without a reader learning anything. That is exactly + * what happened here: closing the warnings left 432 {@code @param} lines, 166 {@code @return} + * lines and 135 one-line summaries that restated the identifier, all on public members, all + * published to whoever depends on this library. One of them read {@code /** The totcou races. *}{@code /} + * over a field recording time-of-check-to-time-of-use races. + * + *

These tests ask the question doclint cannot. A description fails when, ignoring a leading + * article, it is nothing but the identifier it is attached to respelled: {@code @param lockName + * the lock name}, {@code @return the size} on {@code size()}. Anything that adds a unit, a null + * rule, a range, an identity-versus-equality note, or any other fact a caller could not read off + * the signature passes. + * + *

They deliberately do not measure length or wording. A short description can be complete + * ({@code @return this builder}), and no rule about prose style survives contact with 127 + * detectors. The only thing pinned is that the text is not pure restatement. + * + * @see BuildMetadataSyncTest + */ +class JavadocDescribesRatherThanRestatesTest { + + /** Matches a {@code @param} tag, capturing the name and its description. */ + private static final Pattern PARAM = + Pattern.compile("^\\s*\\*\\s*@param\\s+?\\s+(\\S.*?)\\s*$"); + + /** Matches a {@code @return} tag, capturing its description. */ + private static final Pattern RETURN = + Pattern.compile("^\\s*\\*\\s*@return\\s+(\\S.*?)\\s*$"); + + /** Matches a whole javadoc block written on one line, capturing its text. */ + private static final Pattern SUMMARY = + Pattern.compile("^\\s*/\\*\\*\\s*(.+?)\\s*\\*/\\s*$"); + + /** Matches the name of the member a javadoc block sits on: a method, constructor or field. */ + private static final Pattern DECLARED_MEMBER = + Pattern.compile("(\\w+)\\s*(?:[;=]|\\()"); + + /** Splits an identifier into its words, so {@code lockName} and "lock name" compare equal. */ + private static final Pattern IDENTIFIER_WORDS = Pattern.compile("(? offenders = new ArrayList<>(); + for (Path file : mainSources()) { + offenders.addAll(restatementsIn(file)); + } + assertTrue(offenders.isEmpty(), + "These javadoc tags only respell the identifier they document, so they reach " + + "consumers without saying anything the signature does not. Give the " + + "unit, the null rule, the range, or what the value is used for:\n " + + String.join("\n ", offenders)); + } + + @Test + @DisplayName("no one-line javadoc summary in main sources merely restates the identifier") + void summariesDescribeRatherThanRestate() { + List offenders = new ArrayList<>(); + for (Path file : mainSources()) { + List lines = readLines(file); + for (int i = 0; i < lines.size(); i++) { + Matcher m = SUMMARY.matcher(lines.get(i)); + if (!m.matches() || m.group(1).startsWith("{@return")) { + continue; + } + String subject = declaredNameAfter(lines, i); + if (subject != null && restates(subject, m.group(1))) { + offenders.add(report(file, i, lines.get(i))); + } + } + } + assertTrue(offenders.isEmpty(), + "These javadoc summaries only respell the member they describe. A public field " + + "surfaced in a report has to say what its entries mean, not repeat its " + + "own name:\n " + String.join("\n ", offenders)); + } + + /** {@return every restating {@code @param} or {@code @return} tag in {@code file}} */ + private static List restatementsIn(Path file) { + List lines = readLines(file); + List offenders = new ArrayList<>(); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + + Matcher param = PARAM.matcher(line); + if (param.matches() && restates(param.group(1), param.group(2))) { + offenders.add(report(file, i, line)); + continue; + } + + Matcher ret = RETURN.matcher(line); + if (ret.matches()) { + String subject = declaredNameAfter(lines, i); + if (subject != null && restates(subject, ret.group(1))) { + offenders.add(report(file, i, line)); + } + } + } + return offenders; + } + + /** + * Whether {@code description} is nothing but {@code identifier} respelled. + * + * @param identifier the member or parameter name the text is attached to + * @param description the text as written, without its tag + * @return {@code true} when the description adds nothing to the identifier + */ + private static boolean restates(String identifier, String description) { + String stripped = LEADING_ARTICLE + .matcher(description.toLowerCase(Locale.ROOT).trim()) + .replaceFirst("") + .replaceAll("\\.$", "") + .trim(); + String words = IDENTIFIER_WORDS.matcher(identifier).replaceAll(" ").toLowerCase(Locale.ROOT); + return stripped.equals(words) + || stripped.replace(" ", "").equals(words.replace(" ", "")); + } + + /** + * The member declared after the javadoc block containing line {@code i}, skipping blank + * lines, comments and annotations. + * + * @param lines the file's lines + * @param i the index of the tag or summary being judged + * @return the declared name, or {@code null} when nothing recognisable follows the block + */ + private static String declaredNameAfter(List lines, int i) { + int end = i; + while (end < lines.size() && !lines.get(end).contains("*/")) { + end++; + } + int decl = end + 1; + while (decl < lines.size()) { + String candidate = lines.get(decl).trim(); + if (candidate.isEmpty() || candidate.startsWith("//") || candidate.startsWith("@")) { + decl++; + continue; + } + Matcher m = DECLARED_MEMBER.matcher(candidate); + return m.find() ? m.group(1) : null; + } + return null; + } + + /** {@return a {@code path:line} report for the javadoc on line {@code i}} */ + private static String report(Path file, int i, String line) { + return repoRoot().relativize(file) + ":" + (i + 1) + " " + line.trim(); + } + + /** {@return every {@code .java} file under any published module's {@code src/main/java}} */ + private static List mainSources() { + Path root = repoRoot(); + List files = new ArrayList<>(); + for (String module : List.of("async-test-lib", "async-test-agent", "async-test-analysis")) { + Path src = root.resolve(module).resolve("src/main/java"); + if (!Files.isDirectory(src)) { + continue; + } + try (Stream walk = Files.walk(src)) { + walk.filter(p -> p.toString().endsWith(".java")).forEach(files::add); + } catch (IOException e) { + throw new UncheckedIOException("Could not walk " + src, e); + } + } + assertTrue(files.size() > 100, + "Expected to scan the published sources but found only " + files.size() + + " files under " + root + ". The test is looking in the wrong place, " + + "which would let it pass by scanning nothing."); + return files; + } + + private static List readLines(Path file) { + try { + return Files.readAllLines(file, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new UncheckedIOException("Could not read " + file, e); + } + } + + /** {@return the reactor root, found by walking up to the directory holding the parent pom} */ + private static Path repoRoot() { + Path dir = Path.of("").toAbsolutePath(); + while (dir != null) { + if (Files.exists(dir.resolve("settings.gradle.kts")) + && Files.exists(dir.resolve("pom.xml"))) { + return dir; + } + dir = dir.getParent(); + } + throw new IllegalStateException( + "Could not find the reactor root (a directory holding both pom.xml and " + + "settings.gradle.kts) above " + Path.of("").toAbsolutePath()); + } +} diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 39ac66ed..26529ba3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,96 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed — 733 published javadoc descriptions said nothing, and a test now says so + +The previous entries in this release closed every doclint warning. Doclint answers exactly one +question, is the tag present, so closing its warnings does not mean a reader learns anything. It +cannot tell `@param timeout the timeout` from a description. Counting what the mechanical pass had +actually produced: + +| Restatement | Count | All on public members | +|---|---|---| +| `@param` tags | 432 | yes | +| `@return` tags | 166 | yes | +| one-line summaries | 135 | yes | + +Every one restated the identifier it documented and nothing else. `@param lockName the lock name`. +`@return the size` on `size()`. The worst read `/** The totcou races. */` over a public field +recording time-of-check-to-time-of-use races, where the generated prose had respelled a misspelled +acronym into a non-word. All 733 now describe something a caller cannot read off the signature: the +unit on `sleepDuration` (nanoseconds), the null rule on `SiteCapture.capture()`, that detectors +track subjects by identity rather than equality, that `distanceInBytes` under a cache line is what +makes two fields share one. + +Two defects surfaced while doing it, both invisible to doclint because it checks nothing below +`protected` by default: + +- `SpinContentionBarrier`'s constructor javadoc had been placed between two cache-line padding + fields, documenting `pad7`. The public constructor had no javadoc at all, and the build was + silent about it. Reattached, and it now states the contract that matters, that arriving threads + spin rather than park so the collision stays tight enough to reproduce a race. +- 183 stray blank lines sat between a javadoc block and the member it documents. + +`totcouRaces` keeps its misspelled name. It is a public field and renaming it would break binary +compatibility against the 1.6.0 baseline; the javadoc now spells out what it records and notes the +name is kept deliberately. + +`JavadocDescribesRatherThanRestatesTest` pins all of this. It asks the question doclint cannot: +whether a description, ignoring a leading article, is anything more than its identifier respelled. +It does not measure length or style, because a short description can be complete (`@return this +builder`) and no prose rule survives contact with 127 detectors. It was verified in both +directions: it failed on the real tree before the fix, naming +`ABAProblemDetector.java:197 * @return the analyze ABA` and one other that the initial sweep's +acronym handling had missed, and putting a single placeholder back afterwards turned it red again +with the exact file and line. It also asserts it scanned more than 100 files, so it cannot pass by +looking in the wrong directory and finding nothing. + +### Added — DetectorType's 127 constants documented, and its lock text corrected + +The enum a user types into `@AsyncTest(excludes = ...)` had no documentation on any of its 127 +constants, so the published javadoc listed 127 bare names. Each now carries the first sentence of +the detector it selects, taken from that detector's own class javadoc rather than invented, so +`DEADLOCKS` reads "Enhanced deadlock detector that analyzes thread dumps and identifies circular +lock dependencies..." The mapping is derived, not hand-maintained: `AsyncTestConfig.build()` gives +constant to flag, `DetectorRegistry`'s constructor gives flag to detector class, and all 127 resolve +with none left over. + +The file is `@AILocked`, and the lock was waived for this deliberately. Its own reason says a +constant needs synchronized edits in five places; a comment adds no constant and cannot break that. +The annotation now says so, so the next reader does not have to ask: the lock is on the constant +set, not on the file. + +Two pieces of that guardrail had also gone stale and are corrected in the same change. It still +described "both branches of `AsyncTestConfig.build()` (detectAll block + excludes block)", which has +been a single expression per detector for some time, and `@AIKeepInSync` still listed +`META-INF/services/…DetectorFactory` as the file that must agree, which stopped being true when the +built-in factories moved to `META-INF/async-test/builtin-detector-factories`. Both feed the +generated `CLAUDE.md`, so a stale guardrail misdirects every future contributor. + +### Added — 295 `@param` and 122 `@return` tags on already-documented members + +Tags were appended to existing blocks rather than blocks being rebuilt. That distinction is the +whole change: an earlier attempt rebuilt each block and replaced real prose with a generated stub, +turning `ConcurrencyRunner.execute`'s detailed javadoc into "Execute.". Appending cannot lose text. +287 single-line comments were expanded to multi-line first, as a separate pass, so no insertion had +to reason about indices that another insertion had already moved. + +### Documented — what happens on a first run with no licence key + +`LicenseGuard` denies a developer who has no key and has not set `-Dlicense.mock.mode=true`; mock +mode turns itself on only in CI. That is intended behaviour for a PolyForm Noncommercial library and +is left alone, but the README described it as "outcome depends on the configured backend", which +does not prepare anyone for a `SecurityException` before a single test body runs. + +The README now shows the actual error and says plainly that CI is silently mocked while a laptop is +not, which is why the same suite can pass in CI and stop locally. `TROUBLESHOOTING.md` gains a +section with the fix for Maven, Gradle and the IDE, and with the reason the gate is loud rather than +silently degrading: a run that was not licensed must never look like a run that found no bugs. + +Not changed: the 51 default-constructor javadoc warnings. Clearing them means adding 51 public +constructors to satisfy a style rule, which widens the documented API surface for no functional +gain, so the warnings stay. + ### Fixed — javadoc reaching consumers was missing ~260 tags, and the build was configured not to notice `maven-javadoc-plugin` ran with `all,-missing`: every check except `missing`. So diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index cd41edb9..37e13703 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -59,3 +59,49 @@ Default timeouts (5,000ms) that work locally can easily trigger timeouts in reso * **Tuning Guide**: * **Local / High-Spec CI**: Use `virtualThreadStressMode = "MEDIUM"` (spawns 500 virtual threads). * **Low-Spec CI / Containerized Runs**: Use `virtualThreadStressMode = "LOW"` (spawns 100 virtual threads) or `OFF`. + +--- + +## 6. `SecurityException: LICENSE DENIED` before any test runs + +### Symptom + +A run stops immediately, before a single test body executes: + +``` +java.lang.SecurityException: LICENSE DENIED: + To run locally without a key: -Dlicense.mock.mode=true + In CI (GITHUB_ACTIONS or CI env var set, no key): mock mode activates automatically. +``` + +### Cause + +`LicenseGuard` runs once per configuration at the start of `ConcurrencyRunner.execute`, before the +`CyclicBarrier` is built. It is not reacting to anything your test did; it decided before the test +started. + +Mock mode, which bypasses the check, turns itself on in exactly two situations: + +* `-Dlicense.mock.mode=true` is set, or +* the run looks like CI (`GITHUB_ACTIONS` or `CI` is set in the environment) **and** no key is + configured. + +A developer machine with no key matches neither, so the gate consults the backend and can refuse. +This is why the same suite passes in CI and stops locally: CI is silently mocked, your laptop is not. + +### Fix + +For local development, set the flag once rather than per run: + +* **Maven**: `mvn test -Dlicense.mock.mode=true`, or add it to `.mvn/jvm.config`. +* **Gradle**: `systemProperty("license.mock.mode", "true")` in your `test { }` block. +* **IDE**: add `-Dlicense.mock.mode=true` to the default JUnit run configuration, so every new test + you create inherits it. + +With a real key, pass `-Dlicense.key=` and set `-Dlicense.user.email=you@example.com`. + +### Why it is not simply off by default + +The library is [PolyForm Noncommercial](../LICENSE); the gate is the mechanism behind that, not an +accident. It is deliberately loud rather than silently degrading, so that a run which was not +licensed never looks like a run that found no bugs.