Skip to content

Commit d2bc9c3

Browse files
authored
feat: no op scheduler (#12530)
Signed-off-by: Cody Littley <[email protected]>
1 parent 426abc2 commit d2bc9c3

File tree

14 files changed

+516
-63
lines changed

14 files changed

+516
-63
lines changed

platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/StandardWiringModel.java

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static com.swirlds.common.wiring.model.internal.ModelVertexMetaType.SCHEDULER;
2020
import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType.DIRECT;
2121
import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType.DIRECT_THREADSAFE;
22+
import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType.NO_OP;
2223
import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType.SEQUENTIAL_THREAD;
2324

2425
import com.swirlds.common.context.PlatformContext;
@@ -252,6 +253,12 @@ private void addVertexForUnsolderedInputWires(final boolean moreMystery) {
252253
*/
253254
public void registerScheduler(@NonNull final TaskScheduler<?> scheduler, @Nullable final String hyperlink) {
254255
throwIfStarted();
256+
257+
if (scheduler.getType() == NO_OP) {
258+
// Ignore no-op schedulers.
259+
return;
260+
}
261+
255262
registerVertex(scheduler.getName(), scheduler.getType(), hyperlink, scheduler.isInsertionBlocking());
256263
if (scheduler.getType() == SEQUENTIAL_THREAD) {
257264
threadSchedulers.add((SequentialThreadTaskScheduler<?>) scheduler);

platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/schedulers/TaskScheduler.java

+15-2
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ protected TaskScheduler(
9292
this.squelcher = new ThrowingSquelcher();
9393
}
9494

95-
primaryOutputWire = new StandardOutputWire<>(model, name);
95+
primaryOutputWire = buildPrimaryOutputWire(model, name);
9696
this.insertionIsBlocking = insertionIsBlocking;
9797
}
9898

@@ -105,10 +105,23 @@ protected TaskScheduler(
105105
* @return the input wire
106106
*/
107107
@NonNull
108-
public final <I> BindableInputWire<I, OUT> buildInputWire(@NonNull final String name) {
108+
public <I> BindableInputWire<I, OUT> buildInputWire(@NonNull final String name) {
109109
return new BindableInputWire<>(model, this, name);
110110
}
111111

112+
/**
113+
* Build the primary output wire for this scheduler.
114+
*
115+
* @param model the wiring model that contains this scheduler
116+
* @param name the name of this scheduler
117+
* @return the primary output wire
118+
*/
119+
@NonNull
120+
protected StandardOutputWire<OUT> buildPrimaryOutputWire(
121+
@NonNull final StandardWiringModel model, @NonNull final String name) {
122+
return new StandardOutputWire<>(model, name);
123+
}
124+
112125
/**
113126
* Get the default output wire for this task scheduler. Sometimes referred to as the "primary" output wire. All data
114127
* returned by handlers is passed ot this output wire. Calling this method more than once will always return the

platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/schedulers/builders/TaskSchedulerBuilder.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType.DIRECT;
2020
import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType.DIRECT_THREADSAFE;
21+
import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType.NO_OP;
2122
import static com.swirlds.logging.legacy.LogMarker.EXCEPTION;
2223

2324
import com.swirlds.common.context.PlatformContext;
@@ -34,6 +35,7 @@
3435
import com.swirlds.common.wiring.schedulers.TaskScheduler;
3536
import com.swirlds.common.wiring.schedulers.internal.ConcurrentTaskScheduler;
3637
import com.swirlds.common.wiring.schedulers.internal.DirectTaskScheduler;
38+
import com.swirlds.common.wiring.schedulers.internal.NoOpTaskScheduler;
3739
import com.swirlds.common.wiring.schedulers.internal.SequentialTaskScheduler;
3840
import com.swirlds.common.wiring.schedulers.internal.SequentialThreadTaskScheduler;
3941
import edu.umd.cs.findbugs.annotations.NonNull;
@@ -362,7 +364,9 @@ private static ObjectCounter combineCounters(
362364
*/
363365
@NonNull
364366
private Counters buildCounters() {
365-
final ObjectCounter innerCounter;
367+
if (type == NO_OP) {
368+
return new Counters(NoOpObjectCounter.getInstance(), NoOpObjectCounter.getInstance());
369+
}
366370

367371
// If we need to enforce a maximum capacity, we have no choice but to use a backpressure object counter.
368372
//
@@ -375,6 +379,7 @@ private Counters buildCounters() {
375379
// In all other cases, better to use a no-op counter. Counters have overhead, and if we don't need one
376380
// then we shouldn't use one.
377381

382+
final ObjectCounter innerCounter;
378383
if (unhandledTaskCapacity != UNLIMITED_CAPACITY && type != DIRECT && type != DIRECT_THREADSAFE) {
379384
innerCounter = new BackpressureObjectCounter(name, unhandledTaskCapacity, sleepDuration);
380385
} else if (unhandledTaskMetricEnabled || flushingEnabled) {
@@ -393,7 +398,7 @@ private Counters buildCounters() {
393398
*/
394399
@NonNull
395400
private FractionalTimer buildBusyTimer() {
396-
if (!busyFractionMetricEnabled) {
401+
if (!busyFractionMetricEnabled || type == NO_OP) {
397402
return NoOpFractionalTimer.getInstance();
398403
}
399404
if (type == TaskSchedulerType.CONCURRENT) {
@@ -410,6 +415,10 @@ private FractionalTimer buildBusyTimer() {
410415
private void registerMetrics(
411416
@Nullable final ObjectCounter unhandledTaskCounter, @NonNull final FractionalTimer busyFractionTimer) {
412417

418+
if (type == NO_OP) {
419+
return;
420+
}
421+
413422
if (unhandledTaskMetricEnabled) {
414423
Objects.requireNonNull(unhandledTaskCounter);
415424

@@ -495,6 +504,7 @@ public TaskScheduler<O> build() {
495504
squelchingEnabled,
496505
busyFractionTimer,
497506
true);
507+
case NO_OP -> new NoOpTaskScheduler<>(model, name, type, flushingEnabled, squelchingEnabled);
498508
};
499509

500510
model.registerScheduler(scheduler, hyperlink);

platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/schedulers/builders/TaskSchedulerConfiguration.java

+7
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ public record TaskSchedulerConfiguration(
4444
@Nullable Boolean flushingEnabled,
4545
@Nullable Boolean squelchingEnabled) {
4646

47+
/**
48+
* This configuration is for a no-op task scheduler. It is not necessary to use this constant for a no-op task
49+
* scheduler, but it is provided for convenience.
50+
*/
51+
public static final TaskSchedulerConfiguration NO_OP_CONFIGURATION =
52+
new TaskSchedulerConfiguration(TaskSchedulerType.NO_OP, 0L, false, false, false, false);
53+
4754
/**
4855
* Parse a string representation of a task scheduler configuration.
4956
* <p>

platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/schedulers/builders/TaskSchedulerType.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,12 @@ public enum TaskSchedulerType {
7373
* There is no enforcement mechanism in the framework to ensure that the task is actually threadsafe. It is advised
7474
* that this scheduler type be used with caution, as improper use can lead to can lead to nasty race conditions.
7575
*/
76-
DIRECT_THREADSAFE
76+
DIRECT_THREADSAFE,
77+
/**
78+
* A scheduler that does nothing. All wires into and out of this scheduler are effectively non-existent at runtime.
79+
* Useful for testing and debugging, or for when the ability to toggle a scheduler on/off via configuration is
80+
* desired. For a deeper dive into why this is a useful concept, see
81+
* <a href='https://www.youtube.com/watch?v=6h58uT_BGV4'>this explanation</a>.
82+
*/
83+
NO_OP
7784
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright (C) 2024 Hedera Hashgraph, LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.swirlds.common.wiring.schedulers.internal;
18+
19+
import com.swirlds.common.wiring.model.internal.StandardWiringModel;
20+
import com.swirlds.common.wiring.schedulers.TaskScheduler;
21+
import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType;
22+
import com.swirlds.common.wiring.wires.input.BindableInputWire;
23+
import com.swirlds.common.wiring.wires.input.NoOpInputWire;
24+
import com.swirlds.common.wiring.wires.output.NoOpOutputWire;
25+
import com.swirlds.common.wiring.wires.output.StandardOutputWire;
26+
import edu.umd.cs.findbugs.annotations.NonNull;
27+
import java.util.Objects;
28+
import java.util.function.Consumer;
29+
30+
/**
31+
* A no-op task scheduler that does nothing.
32+
*
33+
* @param <OUT> the output type of the scheduler (use {@link Void} for a task scheduler with no output type). This is
34+
* just to appease the compiler, as this scheduler never produces output.
35+
*/
36+
public class NoOpTaskScheduler<OUT> extends TaskScheduler<OUT> {
37+
38+
private final StandardWiringModel model;
39+
40+
/**
41+
* Constructor.
42+
*
43+
* @param model the wiring model containing this task scheduler
44+
* @param name the name of the task scheduler
45+
* @param type the type of task scheduler
46+
* @param flushEnabled if true, then {@link #flush()} will be enabled, otherwise it will throw.
47+
* @param squelchingEnabled if true, then squelching will be enabled, otherwise trying to squelch will throw.
48+
*/
49+
public NoOpTaskScheduler(
50+
@NonNull final StandardWiringModel model,
51+
@NonNull final String name,
52+
@NonNull final TaskSchedulerType type,
53+
final boolean flushEnabled,
54+
final boolean squelchingEnabled) {
55+
super(model, name, type, flushEnabled, squelchingEnabled, false);
56+
57+
this.model = Objects.requireNonNull(model);
58+
}
59+
60+
/**
61+
* {@inheritDoc}
62+
*/
63+
@Override
64+
public long getUnprocessedTaskCount() {
65+
return 0;
66+
}
67+
68+
/**
69+
* {@inheritDoc}
70+
*/
71+
@Override
72+
public void flush() {
73+
throwIfFlushDisabled();
74+
}
75+
76+
/**
77+
* {@inheritDoc}
78+
*/
79+
@Override
80+
protected void put(@NonNull final Consumer<Object> handler, @NonNull final Object data) {
81+
throw new UnsupportedOperationException(
82+
"Data should have been discarded before being sent to this no-op scheduler");
83+
}
84+
85+
/**
86+
* {@inheritDoc}
87+
*/
88+
@Override
89+
protected boolean offer(@NonNull final Consumer<Object> handler, @NonNull final Object data) {
90+
throw new UnsupportedOperationException(
91+
"Data should have been discarded before being sent to this no-op scheduler");
92+
}
93+
94+
/**
95+
* {@inheritDoc}
96+
*/
97+
@Override
98+
protected void inject(@NonNull final Consumer<Object> handler, @NonNull final Object data) {
99+
throw new UnsupportedOperationException(
100+
"Data should have been discarded before being sent to this no-op scheduler");
101+
}
102+
103+
/**
104+
* {@inheritDoc}
105+
*/
106+
@NonNull
107+
@Override
108+
protected StandardOutputWire<OUT> buildPrimaryOutputWire(
109+
@NonNull final StandardWiringModel model, @NonNull final String name) {
110+
return new NoOpOutputWire<>(model, getName());
111+
}
112+
113+
/**
114+
* {@inheritDoc}
115+
*/
116+
@NonNull
117+
@Override
118+
public <T> StandardOutputWire<T> buildSecondaryOutputWire() {
119+
return new NoOpOutputWire<>(model, getName());
120+
}
121+
122+
/**
123+
* {@inheritDoc}
124+
*/
125+
@NonNull
126+
@Override
127+
public <I> BindableInputWire<I, OUT> buildInputWire(@NonNull final String name) {
128+
return new NoOpInputWire<>(model, this, name);
129+
}
130+
}

platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/input/Bindable.java

-16
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,6 @@ public interface Bindable<IN, OUT> {
3737
*/
3838
void bindConsumer(@NonNull Consumer<IN> handler);
3939

40-
/**
41-
* Do not use this method.
42-
* <p>
43-
* Calling bindConsumer() on a function that returns the output type is explicitly not supported. This method is a
44-
* "trap" to catch situations where bindConsumer() is called when bind() should be called instead. Java is happy to
45-
* turn a Function into a Consumer if it can't match the types, and this is behavior we don't want to support.
46-
*
47-
* @param handler the wrong type of handler
48-
* @deprecated to show that this method should not be used. There are no plans to actually remove this method.
49-
*/
50-
@Deprecated
51-
default void bindConsumer(@NonNull final Function<IN, OUT> handler) {
52-
throw new UnsupportedOperationException(
53-
"Do not call bindConsumer() with a function that returns a value. Call bind() instead.");
54-
}
55-
5640
/**
5741
* Bind this object to a handler.
5842
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright (C) 2024 Hedera Hashgraph, LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.swirlds.common.wiring.wires.input;
18+
19+
import com.swirlds.common.wiring.model.internal.StandardWiringModel;
20+
import com.swirlds.common.wiring.schedulers.TaskScheduler;
21+
import edu.umd.cs.findbugs.annotations.NonNull;
22+
import java.util.function.Consumer;
23+
import java.util.function.Function;
24+
25+
/**
26+
* An input wire that doesn't actually do anything. When asked to bind a handler, it does nothing. When asked to insert
27+
* data, it does nothing.
28+
*
29+
* @param <IN> the type of data that passes into the wire
30+
* @param <OUT> the type of the primary output wire for the scheduler that is associated with this object
31+
*/
32+
public class NoOpInputWire<IN, OUT> extends BindableInputWire<IN, OUT> {
33+
/**
34+
* Constructor.
35+
*
36+
* @param model the wiring model containing this input wire
37+
* @param taskScheduler the scheduler to insert data into
38+
* @param name the name of the input wire
39+
*/
40+
public NoOpInputWire(
41+
@NonNull final StandardWiringModel model,
42+
@NonNull final TaskScheduler<OUT> taskScheduler,
43+
@NonNull final String name) {
44+
super(model, taskScheduler, name);
45+
}
46+
47+
/**
48+
* {@inheritDoc}
49+
*/
50+
@Override
51+
public void bindConsumer(@NonNull final Consumer<IN> handler) {
52+
// intentional no-op
53+
}
54+
55+
/**
56+
* {@inheritDoc}
57+
*/
58+
@Override
59+
public void bind(@NonNull final Function<IN, OUT> handler) {
60+
// intentional no-op
61+
}
62+
63+
/**
64+
* {@inheritDoc}
65+
*/
66+
@Override
67+
public void put(@NonNull final IN data) {
68+
// intentional no-op
69+
}
70+
71+
/**
72+
* {@inheritDoc}
73+
*/
74+
@Override
75+
public boolean offer(@NonNull final IN data) {
76+
return true;
77+
}
78+
79+
/**
80+
* {@inheritDoc}
81+
*/
82+
@Override
83+
public void inject(@NonNull final IN data) {
84+
// intentional no-op
85+
}
86+
}

0 commit comments

Comments
 (0)