Skip to content

Commit 9603078

Browse files
committed
Introduce Node.Environment and Node.Settings
Should have done this ages ago.
1 parent 13f8ae9 commit 9603078

7 files changed

Lines changed: 225 additions & 162 deletions

File tree

framework/src/dslabs/framework/Node.java

Lines changed: 125 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222

2323
package dslabs.framework;
2424

25+
import com.google.common.collect.ImmutableList;
2526
import java.io.Serializable;
2627
import java.lang.reflect.InvocationTargetException;
2728
import java.lang.reflect.Method;
29+
import java.time.Duration;
2830
import java.util.Arrays;
2931
import java.util.Collection;
3032
import java.util.Collections;
@@ -35,17 +37,13 @@
3537
import java.util.Objects;
3638
import java.util.Optional;
3739
import java.util.concurrent.ConcurrentHashMap;
38-
import java.util.function.Consumer;
3940
import java.util.logging.Level;
4041
import lombok.EqualsAndHashCode;
4142
import lombok.NonNull;
4243
import lombok.SneakyThrows;
4344
import lombok.ToString;
4445
import lombok.extern.java.Log;
45-
import org.apache.commons.lang3.tuple.ImmutablePair;
46-
import org.apache.commons.lang3.tuple.ImmutableTriple;
47-
import org.apache.commons.lang3.tuple.Pair;
48-
import org.apache.commons.lang3.tuple.Triple;
46+
import org.checkerframework.checker.nullness.qual.Nullable;
4947

5048
/**
5149
* Nodes are the basic unit of computation. They can send and receive {@link Message}s, set and
@@ -104,20 +102,78 @@
104102
@EqualsAndHashCode(of = {"subNodes"})
105103
@ToString(of = {"address", "subNodes"})
106104
public abstract class Node implements Serializable {
105+
106+
/**
107+
* An {@code Environment} is how a {@link Node} sends messages to other nodes and sets timers. It
108+
* also can handle exceptions which are thrown during the handling of events.
109+
*
110+
* @hidden
111+
*/
112+
public interface Environment {
113+
void send(Message message, Address from, Address to);
114+
115+
default void broadcast(Message message, Address from, ImmutableList<Address> to) {
116+
for (Address address : to) {
117+
send(message, from, address);
118+
}
119+
}
120+
121+
default void set(Timer timer, Address destination, Duration duration) {
122+
set(timer, destination, duration, duration);
123+
}
124+
125+
void set(Timer timer, Address destination, Duration minDuration, Duration maxDuration);
126+
127+
/**
128+
* Possibly handles a throwable which was thrown during the execution of an event handler.
129+
*
130+
* @param throwable The exception which was thrown during the handling of an event.
131+
* @return Whether the throwable was consumed. If {@code false}, {@code throwable} should be
132+
* thrown.
133+
*/
134+
default boolean handleThrowable(Throwable throwable) {
135+
return false;
136+
}
137+
}
138+
139+
/**
140+
* @param logExceptions Whether to log exceptions thrown by the node during message and timer
141+
* handling, in addition to sending them to {@link Environment#handleThrowable}.
142+
* @hidden
143+
*/
144+
public record Settings(boolean logExceptions) {
145+
Settings() {
146+
this(true);
147+
}
148+
}
149+
107150
private static final Map<Class<? extends Node>, Map<String, Optional<Method>>> methods =
108151
new ConcurrentHashMap<>();
109152

110153
/** This Node's address. */
111154
@VizIgnore @NonNull private final Address address;
112155

113-
private transient Consumer<Triple<Address, Address, Message>> messageAdder;
114-
private transient Consumer<Triple<Address, Address[], Message>> batchMessageAdder;
115-
private transient Consumer<Triple<Address, Timer, Pair<Integer, Integer>>> timerAdder;
116-
private transient Consumer<Throwable> throwableCatcher;
117-
private transient Boolean logExceptions = true;
156+
/*
157+
* INVARIANT: (environment == null && settings == null) || parentNode == null
158+
*
159+
* We do not store these as a sealed type, as environment and settings must be transient, while
160+
* parentNode must not be transient.
161+
*/
162+
163+
/**
164+
* This node's {@link Environment}, if this node is a root node (i.e., not a sub-node). If this
165+
* node is a sub-node, this must be {@code null}.
166+
*/
167+
private transient @Nullable Environment environment;
168+
169+
/**
170+
* This node's current {@link Settings}, if this node is a root node (i.e., not a sub-node). If
171+
* this node is a sub-node, this must be {@code null}.
172+
*/
173+
private transient @Nullable Settings settings;
118174

119-
/** The Node's parent (or null if this Node is the root Node in the hierarchy). */
120-
@VizIgnore private Node parentNode;
175+
/** The Node's parent (or {@code null} if this Node is a root node). */
176+
@VizIgnore private @Nullable Node parentNode;
121177

