2222
2323package dslabs .framework ;
2424
25+ import com .google .common .collect .ImmutableList ;
2526import java .io .Serializable ;
2627import java .lang .reflect .InvocationTargetException ;
2728import java .lang .reflect .Method ;
29+ import java .time .Duration ;
2830import java .util .Arrays ;
2931import java .util .Collection ;
3032import java .util .Collections ;
3537import java .util .Objects ;
3638import java .util .Optional ;
3739import java .util .concurrent .ConcurrentHashMap ;
38- import java .util .function .Consumer ;
3940import java .util .logging .Level ;
4041import lombok .EqualsAndHashCode ;
4142import lombok .NonNull ;
4243import lombok .SneakyThrows ;
4344import lombok .ToString ;
4445import 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
104102@ EqualsAndHashCode (of = {"subNodes" })
105103@ ToString (of = {"address" , "subNodes" })
106104public 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}
0 commit comments