diff --git a/pom.xml b/pom.xml index 1b45491cf..87ca9b923 100644 --- a/pom.xml +++ b/pom.xml @@ -144,7 +144,7 @@ 2.25ea0 https://teamcity.chronicle.software/repository/download - 84.0 + 81.0 diff --git a/src/main/java/net/openhft/chronicle/threads/BusyPauser.java b/src/main/java/net/openhft/chronicle/threads/BusyPauser.java index 9f34648b4..1b3f4ab7c 100644 --- a/src/main/java/net/openhft/chronicle/threads/BusyPauser.java +++ b/src/main/java/net/openhft/chronicle/threads/BusyPauser.java @@ -22,41 +22,89 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +/** + * Implementation of {@link Pauser} that employs a busy-wait strategy to keep the CPU actively engaged. + * This pauser continuously executes a very short pause (nano-pause) to keep the thread active. + * + *

Because it never actually suspends the thread, most operations related to state management (like pausing, unpausing, and timeout handling) are unsupported or no-op.

+ */ public enum BusyPauser implements Pauser { INSTANCE; + /** + * Does nothing as {@code BusyPauser} does not maintain state that requires resetting. + */ @Override public void reset() { // Do nothing } + /** + * Keeps the thread actively busy by executing a very short pause at the CPU level. + * This method is primarily used to prevent the thread from yielding execution entirely. + */ @Override public void pause() { Jvm.nanoPause(); } + /** + * Throws {@link UnsupportedOperationException} as {@code BusyPauser} does not support pausing with a timeout. + * + * @param timeout the timeout duration + * @param timeUnit the unit of time for the timeout duration + * @throws TimeoutException never thrown + */ @Override public void pause(long timeout, TimeUnit timeUnit) throws TimeoutException { throw new UnsupportedOperationException(this + " is not stateful, use a " + BusyTimedPauser.class.getSimpleName()); } + /** + * Does nothing as {@code BusyPauser} has no pausing state to unpause from. + */ @Override public void unpause() { // nothing to unpause. } + /** + * Always returns {@code 0} as {@code BusyPauser} does not track paused time. + * + * @return {@code 0} always + */ @Override public long timePaused() { return 0; } + /** + * Always returns {@code 0} as {@code BusyPauser} does not count pauses. + * + * @return {@code 0} always + */ @Override public long countPaused() { return 0; } + /** + * Always returns {@code true}, indicating that this pauser keeps the thread busy rather than truly pausing it. + * + * @return {@code true} always + */ @Override public boolean isBusy() { return true; } + + /** + * Provides a string representation of this pauser, identifying it as "PauserMode.busy". + * + * @return the string "PauserMode.busy" + */ + @Override + public String toString() { + return "PauserMode.busy"; + } } diff --git a/src/main/java/net/openhft/chronicle/threads/BusyTimedPauser.java b/src/main/java/net/openhft/chronicle/threads/BusyTimedPauser.java index f1c754225..54271f580 100644 --- a/src/main/java/net/openhft/chronicle/threads/BusyTimedPauser.java +++ b/src/main/java/net/openhft/chronicle/threads/BusyTimedPauser.java @@ -30,6 +30,11 @@ public class BusyTimedPauser implements Pauser, TimingPauser { private long time = Long.MAX_VALUE; private long countPaused = 0; + /** + * Always returns {@code true}, indicating that this pauser predominantly keeps the thread busy. + * + * @return {@code true}, as the primary operation is a busy wait + */ @Override public boolean isBusy() { return true; @@ -47,27 +52,59 @@ public void pause() { Jvm.nanoPause(); } + /** + * Attempts to pause the thread with a specified timeout. If the pause exceeds the specified duration, + * a {@link TimeoutException} is thrown, indicating the timeout has elapsed without resumption of operations. + * + * @param timeout the maximum time to wait before throwing an exception + * @param timeUnit the unit of time for the timeout parameter + * @throws TimeoutException if the wait exceeds the specified timeout duration + */ @Override public void pause(long timeout, TimeUnit timeUnit) throws TimeoutException { if (time == Long.MAX_VALUE) time = System.nanoTime(); - if (time + timeUnit.toNanos(timeout) - System.nanoTime() < 0) - throw new TimeoutException(); + if (System.nanoTime() - time > timeUnit.toNanos(timeout)) + throw new TimeoutException("Pause timed out after " + timeout + " " + timeUnit); pause(); } + /** + * Does nothing since this implementation has no state to unpause from. The method exists to fulfill the interface contract. + */ @Override public void unpause() { // nothing to unpause. } + /** + * Always returns {@code 0} as this pauser does not actually track total pause time. + * + * @return {@code 0}, indicating no measurable pause duration + */ @Override public long timePaused() { return 0; } + /** + * Returns the count of how many times the {@code pause()} method has been called. + * + * @return the number of pauses that have been initiated + */ @Override public long countPaused() { return countPaused; } + + /** + * Provides a string representation for this pauser, identifying it as "PauserMode.timedBusy". + * + * @return a string indicating the type of pauser + */ + @Override + public String toString() { + return "PauserMode.timedBusy"; + } } + diff --git a/src/main/java/net/openhft/chronicle/threads/LongPauser.java b/src/main/java/net/openhft/chronicle/threads/LongPauser.java index e1cffdab3..1b61e696a 100644 --- a/src/main/java/net/openhft/chronicle/threads/LongPauser.java +++ b/src/main/java/net/openhft/chronicle/threads/LongPauser.java @@ -26,6 +26,16 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.LockSupport; +import static net.openhft.chronicle.threads.LongPauser.ToStringHelper.*; + +/** + * A {@link Pauser} that implements a pausing strategy with phases of busy waiting, yielding, and sleeping, + * with each phase increasing in duration up to a configured limit. It is designed for scenarios where a gradual + * back-off is needed from active to more passive waiting states. + *

+ * The pausing behavior begins with busy waiting, transitions to yielding, and ultimately moves to sleeping, + * progressively increasing the pause time from a minimum to a specified maximum duration. + */ public class LongPauser implements Pauser, TimingPauser { private static final String SHOW_PAUSES = Jvm.getProperty("pauses.show"); private final long minPauseTimeNS; @@ -38,7 +48,7 @@ public class LongPauser implements Pauser, TimingPauser { private long timePaused = 0; private long countPaused = 0; @Nullable - private volatile Thread thread = null; + private transient volatile Thread thread = null; private long yieldStart = 0; private long pauseUntilNS = 0; @@ -74,6 +84,9 @@ public void reset() { firstPauseNS = Long.MAX_VALUE; } + /** + * Pauses the current thread according to the phased pausing strategy. + */ @Override public void pause() { try { @@ -82,12 +95,20 @@ public void pause() { } } + /** + * Initiates an asynchronous pause that will last for the current phase's duration. + */ @Override public void asyncPause() { pauseUntilNS = System.nanoTime() + pauseTimeNS; increasePauseTimeNS(); } + /** + * Checks if the pauser is currently in an asynchronous pausing state. + * + * @return {@code true} if still in the pausing state, {@code false} otherwise + */ @Override public boolean asyncPausing() { return pauseUntilNS > System.nanoTime(); @@ -132,9 +153,8 @@ public void pause(long timeout, @NotNull TimeUnit timeUnit) throws TimeoutExcept } // If a finite timeout is given, check if it's exceeded and throw a TimeoutException if it is - if (timeout < Long.MAX_VALUE) { - if (firstPauseNS + timeUnit.toNanos(timeout) - now < 0) - throw new TimeoutException(); + if (timeout < Long.MAX_VALUE && (firstPauseNS + timeUnit.toNanos(timeout) - now < 0)) { + throw new TimeoutException(); } // Check the yield time to determine whether to continue yielding or to move to the next pause strategy @@ -183,13 +203,65 @@ public void unpause() { LockSupport.unpark(threadSnapshot); } + /** + * Returns the total time that the thread has been paused, in milliseconds. + * + * @return total paused time in milliseconds. + */ @Override public long timePaused() { return timePaused / 1_000_000; } + /** + * Returns the total number of pauses that have been initiated. + * + * @return the total count of pauses. + */ @Override public long countPaused() { return countPaused; } + + /** + * Provides a human-readable description of the pauser's configuration. + * + * @return a string representing the configured pause strategy. + */ + @Override + public String toString() { + if (minBusyNS == balancedSample.minBusyNS + && minYieldNS == balancedSample.minYieldNS + && minPauseTimeNS == balancedSample.minPauseTimeNS) { + if (maxPauseTimeNS == balancedSample.maxPauseTimeNS) { + return "PauserMode.balanced"; + } else { + return "Pauser.balancedUpToMillis(" + maxPauseTimeNS / 1_000_000 + ")"; + } + } + if (minBusyNS == millisSample.minBusyNS + && minYieldNS == millisSample.minYieldNS + && minPauseTimeNS >= millisSample.minPauseTimeNS + && maxPauseTimeNS >= millisSample.maxPauseTimeNS) + return "Pauser.milli(" + minPauseTimeNS / 1_000_000 + ", " + maxPauseTimeNS / 1_000_000 + ")"; + + if (minBusyNS == sleepySample.minBusyNS + && minYieldNS == sleepySample.minYieldNS + && minPauseTimeNS == sleepySample.minPauseTimeNS + && maxPauseTimeNS == sleepySample.maxPauseTimeNS) + return "PauserMode.sleepy"; + + return "LongPauser{" + + "minBusyNS=" + minBusyNS + + ", minYieldNS=" + minYieldNS + + ", minPauseTimeNS=" + minPauseTimeNS + + ", maxPauseTimeNS=" + maxPauseTimeNS + + '}'; + } + + static class ToStringHelper { + static final LongPauser sleepySample = (LongPauser) Pauser.sleepy(); + static final LongPauser balancedSample = (LongPauser) Pauser.balanced(); + static final LongPauser millisSample = (LongPauser) Pauser.millis(1, 1); + } } diff --git a/src/main/java/net/openhft/chronicle/threads/MilliPauser.java b/src/main/java/net/openhft/chronicle/threads/MilliPauser.java index d3616b692..25b78cd96 100644 --- a/src/main/java/net/openhft/chronicle/threads/MilliPauser.java +++ b/src/main/java/net/openhft/chronicle/threads/MilliPauser.java @@ -25,6 +25,10 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.LockSupport; +/** + * A {@link Pauser} implementation that provides precise control over thread pausing based on a specified duration in milliseconds. + * This pauser can operate both synchronously and asynchronously, providing flexibility in thread management. + */ public class MilliPauser implements Pauser { private final AtomicBoolean pausing = new AtomicBoolean(); private long pauseTimeMS; @@ -35,19 +39,32 @@ public class MilliPauser implements Pauser { private transient volatile Thread thread = null; /** - * Pauses for a fixed time + * Constructs a new {@code MilliPauser} with a specified pause time in milliseconds. * - * @param pauseTimeMS the pause time for each loop. + * @param pauseTimeMS the pause time for each pause operation, in milliseconds */ public MilliPauser(long pauseTimeMS) { this.pauseTimeMS = pauseTimeMS; } + /** + * Sets the pause time to a specified duration in milliseconds. + * + * @param pauseTimeMS the new pause time in milliseconds + * @return this {@code MilliPauser} instance for chaining + */ public MilliPauser pauseTimeMS(long pauseTimeMS) { this.pauseTimeMS = pauseTimeMS; return this; } + /** + * Sets the pause time to the minimum of the current or specified duration in milliseconds. + * Ensures that the pause time does not drop below 1 millisecond. + * + * @param pauseTimeMS the proposed minimum pause time in milliseconds + * @return this {@code MilliPauser} instance for chaining + */ public MilliPauser minPauseTimeMS(long pauseTimeMS) { this.pauseTimeMS = Math.min(this.pauseTimeMS, pauseTimeMS); if (this.pauseTimeMS < 1) @@ -55,6 +72,11 @@ public MilliPauser minPauseTimeMS(long pauseTimeMS) { return this; } + /** + * Retrieves the current pause time in milliseconds. + * + * @return the pause time in milliseconds + */ public long pauseTimeMS() { return pauseTimeMS; } @@ -64,26 +86,50 @@ public void reset() { pauseUntilMS = 0; } + /** + * Pauses the current thread for the configured duration using millisecond precision. + */ @Override public void pause() { doPauseMS(pauseTimeMS); } + /** + * Initiates an asynchronous pause that will last for the previously set pause duration. + * Does not block the caller but sets the pauser to be in a pausing state. + */ @Override public void asyncPause() { pauseUntilMS = System.currentTimeMillis() + pauseTimeMS; } + /** + * Checks if the pauser is currently in an asynchronous pausing state. + * + * @return {@code true} if still in the pausing state, {@code false} otherwise + */ @Override public boolean asyncPausing() { return pauseUntilMS > System.currentTimeMillis(); } + /** + * Pauses the current thread for a specified duration in milliseconds. + * + * @param timeout the maximum time to pause in the specified {@code timeUnit} + * @param timeUnit the unit of time for {@code timeout} + * @throws TimeoutException if the pause operation is not completed within the specified timeout + */ @Override public void pause(long timeout, @NotNull TimeUnit timeUnit) throws TimeoutException { doPauseMS(timeUnit.toMillis(timeout)); } + /** + * Helper method to perform the actual pause operation in milliseconds. + * + * @param delayMS the delay in milliseconds to pause the thread + */ void doPauseMS(long delayMS) { long start = System.nanoTime(); thread = Thread.currentThread(); @@ -96,6 +142,9 @@ void doPauseMS(long delayMS) { countPaused++; } + /** + * Unpauses the currently paused thread if it is in a paused state. + */ @Override public void unpause() { final Thread threadSnapshot = this.thread; @@ -103,13 +152,35 @@ public void unpause() { LockSupport.unpark(threadSnapshot); } + /** + * Returns the total time that the thread has been paused, measured in milliseconds. + * + * @return the total paused time in milliseconds + */ @Override public long timePaused() { return timePaused / 1_000_000; } + /** + * Returns the number of times this pauser has been activated to pause the thread. + * + * @return the total count of pauses + */ @Override public long countPaused() { return countPaused; } + + /** + * Provides a string representation of this pauser, identifying the configured pause time. + * + * @return a string representation of this {@code MilliPauser} + */ + @Override + public String toString() { + if (pauseTimeMS == 1) + return "PauserMode.milli"; + return "Pauser.millis(" + pauseTimeMS + ')'; + } } diff --git a/src/main/java/net/openhft/chronicle/threads/Pauser.java b/src/main/java/net/openhft/chronicle/threads/Pauser.java index 3681bac7f..d3c079c2f 100644 --- a/src/main/java/net/openhft/chronicle/threads/Pauser.java +++ b/src/main/java/net/openhft/chronicle/threads/Pauser.java @@ -26,7 +26,12 @@ import java.util.concurrent.TimeoutException; /** - * See also {@link PauserMode} for a mechanism to capture these as serialisable configuration + * Provides a suite of factory methods for creating various {@link Pauser} objects, each offering different strategies for managing thread execution. + * The {@link Pauser} is designed to offer flexible pausing strategies depending on CPU availability and desired execution patterns. + * + *

This interface also defines the methods for managing pause states and conditions within an application's threading model. It includes methods to pause, unpause, reset, and other utilities that influence thread scheduling and execution behaviors.

+ * + *

Refer to {@link PauserMode} for capturing these configurations in a serializable manner.

*/ public interface Pauser { @@ -46,26 +51,33 @@ static boolean getSleepy() { return procs < MIN_PROCESSORS; } + /** + * Returns a {@link Pauser} that either yields, pauses, or does not wait at all, based on system capabilities. + * It selects the most appropriate pauser based on CPU availability and specified minimal busyness. + * + * @param minBusy the minimal busyness period in microseconds before yielding or pausing + * @return the most appropriate {@link Pauser} + */ static Pauser yielding(int minBusy) { SleepyWarning.warnSleepy(); return SLEEPY ? sleepy() : BALANCED ? balanced() - : new TimeoutPauser(minBusy); + : new YieldingPauser(minBusy); } /** * A sleepy pauser which yields for a millisecond, then sleeps for 1 to 20 ms * - * @return a sleepy pauser + * @return a {@link TimingPauser} implementing a sleepy strategy */ static TimingPauser sleepy() { - return new LongPauser(0, 50, 500, Jvm.isDebug() ? 500_000 : 20_000, TimeUnit.MICROSECONDS); + return new LongPauser(0, 50, 500, 20_000, TimeUnit.MICROSECONDS); } /** * A balanced pauser which tries to be busy for short bursts but backs off when idle. * - * @return a balanced pauser + * @return a {@link TimingPauser} implementing a balanced strategy */ static TimingPauser balanced() { return balancedUpToMillis(20); @@ -74,46 +86,48 @@ static TimingPauser balanced() { /** * A balanced pauser which tries to be busy for short bursts but backs off when idle with a limit of max back off. * - * @param millis maximum millis (unless in debug mode) - * @return a balanced pauser + * @param millis the maximum back-off period in milliseconds + * @return a {@link TimingPauser} implementing a balanced strategy with a maximum back-off limit */ static TimingPauser balancedUpToMillis(int millis) { return SLEEPY ? sleepy() - : new LongPauser(MIN_BUSY, 800, 200, Jvm.isDebug() ? 500_000 : millis * 1_000L, TimeUnit.MICROSECONDS); + : new LongPauser(MIN_BUSY, 800, 200, millis * 1000L, TimeUnit.MICROSECONDS); } /** - * Wait a fixed time before running again unless woken + * Creates a {@link MilliPauser} that waits for a fixed duration before resuming execution. * - * @param millis to wait for - * @return a waiting pauser + * @param millis the fixed wait time in milliseconds + * @return a {@link MilliPauser} */ static MilliPauser millis(int millis) { return new MilliPauser(millis); } /** - * A pauser which does not busy spin or yield, it just pauses with a backoff + * Creates a {@link Pauser} that pauses with a back-off strategy, starting at a minimum millisecond interval and potentially increasing to a maximum. * - * @param minMillis starting millis - * @param maxMillis maximum millis - * @return a balanced pauser + * @param minMillis the starting minimum pause duration in milliseconds + * @param maxMillis the maximum pause duration in milliseconds + * @return a {@link Pauser} with a back-off strategy */ static Pauser millis(int minMillis, int maxMillis) { return new LongPauser(0, 0, minMillis, maxMillis, TimeUnit.MILLISECONDS); } /** - * Yielding pauser. simpler than LongPauser but slightly more friendly to other processes + * Provides a simple {@link Pauser} that is more process-friendly by yielding the thread execution. + * + * @return a yielding {@link Pauser} */ static Pauser yielding() { return yielding(2); } /** - * A busy pauser which never waits + * Creates a {@link Pauser} that actively keeps the thread busy and does not employ any waiting strategies. * - * @return a busy/non pauser + * @return a {@link Pauser} that never waits */ @NotNull static Pauser busy() { @@ -123,6 +137,11 @@ static Pauser busy() { : BusyPauser.INSTANCE; } + /** + * Creates a {@link TimingPauser} that keeps the thread busy but also incorporates timed waits. + * + * @return a {@link TimingPauser} that combines busy and timed wait strategies + */ @NotNull static TimingPauser timedBusy() { return SLEEPY ? sleepy() @@ -168,9 +187,9 @@ default void asyncPause() { } /** - * Returns if this Pauser is still asynchronously pausing. + * Checks if the pauser is currently in an asynchronous pause state. * - * @return if this Pauser is still asynchronously pausing + * @return {@code true} if the pauser is still pausing asynchronously, {@code false} otherwise */ default boolean asyncPausing() { return false; diff --git a/src/main/java/net/openhft/chronicle/threads/PauserMode.java b/src/main/java/net/openhft/chronicle/threads/PauserMode.java index bfe0ccec6..3e6db5e55 100644 --- a/src/main/java/net/openhft/chronicle/threads/PauserMode.java +++ b/src/main/java/net/openhft/chronicle/threads/PauserMode.java @@ -20,21 +20,24 @@ import java.util.function.Supplier; /** - * This class contains factory methods for Pausers. + * Provides factory methods for creating various types of {@link Pauser} objects. + * This enum facilitates the creation of different pausing strategies that control thread execution based on CPU availability and desired pausing characteristics. * - * Because {@link Pauser} is not an enum, and implementations are not Marshallable, this makes Pausers more yaml friendly - *

- * The various Pauser modes and their properties can be seen here: - * Pauser Mode features + *

Because {@link Pauser} is not an enum, and implementations are not Marshallable, using this enum helps in making configurations more YAML-friendly.

+ * + *

For detailed descriptions of the different Pauser modes and their specific properties, see: + * Pauser Mode features

*/ public enum PauserMode implements Supplier { /** - * Returns a Supplier providing pausers that will busy wait (spin-wait at 100% CPU) for short - * periods and then backs off when idle for longer periods. + * Provides a {@link Pauser} that busy-waits (spins at 100% CPU) for short durations + * and then backs off when idle for longer periods. * If there are not sufficient available processors, returns {@link #sleepy} depending on the system property "pauser.minProcessors". *

- * See {@link Pauser#balanced()} + * This strategy is ideal for balancing responsiveness with CPU consumption. + * + * @see Pauser#balanced() * @see Runtime#availableProcessors() */ balanced { @@ -43,12 +46,15 @@ public Pauser get() { return Pauser.balanced(); } }, + /** * Returns a Supplier providing pausers this will busy wait (spin-wait at 100% CPU) * if there are sufficient available processors. Otherwise, returns Supplier consistent with * {@link #balanced } or even {@link #sleepy} depending on the system property "pauser.minProcessors". *

- * See {@link Pauser#busy()} + * This pauser is designed for scenarios requiring high responsiveness at the cost of higher CPU usage. + * + * @see Pauser#busy() * @see Runtime#availableProcessors() */ busy { @@ -67,12 +73,13 @@ public boolean monitor() { return false; } }, + /** - * Returns a Supplier providing pausers that sleeps for one millisecond with no back off. + * Provides a {@link Pauser} that sleeps for one millisecond consistently, without any backing off. *

* Milli pausers have long latency times but require minimum CPU resources. - *

- * See {@link Pauser#millis(int)} + * + * @see Pauser#millis(int) */ milli { @Override @@ -80,12 +87,13 @@ public Pauser get() { return Pauser.millis(1); } }, + /** - * Returns a Supplier providing back off pausers that are less aggressive than {@link #balanced}. - *

- * Sleepy pausers have relatively high latency but require limited CPU resources. + * Provides a {@link Pauser} that is less aggressive than {@link #balanced}, using sleep intervals to conserve CPU resources. *

- * See {@link Pauser#sleepy()} + * Suitable for lower-priority tasks where response time is less critical. + * + * @see Pauser#sleepy() */ sleepy { @Override @@ -95,10 +103,11 @@ public Pauser get() { }, /** - * Returns a Supplier similar to {@link #busy} but that provides pausers that supports - * {@link TimingPauser}. + * Similar to {@link #busy} but provides a {@link Pauser} supporting timed waits. *

- * See {@link Pauser#timedBusy()} + * This pauser combines busy-waiting with timed pauses to optimize CPU usage during variable workload conditions. + * + * @see Pauser#timedBusy() */ timedBusy { @Override @@ -112,11 +121,11 @@ public boolean isolcpus() { } }, /** - * Returns a Supplier providing pausers that yields execution (see {@link Thread#yield()}, if there are sufficient - * available processors. Otherwise, returns a Supplier consistent with - * {@link #balanced } or even {@link #sleepy} depending on the system property "pauser.minProcessors". + * Provides a {@link Pauser} that yields thread execution if there are sufficient processors, otherwise it falls back to {@link #balanced} or {@link #sleepy} depending on the system property "pauser.minProcessors". *

- * See {@link Pauser#yielding()} + * It is designed to maintain responsiveness without consuming excessive CPU resources in systems with sufficient processing power. + * + * @see Pauser#yielding() */ yielding { @Override @@ -126,20 +135,20 @@ public Pauser get() { }; /** - * Returns if provided Pausers requires CPU isolation. + * Indicates whether the provided {@link Pauser} is suitable for CPU isolation. * - * @return if provided Pausers requires CPU isolation + * @return {@code true} if CPU isolation is suitable, otherwise {@code false} */ public boolean isolcpus() { return false; } /** - * Returns if provided Pausers can be monitored. + * Indicates whether the provided {@link Pauser} can be monitored. * - * @return if provided Pausers can be monitored + * @return {@code true} if the pauser can be monitored, otherwise {@code false} */ public boolean monitor() { return true; } -} \ No newline at end of file +} diff --git a/src/main/java/net/openhft/chronicle/threads/TimeoutPauser.java b/src/main/java/net/openhft/chronicle/threads/TimeoutPauser.java index 9499f337d..2a5624e90 100644 --- a/src/main/java/net/openhft/chronicle/threads/TimeoutPauser.java +++ b/src/main/java/net/openhft/chronicle/threads/TimeoutPauser.java @@ -17,19 +17,8 @@ */ package net.openhft.chronicle.threads; -import net.openhft.chronicle.core.Jvm; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public class TimeoutPauser implements Pauser, TimingPauser { - private final int minBusy; - private int count = 0; - private long timePaused = 0; - private long countPaused = 0; - private long yieldStart = 0; - private long timeOutStart = Long.MAX_VALUE; +@Deprecated(/* to be removed in x.27, use YieldingPauser */) +public class TimeoutPauser extends YieldingPauser implements TimingPauser { /** * first it will busy wait, then it will yield. @@ -37,71 +26,6 @@ public class TimeoutPauser implements Pauser, TimingPauser { * @param minBusy the min number of times it will go around doing nothing, after this is reached it will then start to yield */ public TimeoutPauser(int minBusy) { - this.minBusy = minBusy; - } - - @Override - public void reset() { - checkYieldTime(); - count = 0; - timeOutStart = Long.MAX_VALUE; - } - - @Override - public void pause() { - ++count; - if (count < minBusy) { - ++countPaused; - Jvm.nanoPause(); - return; - } - - yield0(); - checkYieldTime(); - } - - @Override - public void pause(long timeout, @NotNull TimeUnit timeUnit) throws TimeoutException { - if (timeOutStart == Long.MAX_VALUE) - timeOutStart = System.nanoTime(); - - ++count; - if (count < minBusy) - return; - yield0(); - - if (timeOutStart + timeUnit.toNanos(timeout) - System.nanoTime() < 0) - throw new TimeoutException(); - checkYieldTime(); - } - - private void checkYieldTime() { - if (yieldStart > 0) { - long time = System.nanoTime() - yieldStart; - timePaused += time; - countPaused++; - yieldStart = 0; - } - } - - private void yield0() { - if (yieldStart == 0) - yieldStart = System.nanoTime(); - Thread.yield(); - } - - @Override - public void unpause() { - // Do nothing - } - - @Override - public long timePaused() { - return timePaused / 1_000_000; - } - - @Override - public long countPaused() { - return countPaused; + super(minBusy); } } diff --git a/src/main/java/net/openhft/chronicle/threads/YieldingPauser.java b/src/main/java/net/openhft/chronicle/threads/YieldingPauser.java index 0e759d46c..b57631d86 100644 --- a/src/main/java/net/openhft/chronicle/threads/YieldingPauser.java +++ b/src/main/java/net/openhft/chronicle/threads/YieldingPauser.java @@ -18,19 +18,29 @@ package net.openhft.chronicle.threads; import net.openhft.chronicle.core.Jvm; +import org.jetbrains.annotations.NotNull; -public class YieldingPauser implements Pauser { - private final int minBusy; - private int count = 0; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * This pauser is designed for situations where short bursts of busyness are acceptable before yielding, + * aiming to balance responsiveness with CPU usage. The transition from busy-waiting to yielding helps + * to manage CPU resources more effectively while still allowing the thread to remain responsive. + *

+ */ +public class YieldingPauser implements TimingPauser { + final int minBusy; + int count = 0; private long timePaused = 0; private long countPaused = 0; private long yieldStart = 0; + private long timeOutStart = Long.MAX_VALUE; /** - * first it will busy wait, then it will yield. + * Constructs a {@link YieldingPauser} with a specified threshold for busy waiting. * - * @param minBusy the min number of times it will go around doing nothing, after this is - * reached it will then start to yield + * @param minBusy the minimum number of iterations to perform busy waiting before yielding */ public YieldingPauser(int minBusy) { this.minBusy = minBusy; @@ -40,20 +50,52 @@ public YieldingPauser(int minBusy) { public void reset() { checkYieldTime(); count = 0; + timeOutStart = Long.MAX_VALUE; } + /** + * Pauses the thread by either busy-waiting or yielding, depending on the number of iterations specified by {@code minBusy}. + * Initially, it will busy-wait up to {@code minBusy} iterations; thereafter, it will yield to other threads. + */ @Override public void pause() { ++count; - ++countPaused; if (count < minBusy) { + ++countPaused; Jvm.safepoint(); return; } yield0(); + checkYieldTime(); + } + + /** + * Pauses the thread with a timeout. The pause may end either after busy-waiting and yielding or when the timeout expires, + * whichever comes first. + * + * @param timeout the maximum time to wait before throwing a {@link TimeoutException} + * @param timeUnit the unit of time for the {@code timeout} argument + * @throws TimeoutException if the pause operation exceeds the specified timeout + */ + @Override + public void pause(long timeout, @NotNull TimeUnit timeUnit) throws TimeoutException { + if (timeOutStart == Long.MAX_VALUE) + timeOutStart = System.nanoTime(); + + ++count; + if (count < minBusy) + return; + yield0(); + + if (System.nanoTime() - timeOutStart > timeUnit.toNanos(timeout)) + throw new TimeoutException(); + checkYieldTime(); } - private void checkYieldTime() { + /** + * Records and accumulates the duration of yielding if any, and resets the start time of yielding. + */ + void checkYieldTime() { if (yieldStart > 0) { long time = System.nanoTime() - yieldStart; timePaused += time; @@ -62,7 +104,10 @@ private void checkYieldTime() { } } - private void yield0() { + /** + * Initiates or continues a yielding phase for this pauser. + */ + void yield0() { if (yieldStart == 0) yieldStart = System.nanoTime(); Thread.yield(); @@ -73,13 +118,37 @@ public void unpause() { // Do nothing } + /** + * Returns the total time this pauser has spent yielding, measured in milliseconds. + * + * @return total yielding time in milliseconds + */ @Override public long timePaused() { return timePaused / 1_000_000; } + /** + * Returns the number of times this pauser has been activated, including both busy-wait and yield iterations. + * + * @return the total number of pause activations + */ @Override public long countPaused() { return countPaused; } + + /** + * Provides a string representation of this pauser, which varies based on the {@code minBusy} configuration. + * + * @return a string representation identifying the mode and settings of this pauser + */ + @Override + public String toString() { + if (minBusy == 2) + return "PauserMode.yielding"; + return "YieldingPauser{" + + "minBusy=" + minBusy + + '}'; + } } diff --git a/src/test/java/net/openhft/chronicle/threads/Issue251Test.java b/src/test/java/net/openhft/chronicle/threads/Issue251Test.java new file mode 100644 index 000000000..1be02f25a --- /dev/null +++ b/src/test/java/net/openhft/chronicle/threads/Issue251Test.java @@ -0,0 +1,86 @@ +package net.openhft.chronicle.threads; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class Issue251Test { + @Test + public void toString_timedBusyVariants() { + assertEquals("PauserMode.timedBusy", new BusyTimedPauser().toString()); + assertEquals("PauserMode.timedBusy", PauserMode.timedBusy.get().toString()); + assertEquals("PauserMode.timedBusy", Pauser.timedBusy().toString()); + } + + @Test + public void toString_busyVariants() { + assertEquals("PauserMode.busy", BusyPauser.INSTANCE.toString()); + assertEquals("PauserMode.busy", PauserMode.busy.get().toString()); + assertEquals("PauserMode.busy", Pauser.busy().toString()); + } + + @Test + public void toString_balancedFromMode() { + assertEquals("PauserMode.balanced", PauserMode.balanced.get().toString()); + } + + @Test + public void toString_balanced() { + assertEquals("PauserMode.balanced", Pauser.balanced().toString()); + } + + @Test + public void toString_millis3ms() { + assertEquals("Pauser.millis(3)", Pauser.millis(3).toString()); + } + + @Test + public void toString_milli1and10() { + assertEquals("Pauser.milli(1, 10)", Pauser.millis(1, 10).toString()); + } + + @Test + public void toString_balanced2ms() { + assertEquals("Pauser.balancedUpToMillis(2)", Pauser.balancedUpToMillis(2).toString()); + } + + @Test + public void toString_yieldingNoParams() { + assertEquals("PauserMode.yielding", Pauser.yielding().toString()); + } + + @Test + public void toString_yieldingMinBusy3() { + assertEquals("YieldingPauser{minBusy=3}", Pauser.yielding(3).toString()); + } + + @Test + public void toString_milliMode() { + assertEquals("PauserMode.milli", PauserMode.milli.get().toString()); + } + + @Test + public void toString_yieldingMode() { + assertEquals("PauserMode.yielding", PauserMode.yielding.get().toString()); + } + + @Test + public void toString_sleepyMode() { + assertEquals("PauserMode.sleepy", PauserMode.sleepy.get().toString()); + } + + @Test + public void toString_yieldingMinBusy7() { + assertEquals("YieldingPauser{minBusy=7}", new YieldingPauser(7).toString()); + } + + @Test + public void toString_yieldingMinBusy1() { + assertEquals("YieldingPauser{minBusy=1}", new YieldingPauser(1).toString()); + } + + @Test + public void toString_millis7ms() { + assertEquals("Pauser.millis(7)", new MilliPauser(7).toString()); + } +} diff --git a/src/test/java/net/openhft/chronicle/threads/PauserTimeoutTest.java b/src/test/java/net/openhft/chronicle/threads/PauserTimeoutTest.java index 8f2e10149..e1f06d88b 100644 --- a/src/test/java/net/openhft/chronicle/threads/PauserTimeoutTest.java +++ b/src/test/java/net/openhft/chronicle/threads/PauserTimeoutTest.java @@ -30,13 +30,12 @@ public class PauserTimeoutTest extends ThreadsTestCommon { Pauser.balanced(), Pauser.sleepy(), new BusyTimedPauser(), - new TimeoutPauser(0), + new YieldingPauser(0), new LongPauser(0, 0, 1, 10, TimeUnit.MILLISECONDS), // new MilliPauser(1) }; Pauser[] pausersDontSupportTimeout = { - BusyPauser.INSTANCE, - new YieldingPauser(0)}; + BusyPauser.INSTANCE}; @Test public void pausersSupportTimeout() { diff --git a/src/test/java/net/openhft/chronicle/threads/TimeoutPauserTest.java b/src/test/java/net/openhft/chronicle/threads/YieldingPauserTest.java similarity index 94% rename from src/test/java/net/openhft/chronicle/threads/TimeoutPauserTest.java rename to src/test/java/net/openhft/chronicle/threads/YieldingPauserTest.java index 9ff19692a..a318f0848 100644 --- a/src/test/java/net/openhft/chronicle/threads/TimeoutPauserTest.java +++ b/src/test/java/net/openhft/chronicle/threads/YieldingPauserTest.java @@ -26,12 +26,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; -public class TimeoutPauserTest extends ThreadsTestCommon { +public class YieldingPauserTest extends ThreadsTestCommon { @Test public void pause() { final int pauseTimeMillis = 100; - final TimeoutPauser tp = new TimeoutPauser(pauseTimeMillis); + final YieldingPauser tp = new YieldingPauser(pauseTimeMillis); for (int i = 0; i < 10; i++) { final long start = System.currentTimeMillis(); while (true) {