122178
/**
123179
* This Node's sub-Nodes, indexed by their ID. Sub-Nodes must have a {@link SubAddress} composed
@@ -134,6 +190,26 @@ protected Node(@NonNull Address address) {
134190
this.address = address;
135191
}
136192

193+
/** The {@link Environment} for this Node's root Node. */
194+
private @Nullable Environment environment() {
195+
if (parentNode != null) {
196+
return parentNode.environment();
197+
} else {
198+
return environment;
199+
}
200+
}
201+
202+
/** The {@link Settings} for this Node's root Node. */
203+
private Settings settings() {
204+
if (parentNode != null) {
205+
return parentNode.settings();
206+
} else if (settings == null) {
207+
return new Settings();
208+
} else {
209+
return settings;
210+
}
211+
}
212+
137213
/**
138214
* Takes any initialization steps necessary (potentially sending {@link Message}s and setting
139215
* {@link Timer}s).
@@ -153,9 +229,7 @@ protected final void addSubNode(@NonNull Node subNode) {
153229
"Attempting to add subNode with address that isn't a subAddress of this node.");
154230
}
155231

156-
if (subNode.messageAdder != null
157-
|| subNode.batchMessageAdder != null
158-
|| subNode.timerAdder != null) {
232+
if (subNode.environment != null) {
159233
throw new IllegalArgumentException(
160234
"Cannot configure node; already configured as stand-alone.");
161235
}
@@ -167,6 +241,7 @@ protected final void addSubNode(@NonNull Node subNode) {
167241
}
168242

169243
subNode.parentNode = this;
244+
subNode.settings = null;
170245
subNodes.put(subAddress.id(), subNode);
171246
}
172247

@@ -261,24 +336,17 @@ private void send(Message message, Address from, Address to) {
261336
return;
262337
}
263338

264-
// If this Node is a sub-Node, use the parent to send the message.
265-
if (parentNode != null && messageAdder == null && batchMessageAdder == null) {
266-
parentNode.send(message, from, to);
267-
return;
268-
}
269-
270-
LOG.finest(() -> String.format("MessageSend(%s -> %s, %s)", from, to, message));
271-
272-
if (messageAdder != null) {
273-
messageAdder.accept(new ImmutableTriple<>(from, to, message));
274-
} else if (batchMessageAdder != null) {
275-
batchMessageAdder.accept(new ImmutableTriple<>(from, new Address[] {to}, message));
276-
} else {
339+
Environment env = environment();
340+
if (env == null) {
277341
LOG.severe(
278342
String.format(
279343
"Attempting to send %s from %s to %s before node configured, not sending",
280344
message, from, to));
345+
return;
281346
}
347+
348+
LOG.finest(() -> String.format("MessageSend(%s -> %s, %s)", from, to, message));
349+
env.send(message, from, to);
282350
}
283351

284352
private void broadcast(Message message, Address from, Address[] to) {
@@ -303,27 +371,18 @@ private void broadcast(Message message, Address from, Address[] to) {
303371
}
304372
}
305373

306-
// If this Node is a sub-Node, use the parent to broadcast the message.
307-
if (parentNode != null && messageAdder == null && batchMessageAdder == null) {
308-
parentNode.broadcast(message, from, to);
309-
return;
310-
}
311-
312-
LOG.finest(
313-
() -> String.format("MessageSend(%s -> %s, %s)", from, Arrays.toString(to), message));
314-
315-
if (batchMessageAdder != null) {
316-
batchMessageAdder.accept(new ImmutableTriple<>(from, to, message));
317-
} else if (messageAdder != null) {
318-
for (Address a : to) {
319-
messageAdder.accept(new ImmutableTriple<>(from, a, message));
320-
}
321-
} else {
374+
Environment env = environment();
375+
if (env == null) {
322376
LOG.severe(
323377
String.format(
324378
"Attempting to send %s from %s to %s before node configured, not sending",
325379
message, from, Arrays.toString(to)));
380+
return;
326381
}
382+
383+
LOG.finest(
384+
() -> String.format("MessageSend(%s -> %s, %s)", from, Arrays.toString(to), message));
385+
env.broadcast(message, from, ImmutableList.copyOf(to));
327386
}
328387

329388
private void set(Timer timer, int minTimerLengthMillis, int maxTimerLengthMillis, Address from) {
@@ -332,23 +391,20 @@ private void set(Timer timer, int minTimerLengthMillis, int maxTimerLengthMillis
332391
return;
333392
}
334393

335-
// If this Node is a sub-Node, use the parent to set the timer.
336-
if (parentNode != null && timerAdder == null) {
337-
parentNode.set(timer, minTimerLengthMillis, maxTimerLengthMillis, from);
338-
return;
339-
}
340-
341-
LOG.finest(() -> String.format("TimerSet(-> %s, %s)", from, timer));
342-
343-
if (timerAdder != null) {
344-
timerAdder.accept(
345-
new ImmutableTriple<>(
346-
from, timer, new ImmutablePair<>(minTimerLengthMillis, maxTimerLengthMillis)));
347-
} else {
394+
Environment env = environment();
395+
if (env == null) {
348396
LOG.severe(
349397
String.format(
350398
"Attempting to set %s from %s before node configured, not setting", timer, from));
399+
return;
351400
}
401+
402+
LOG.finest(() -> String.format("TimerSet(-> %s, %s)", from, timer));
403+
env.set(
404+
timer,
405+
from,
406+
Duration.ofMillis(minTimerLengthMillis),
407+
Duration.ofMillis(maxTimerLengthMillis));
352408
}
353409

354410
private Object handleMessageInternal(
@@ -544,7 +600,7 @@ private Object callMethod(
544600
throw t;
545601
}
546602

547-
if (logExceptions) {
603+
if (settings().logExceptions) {
548604
LOG.log(
549605
Level.SEVERE,
550606
String.format(
@@ -553,8 +609,9 @@ private Object callMethod(
553609
t);
554610
}
555611

556-
if (throwableCatcher != null) {
557-
throwableCatcher.accept(t);
612+
Environment env = environment();
613+
if (env == null || !env.handleThrowable(t)) {
614+
throw t;
558615
}
559616
}
560617

@@ -564,39 +621,19 @@ private Object callMethod(
564621
/**
565622
* <b>Do not use.</b> Only used by testing framework.
566623
*
567-
* <p>Configures the node to allow it to send messages and set timers.
624+
* <p>Configures the node to allow it to send messages and set timers. Should only be set on a
625+
* root Node.
568626
*
569-
* <p>At least one of {@code messageAdder}/{@code batchMessageAdder} must be non-null.
570-
*
571-
* @param messageAdder a function which consumes messages sent by the node, or {@code null} to
572-
* have the node send all messages to the {@code batchMessageAdder}
573-
* @param batchMessageAdder a function which consumes messages sent by the node to multiple
574-
* recipients, or {@code null} to have the node send all messages to the {@code messageAdder}
575-
* @param timerAdder a function which consumes timers set by the node
576-
* @param throwableCatcher a function which consumes exceptions thrown by the node during message
577-
* and timer handling, or {@code null} to have the node drop exceptions
578-
* @param logExceptions whether to log exceptions thrown by the node during message and timer
579-
* handling, in addition to sending them to the {@code throwableCatcher}
627+
* @param environment The environment this node should use.
628+
* @param settings The settings for this node.
580629
* @hidden
581630
*/
582-
public void config(
583-
Consumer<Triple<Address, Address, Message>> messageAdder,
584-
Consumer<Triple<Address, Address[], Message>> batchMessageAdder,
585-
@NonNull Consumer<Triple<Address, Timer, Pair<Integer, Integer>>> timerAdder,
586-
Consumer<Throwable> throwableCatcher,
587-
boolean logExceptions) {
631+
public void config(Environment environment, Settings settings) {
588632
if (parentNode != null) {
633+
// TODO: Throw IllegalStateException?
589634
LOG.severe("Cannot configure Node already configured as sub-Node.");
590635
}
591-
592-
if (messageAdder == null && batchMessageAdder == null) {
593-
LOG.severe("Cannot configure Node without messageAdder or batchMessageAdder.");
594-
}
595-
596-
this.messageAdder = messageAdder;
597-
this.batchMessageAdder = batchMessageAdder;
598-
this.timerAdder = timerAdder;
599-
this.throwableCatcher = throwableCatcher;
600-
this.logExceptions = logExceptions;
636+
this.environment = environment;
637+
this.settings = settings;
601638
}
602639
}

