Skip to content

Commit

Permalink
Threads/issues/251 (#252)
Browse files Browse the repository at this point in the history
* Give pausers a description so they can be meaningfully logged as text. Added Javadoc to Pausers

* Give pausers a description so they can be meaningfully logged as text. Added Javadoc to Pausers

* Use less magic numbers to make the toString() more robust.

* Custom toString() for Pausers to be descriptive, Fixes #251
  • Loading branch information
peter-lawrey authored May 13, 2024
1 parent f1302ee commit e19ba89
Show file tree
Hide file tree
Showing 12 changed files with 484 additions and 150 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
<configuration>
<referenceVersion>2.25ea0</referenceVersion>
<artifactsURI>https://teamcity.chronicle.software/repository/download</artifactsURI>
<binaryCompatibilityPercentageRequired>84.0</binaryCompatibilityPercentageRequired>
<binaryCompatibilityPercentageRequired>81.0</binaryCompatibilityPercentageRequired>
</configuration>
</execution>
</executions>
Expand Down
48 changes: 48 additions & 0 deletions src/main/java/net/openhft/chronicle/threads/BusyPauser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>Because it never actually suspends the thread, most operations related to state management (like pausing, unpausing, and timeout handling) are unsupported or no-op.</p>
*/
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";
}
}
41 changes: 39 additions & 2 deletions src/main/java/net/openhft/chronicle/threads/BusyTimedPauser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";
}
}

80 changes: 76 additions & 4 deletions src/main/java/net/openhft/chronicle/threads/LongPauser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* 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;
Expand All @@ -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;

Expand Down Expand Up @@ -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 {
Expand All @@ -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();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}
Loading

0 comments on commit e19ba89

Please sign in to comment.