Class SuppressableCallback<T>

java.lang.Object
utilities.util.SuppressableCallback<T>
Type Parameters:
T - the type of return value, often Void

public class SuppressableCallback<T> extends Object
A mechanism for preventing callback loops, stack overflows, event storms, etc.

This thing really is a terrible hack, but it becomes necessary when the cause of an event cannot be reliably determined. In cases where the callback (lacking a cause parameter) is expected to be invoked on the same stack as the method calling, this mechanism can optionally suppress that callback.

Suppression is implemented on a per-thread basis, so that suppression requests are only effective when the callback is in fact on the same stack as the request.

A common use case is for a method to suppress any recursive calls, i.e., it is self suppressing, e.g.:


 private final SuppressableCallback<Void> cbDoSomething = new SuppressableCallback<>();
 
 public boolean doSomething() {
 	cbDoSomething.invoke(__ -> {
 		try (Suppression supp = cbDoSomething.supppress(null)) {
 			// do the thing
 			return doAnotherThing();
 		}
 	}, false);
 }
 
 public boolean doAnotherThing() {
 	// do the other thing
 	return doSomething();
 }
 

This example is very trivial, but this sort of thing can easily happen in event driven programming. Consider the case where a state change causes an update to a table selection, which fires the selection changed event, in turn requesting the same state change. Checking for equality of the requested state with the current state can resolve some cases, but such checks are thwarted when any two events requesting unequal state get queued on an event thread, resulting in unending oscillation between the requested states. A more robust solution demands we know the cause(s) of events. For now, the solution is to suppress event firing whenever a state change is due to receiving an event.

  • Constructor Details

    • SuppressableCallback

      public SuppressableCallback()
  • Method Details

    • suppress

      public SuppressableCallback.Suppression suppress(T value)
      Suppress this callback, providing the given value as information

      This should almost always be used in a try-with-resources block. The only exception is perhaps to wrap this in another AutoCloseable. The information is usually one of the parameters, or perhaps a n-tuple of all parameters. For many use cases, null is all that is needed. The information is needed if the callback would like to make the suppression decision based on that information, e.g., is this callback telling me to "go to" a place I'm already "going to"?

      Parameters:
      value - the value
      Returns:
      a handle to the request
    • invoke

      public void invoke(Runnable callback)
      Run the given callback unless it has been suppressed

      The values on the stack of suppression requests do not matter. If there's a request, the callback is suppressed.

      Parameters:
      callback - the callback
    • invoke

      public <R> R invoke(Supplier<R> callback, R fallback)
      Run the given callback, returning its value, unless it has been suppressed

      Works like invoke(Runnable), except that the callback can return a value. If the callback is suppressed, a fallback value is returned instead.

      Type Parameters:
      R - the return type
      Parameters:
      callback - the callback
      fallback - a fallback value in case of suppression
      Returns:
      the value from the callback, or the fallback value
    • invokeWithTop

      public void invokeWithTop(Consumer<T> callback)
      Run the given callback with the topmost suppression value or null if not suppressed

      The callback is always invoked, allow it to decide what actions to take (or not take) based on the given value. Not for a SuppressableCallback<T>, this method is useless, as the provided value will always be null, whether or not suppressed.

      Parameters:
      callback - the callback
    • invokeWithTop

      public <R> R invokeWithTop(Function<T,R> callback)
      Run the given callback with the topmost value, returning its value

      This works like invokeWithTop(Consumer), except that the callback can return a value. Note that a fallback parameter is not taken, since the callback is always invoked. The callback should implement its own fallback logic.

      Type Parameters:
      R - the return type
      Parameters:
      callback - the callback
      Returns:
      the value from the callback
    • invokeWithStack

      public void invokeWithStack(Consumer<List<T>> callback)
      Run the given callback with the values from the stack of suppression requests

      The callback is always invoked, allowing it to decide what actions to take (or not take) based on the values present in the stack.

      Parameters:
      callback - a method to receive the stack
    • invokeWithStack

      public <R> R invokeWithStack(Function<List<T>,R> callback)
      Run the given callback with the stack, returning its value

      This works like invokeWithStack(Consumer), except that the callback can return a value. Note that a fallback parameter is not taken, since the callback is always invoked. The callback should implement its own fallback logic.

      Type Parameters:
      R - the return type
      Parameters:
      callback - the callback
      Returns:
      the value from the callback