framework/tst/dslabs/framework/testing/ClientWorker.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,13 @@
3636
import java.util.ArrayList;
3737
import java.util.List;
3838
import java.util.Objects;
39-
import java.util.function.Consumer;
4039
import javax.annotation.Nullable;
4140
import lombok.EqualsAndHashCode;
4241
import lombok.Getter;
4342
import lombok.NonNull;
4443
import lombok.ToString;
4544
import org.apache.commons.lang3.tuple.ImmutablePair;
4645
import org.apache.commons.lang3.tuple.Pair;
47-
import org.apache.commons.lang3.tuple.Triple;
4846

4947
@EqualsAndHashCode(
5048
of = {"client", "results"},
@@ -297,14 +295,9 @@ public synchronized void onTimer(Timer timer, Address destination) {
297295
}
298296

299297
@Override
300-
public void config(
301-
Consumer<Triple<Address, Address, Message>> messageAdder,
302-
Consumer<Triple<Address, Address[], Message>> batchMessageAdder,
303-
Consumer<Triple<Address, Timer, Pair<Integer, Integer>>> timerAdder,
304-
Consumer<Throwable> throwableCatcher,
305-
boolean logExceptions) {
298+
public void config(Environment environment, Settings settings) {
306299
// TODO: make sure there's no overhead for having the config both places
307-
super.config(messageAdder, batchMessageAdder, timerAdder, throwableCatcher, logExceptions);
308-
client().config(messageAdder, batchMessageAdder, timerAdder, throwableCatcher, logExceptions);
300+
super.config(environment, settings);
301+
client().config(environment, settings);
309302
}
310303
}

0 commit comments

Comments
 (0)