diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fc..fae0804 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/dev/oxoo2a/sim4da/Message.java b/src/main/java/dev/oxoo2a/sim4da/Message.java index 0f5da9b..aaf164e 100644 --- a/src/main/java/dev/oxoo2a/sim4da/Message.java +++ b/src/main/java/dev/oxoo2a/sim4da/Message.java @@ -1,21 +1,42 @@ package dev.oxoo2a.sim4da; -import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; public class Message { + private final String time; + public Message () { - content = new HashMap<>(); + this(new HashMap<>(), ""); } - protected Message ( HashMap content ) { + this(content, ""); + } + public Message(Time time){ + this(new HashMap<>(), time.toString()); + } + public Message(Message m, Time time){ + this(m.content, time); + } + public Message(HashMap content, Time time){ + this(content, time.toString()); + } + public Message(HashMap content, String time){ this.content = content; + this.time = time; } + + public boolean hasTime(){ + return !time.equals(""); + } + + public String getTime(){ + return time; + } + public Message add ( String key, String value ) { content.put(key,value); return this; @@ -24,7 +45,6 @@ public Message add ( String key, String value ) { public Message add ( String key, int value ) { content.put(key,String.valueOf(value)); return this; - } public String query ( String key ) { @@ -36,18 +56,17 @@ public Map getMap () { } public String toJson () { - return serialize(content); + return serialize(this); } public static Message fromJson ( String s ) { - Type contentType = new TypeToken>() {}.getType(); - return new Message(serializer.fromJson(s,contentType)); + return serializer.fromJson(s, Message.class); } - private static synchronized String serialize ( Map content ) { - return serializer.toJson(content); // Not sure about thread safety of Gson + private static synchronized String serialize ( Message m ) { + return serializer.toJson(m); // Not sure about thread safety of Gson } - private final HashMap content; - private static final Gson serializer = new Gson(); + protected final HashMap content; + protected static final Gson serializer = new Gson(); } diff --git a/src/main/java/dev/oxoo2a/sim4da/NodeWithTime.java b/src/main/java/dev/oxoo2a/sim4da/NodeWithTime.java new file mode 100644 index 0000000..bcf9ab4 --- /dev/null +++ b/src/main/java/dev/oxoo2a/sim4da/NodeWithTime.java @@ -0,0 +1,63 @@ +package dev.oxoo2a.sim4da; + +/*** + * Node in the network that has a logic clock + * @author Tessa Steinigke + */ +public abstract class NodeWithTime extends Node { + + protected final Time time; // manages the time according to the rules in the class of the logic clock + private final boolean logTime; + + public NodeWithTime(int my_id, Time time) { + this(my_id, time, false); + } + public NodeWithTime(int my_id, Time time, boolean logTime) { + super(my_id); + this.time = time; + this.logTime = logTime; + } + + protected void sendUnicast ( int receiver_id, String m ) { + time.incrementMyTime(); + emit("Warning: Can not update time in message!"); + if(logTime) emit("%d: perform Unicast at %s",myId,time.toString()); + super.sendUnicast(receiver_id, m); // WARNING: can not update time in message + } + + protected void sendUnicast ( int receiver_id, Message m ) { + time.incrementMyTime(); + if(logTime) emit("%d: perform Unicast at %s",myId,time.toString()); + super.sendUnicast(receiver_id, new Message(m, time)); + } + + protected void sendBroadcast ( String m ) { + time.incrementMyTime(); + emit("Warning: Can not update time in message!"); + if(logTime) emit("%d: perform Broadcast at %s",myId,time.toString()); + super.sendBroadcast(m); // WARNING: can not update time in message + } + + protected void sendBroadcast ( Message m ) { + time.incrementMyTime(); + if(logTime) emit("%d: perform Broadcast at %s",myId,time.toString()); + super.sendBroadcast(new Message(m, time)); + } + + protected Network.Message receive () { + Network.Message m_raw = super.receive(); + if (m_raw != null) { + Message m = Message.fromJson(m_raw.payload); + // Update the time with the new time information given in the message + if(m.hasTime()) + time.updateTime(m.getTime()); + + if(logTime) { + String m_type = m_raw.type == Network.MessageType.BROADCAST ? "Broadcast" : "Unicast"; + emit("%d: receive %s at %s", myId, m_type,time.toString()); + } + } + return m_raw; + } + +} diff --git a/src/main/java/dev/oxoo2a/sim4da/Time.java b/src/main/java/dev/oxoo2a/sim4da/Time.java new file mode 100644 index 0000000..095e4c3 --- /dev/null +++ b/src/main/java/dev/oxoo2a/sim4da/Time.java @@ -0,0 +1,29 @@ +package dev.oxoo2a.sim4da; + +public interface Time { + /*** + * Increment the time of the local node + */ + void incrementMyTime(); + + /*** + * String representation of the time + * @return - time as string + */ + String toString(); + + /*** + * New time was received by a different node (sender). + * Now the time of the local node must be updated. + * @param time_sender - time of the sender node in the string representation + */ + void updateTime(String time_sender); + + + /*** + * New time was received by a different node (sender). + * Now the time of the local node must be updated. + * @param time_sender - time of the sender node + */ + void updateTime(Time time_sender); +} diff --git a/src/main/java/dev/oxoo2a/sim4da/times/LamportTime.java b/src/main/java/dev/oxoo2a/sim4da/times/LamportTime.java new file mode 100644 index 0000000..f9496c3 --- /dev/null +++ b/src/main/java/dev/oxoo2a/sim4da/times/LamportTime.java @@ -0,0 +1,46 @@ +package dev.oxoo2a.sim4da.times; + +import dev.oxoo2a.sim4da.Time; + +/*** + * Implements the rules of the lamport time + */ +public class LamportTime implements Time { + + private int time; + + public LamportTime(){ + time = 0; + } + + @Override + public void incrementMyTime() { + time++; + } + + @Override + public String toString() { + return time+""; + } + + @Override + public void updateTime(String time_sender) { + int time_s_i = Integer.parseInt(time_sender); + // max function + if(time_s_i > time) + time = time_s_i; + // increment local time + incrementMyTime(); + } + + @Override + public void updateTime(Time time_sender) { + if(!( time_sender instanceof LamportTime)) + throw new IllegalArgumentException("Wrong time format"); + LamportTime l_time_sender = (LamportTime) time_sender; + if(l_time_sender.time > time) + time = l_time_sender.time; + // increment local time + incrementMyTime(); + } +} diff --git a/src/main/java/dev/oxoo2a/sim4da/times/VectorTime.java b/src/main/java/dev/oxoo2a/sim4da/times/VectorTime.java new file mode 100644 index 0000000..1fb049c --- /dev/null +++ b/src/main/java/dev/oxoo2a/sim4da/times/VectorTime.java @@ -0,0 +1,58 @@ +package dev.oxoo2a.sim4da.times; + +import dev.oxoo2a.sim4da.Time; + +import java.util.Arrays; + +/*** + * Implements the rules of the vector time + */ +public class VectorTime implements Time { + + private int[] time; + private final int myId; + + public VectorTime(int myId, int n_nodes){ + time = new int[n_nodes]; + for(int i = 0; i < n_nodes; i++){ + time[i] = 0; + } + this.myId = myId; + } + + @Override + public void incrementMyTime() { + time[myId]++; + } + + @Override + public String toString() { + return Arrays.toString(time); + } + + @Override + public void updateTime(String time_sender) { + // increment local time + incrementMyTime(); + + time_sender = time_sender.substring(1, time_sender.length()-1); + String[] arr = time_sender.split(", "); + // max function + for(int i = 0; i < arr.length; i++){ + int sender_i_int = Integer.parseInt(arr[i]); + if(sender_i_int > time[i]) + time[i] = sender_i_int; + } + } + + @Override + public void updateTime(Time time_sender) { + if(!( time_sender instanceof VectorTime)) + throw new IllegalArgumentException("Wrong time format"); + VectorTime v_time_sender = (VectorTime) time_sender; + for(int i = 0; i < time.length; i++){ + if(v_time_sender.time[i] > time[i]) + time[i] = v_time_sender.time[i]; + } + } +} diff --git a/src/test/java/dev/oxoo2a/sim4da/NodeWithTimeTest.java b/src/test/java/dev/oxoo2a/sim4da/NodeWithTimeTest.java new file mode 100644 index 0000000..e7bcb32 --- /dev/null +++ b/src/test/java/dev/oxoo2a/sim4da/NodeWithTimeTest.java @@ -0,0 +1,53 @@ +package dev.oxoo2a.sim4da; + +import dev.oxoo2a.sim4da.nodes.NodeWithTimeRandom; +import dev.oxoo2a.sim4da.times.LamportTime; +import dev.oxoo2a.sim4da.times.VectorTime; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.fail; + +/*** + * Test nodes with logic clocks + * @author Tessa Steinigke + */ +public class NodeWithTimeTest { + + public enum TimeType { LAMPORT, VECTOR } + + private static final int n_nodes = 3; + + /*** + * Same test for both clocks. Just the Time instances are different. + * @param type - Type of logic clock + */ + private static void test(TimeType type){ + Simulator s = Simulator.createDefaultSimulator(n_nodes); + for (int id=0; id 0) running = true; + } + + // Check termination + if(!running){ + // TERMINATED + termination_detected = true; + emit("!! TERMINATION DETECTED BY FOLLOWING-VECTOR-METHODE"); + }else{ + // Not terminated + send_request(); + } + } + +} diff --git a/src/test/java/dev/oxoo2a/sim4da/nodes/TerminatorDoubleCountingNode.java b/src/test/java/dev/oxoo2a/sim4da/nodes/TerminatorDoubleCountingNode.java new file mode 100644 index 0000000..1249ea2 --- /dev/null +++ b/src/test/java/dev/oxoo2a/sim4da/nodes/TerminatorDoubleCountingNode.java @@ -0,0 +1,81 @@ +package dev.oxoo2a.sim4da.nodes; + +import dev.oxoo2a.sim4da.*; + +/** + * Doppelzählverfahren zur Bestimmung der Terminierung + * @author Tessa Steinigke + */ +public class TerminatorDoubleCountingNode extends Terminator { + + /** + * [Interval 1: sums of send|received|active], + * [Interval 2: sums of send|received|active] + */ + private int[][] statistics; + private int statistics_interval = 0; + private int c_statistics_missing = n_nodes; + + public TerminatorDoubleCountingNode(int my_id, int n_nodes) { + super(my_id, n_nodes); + statistics = new int[2][3]; + } + + protected void send_request(){ + c_statistics_missing = n_nodes; + Message m = new Message(); + m.add("request","send/received"); + sendBroadcast(m); + } + + protected void receive_status(Message m){ + if(m.query("request")!= null) return; // Ignore requests + + c_statistics_missing--; + // Add statistics + statistics[statistics_interval][0] += Integer.parseInt(m.query("send")); + statistics[statistics_interval][1] += Integer.parseInt(m.query("received")); + statistics[statistics_interval][2] += (m.query("status").equals("active") ? 1 : 0 ); + + // Analyse + if(c_statistics_missing == 0){ + // All answers received + if(statistics_interval == 0) { + // first loop + statistics_interval = 1; + send_request(); + return; + } + + //emit("0 missing: (%d,%d,%d),(%d,%d,%d)", stati[0][0], stati[0][1], stati[0][2], stati[1][0], stati[1][1], stati[1][2]); + + // Check terminated + // 1. check all passive + boolean terminated = (statistics[0][2] + statistics[1][2] == 0); + if(terminated){ + //2. Check values + int val = statistics[0][0]; + terminated = (val == statistics[0][1]) & (val == statistics[1][0]) & (val == statistics[1][1]); + if(terminated){ + // TERMINATED + termination_detected = true; + emit("!! TERMINATION DETECTED BY DOUBLE-COUNTING-METHODE"); + return; + } + } + + // Not terminated + if(statistics[1][0] == statistics[1][1]){ + // Last attempt could indicate termination + statistics[0] = statistics[1]; // shift last to new first + statistics[1] = new int[3]; // empty new last + }else{ + // Last attempt already indicates no termination -> can be deleted + statistics = new int[2][3]; + statistics_interval = 0; + } + send_request(); + } + } + +} diff --git a/src/test/java/dev/oxoo2a/sim4da/nodes/TokenRingNode.java b/src/test/java/dev/oxoo2a/sim4da/nodes/TokenRingNode.java new file mode 100644 index 0000000..8f0c65a --- /dev/null +++ b/src/test/java/dev/oxoo2a/sim4da/nodes/TokenRingNode.java @@ -0,0 +1,37 @@ +package dev.oxoo2a.sim4da.nodes; + +import dev.oxoo2a.sim4da.Message; +import dev.oxoo2a.sim4da.Network; +import dev.oxoo2a.sim4da.Node; + +public class TokenRingNode extends Node { + + public TokenRingNode(int my_id) { + super(my_id); + } + + @Override + protected void main() { + Message m = new Message(); + if (myId == 0) { + // Send first message + m.add("counter",0); + sendUnicast(1,m); + } + while (true) { + // Listen for messages + Network.Message m_raw = receive(); + if (m_raw == null) break; + // Message received + m = Message.fromJson(m_raw.payload); + int counter = Integer.parseInt(m.query("counter")); + emit("%d: counter==%d",myId,counter); + // Token increased + counter++; + m.add("counter",counter); + // Next message send + sendUnicast((myId+1) % numberOfNodes(),m); + } + } + +}