From ae6b3fde6d2ef130e2bb18dd2c6ee040687b143d Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 30 Mar 2026 09:17:13 +0300 Subject: Update content for html --- gemfeed/atom.xml | 29246 ++++++++++++++++++++++++++--------------------------- 1 file changed, 14533 insertions(+), 14713 deletions(-) (limited to 'gemfeed/atom.xml') diff --git a/gemfeed/atom.xml b/gemfeed/atom.xml index 0b9d9da9..33188a8e 100644 --- a/gemfeed/atom.xml +++ b/gemfeed/atom.xml @@ -1,6233 +1,4535 @@ - 2026-03-28T00:29:48+02:00 + 2026-03-30T09:16:50+03:00 foo.zone feed To be in the .zone! https://foo.zone/ - RCM: The Ruby Configuration Management DSL - - https://foo.zone/gemfeed/2026-03-02-rcm-ruby-configuration-management-dsl.html - 2026-03-02T00:00:00+02:00 + Distributed Systems Simulator - Part 3: Advanced Examples and Protocol API + + https://foo.zone/gemfeed/2026-04-02-distributed-systems-simulator-part-3.html + 2026-04-02T00:00:00+03:00 Paul Buetow aka snonux paul@dev.buetow.org - RCM is a tiny configuration management system written in Ruby. It gives me a small DSL for describing how I want my machines to look, then it applies the changes: create files and directories, manage packages, and make sure certain lines exist in configuration files. It's deliberately KISS and optimised for a single person's machines instead of a whole fleet. + This is the third and final blog post of the Distributed Systems Simulator series. This part covers advanced simulation examples, the Raft consensus protocol, and the extensible Protocol API.
-

RCM: The Ruby Configuration Management DSL


+

Distributed Systems Simulator - Part 3: Advanced Examples and Protocol API



-Published at 2026-03-02T00:00:00+02:00
+Published at 2026-04-02T00:00:00+03:00

-RCM is a tiny configuration management system written in Ruby. It gives me a small DSL for describing how I want my machines to look, then it applies the changes: create files and directories, manage packages, and make sure certain lines exist in configuration files. It's deliberately KISS and optimised for a single person's machines instead of a whole fleet.
+This is the third and final blog post of the Distributed Systems Simulator series. This part covers advanced simulation examples, the Raft consensus protocol, and the extensible Protocol API.

-RCM DSL in action
+ds-sim on Codeberg (modernized, English-translated version)
+
+These are all the posts of this series:
+
+2026-03-31 Distributed Systems Simulator - Part 1: Introduction and GUI
+2026-04-01 Distributed Systems Simulator - Part 2: Built-in Protocols
+2026-04-02 Distributed Systems Simulator - Part 3: Advanced Examples and Protocol API (You are currently reading this)
+
+Screenshot: The Distributed Systems Simulator running a Broadcast protocol simulation with 6 processes. The visualization shows message lines between process bars, with blue indicating delivered messages and green indicating messages still in transit.

Table of Contents




-

Why I built RCM


+

Additional Examples



-I've used (and still use) the usual suspects in configuration management: Puppet, Ansible, etc. They are powerful, but also come with orchestration layers, agents, inventories, and a lot of moving parts. For my personal machines I wanted something smaller: one Ruby process, one configuration file, a few resource types, and good enough safety features.
-
-I've always been a fan of Ruby's metaprogramming features, and this project let me explore them in a focused, practical way.
+

Lamport and Vector Timestamps



-Because of that metaprogramming support, Ruby is a great fit for DSLs. You can get very close to natural language without inventing a brand-new syntax. RCM leans into that: the goal is to read a configuration and understand what happens without jumping between multiple files or templating languages.
+Visualization: Lamport Timestamps displayed on the Berkeley Algorithm simulation. Each event on a process bar shows its Lamport timestamp as a number in parentheses. The timestamps increase monotonically and are updated according to the Lamport clock rules when messages are sent and received between P1, P2, and P3.

-RCM repo on Codeberg
+"For many purposes, it is sufficient that all machines agree on the same time. It is not necessary that this time also agrees with real time, like every hour announced on the radio... For a certain class of algorithms, only the internal consistency of clocks is important." - Andrew Tanenbaum

-

How the DSL feels


+Clocks that provide such a time are also known as logical clocks. Two implementations are realized in the simulator: Lamport timestamps and vector timestamps.

-An RCM configuration starts with a configure block. Inside it you declare resources (file, package, given, notify, …). RCM figures out dependencies between resources and runs them in the right order.
+After activating the Lamport time switch in expert mode, the current Lamport timestamp appears at every event of a process. Each process has its own Lamport timestamp that is incremented when a message is sent or received. Each message carries the current Lamport time t_l(i) of the sending process i. When another process j receives this message, its Lamport timestamp t_l(j) is recalculated as:

- -
configure do
-  given { hostname is :earth }
-
-  file '/tmp/test/wg0.conf' do
-    requires file '/etc/hosts.test'
-    manage directory
-    from template
-    'content with <%= 1 + 2 %>'
-  end
-
-  file '/etc/hosts.test' do
-    line '192.168.1.101 earth'
-  end
-end
+
+t_l(j) := 1 + max(t_l(j), t_l(i))
 

-Which would look like this when run:
+The larger Lamport time of the sender and receiver process is used and then incremented by 1. After the Berkeley simulation shown here, P1 has Lamport timestamp 16, P2 has 14, and P3 has 15.

- -
% sudo ruby example.rb
-INFO 20260301-213817 dsl(0) => Configuring...
-INFO 20260301-213817 file('/tmp/test/wg0.conf') => Registered dependency on file('/etc/hosts.test')
-INFO 20260301-213817 file('/tmp/test/wg0.conf') => Evaluating...
-INFO 20260301-213817 file('/etc/hosts.test') => Evaluating...
-INFO 20260301-213817 file('/etc/hosts.test') => Writing file /etc/hosts.test
-INFO 20260301-213817 file('/tmp/test/wg0.conf') => Creating parent directory /tmp/test
-INFO 20260301-213817 file('/tmp/test/wg0.conf') => Writing file /tmp/test/wg0.conf
+Visualization: Vector Timestamps displayed on the same Berkeley Algorithm simulation. Each event shows its vector timestamp as a tuple (v1,v2,v3) representing the known state of all three processes. The tuples grow as processes communicate and merge their knowledge of each other's progress.
+
+With the active vector time switch, all vector timestamps are displayed. Like the Lamport timestamp, each message includes the current vector timestamp of the sending process. With n participating processes, the vector timestamp v has size n. Each participating process i has its own index, accessible via v(i). When v is the vector timestamp of the receiving process j and w is the vector timestamp of the sending process, the new local vector timestamp of process j is calculated as follows:
+
+
+for (i := 0; i < n; i++) {
+    if (i = j) {
+        v(i)++;
+    } else if (v(i) < w(i)) {
+        v(i) := w(i);
+    }
+}
 

-The idea is that you describe the desired state and RCM worries about the steps. The given block can short‑circuit the whole run (for example, only run on a specific hostname). Each file resource can either manage a complete file (from a template) or just make sure individual lines are present.
+By default, the vector timestamp is only incremented when a message is sent or received. In both cases, the sender and receiver each increment their own index in the vector timestamp by 1. Upon receiving a message, the local vector timestamp is then compared with the sender's, and the larger value is taken for all indices.

-

Keywords and resources


+After the simulation, P1 has vector timestamp (8,10,6), P2 has (6,10,6), and P3 has (6,10,8).

-Under the hood, each DSL word is either a keyword or a resource:
+The simulation settings include boolean variables "Lamport times affect all events" and "Vector times affect all events" (both default to false). When set to true, all events (not just message send/receive) will update the timestamps.

-
    -
  • Keyword is the base class for all top‑level DSL constructs.
  • -
  • Resource is the base class for things RCM can manage (files, packages, and so on).
  • -

-Resources can declare dependencies with requires. Before a resource runs, RCM makes sure all its requirements are satisfied and only evaluates each resource once per run. This keeps the mental model simple even when you compose more complex configurations.
+

Simulating Slow Connections



-

Files, directories, and templates


+Visualization: Slow connection simulation comparing Internal Synchronization (P1) and Christian's Method (P3) with P2 as server. P3 has high transmission times (2000-8000ms) simulating a slow network connection. P1 synchronizes to 21446ms (error: -1446ms) while P3 only reaches 16557ms (error: -3443ms), showing how slow connections degrade synchronization quality.

-The file resource handles three common cases:
+The simulator can also simulate slow connections to a specific process. This example revisits the comparison of Internal Synchronization (P1) and Christian's Method (P3), with P2 serving both. In this scenario, P3 has a poor network connection, so messages to and from P3 always require a longer transmission time.

-
    -
  • Managing parent directories (manage directory) so you don't have to create them manually.
  • -
  • Rendering ERB templates (from template) so you can mix Ruby expressions into config files.
  • -
  • Ensuring individual lines exist (line) for the many "append this line if missing" situations.
  • -

-Every write operation creates a backup copy in .rcmbackup/, so you can always inspect what changed and roll back manually if needed.
+P3's minimum transmission time is set to 2000ms and maximum to 8000ms, while P1 and P2 keep the defaults (500ms/2000ms). The simulation duration is 20000ms. With the "Average transmission times" setting enabled, the effective transmission time for messages involving P3 is:

-

How Ruby's metaprogramming helps


+
+1/2 * (rand(500,2000) + rand(2000,8000)) = 1/2 * rand(2500,10000) = rand(1250,5000)ms
+

-The nice thing about RCM is that the Ruby code you write in your configuration is not that different from the Ruby code inside RCM itself. The DSL is just a thin layer on top.
+Because P3 starts a new request before receiving the answer to its previous one, and because it always associates server responses with its most recently sent request, its RTT calculations become incorrect on each round, and its local time is poorly synchronized. P1 synchronizes to 21446ms (error: -1446ms) while P3 only reaches 16557ms (error: -3443ms).

-For example, when you write:
+

Raft Consensus Failover



- -
file '/etc/hosts.test' do
-  line '192.168.1.101 earth'
-end
-
+Screenshot: A 60-second Raft simulation with three processes. P1 starts as the initial leader, crashes at 3500ms, later recovers, P2 wins the reelection and remains leader, and P3 crashes later. The blue and red message lines show the continuing heartbeat and acknowledgment traffic during and after failover.

-Ruby turns file into a method call and '/etc/hosts.test' into a normal argument. Inside RCM, that method builds a File resource object and stores it for later. The block you pass is just a Ruby block; RCM calls it with the file resource as self, so method calls like line configure that resource. There is no special parser here, just plain Ruby method and block dispatch.
+While modernizing ds-sim, I also added a simplified Raft Consensus example. The simulation is intentionally small: three processes, one initial leader, one crash, a clean reelection, a recovery of the old leader, and then another crash later in the run. This makes it possible to see the most important Raft transitions without being overwhelmed by cluster size.

-The same goes for constructs like:
+The event log tells a very readable story. At 0ms, P1 starts as the initial leader in term 0. It immediately sends a heartbeat and an appendEntry message carrying the log entry cmd1. P2 joins at 100ms, P3 at 1700ms, and both acknowledge the leader's traffic. At that point the cluster is healthy: one leader, two followers, successful heartbeats, and successful log replication.

- -
given { hostname is :earth }
+At 3500ms, P1 crashes. The followers still process the last in-flight messages, but once the election timeout expires, P2 becomes a candidate and sends a voteRequest for term 1. P3 grants that vote, and at 9395ms the log records the decisive line:
+
+
+009395ms: PID: 2; ... Leader elected by majority vote: process 2 (term 1)
 

-RCM uses Ruby's dynamic method lookup to interpret hostname and is in that block and to decide whether the rest of the configuration should run at all. Features like method_missing, blocks, and the ability to change what self means in a block make this kind of DSL possible with very little code. You still get all the power of Ruby (conditionals, loops, helper methods), but the surface reads like a small language of its own.
+That transition is followed immediately by new heartbeats and a new appendEntry, which is exactly what you want to see in a Raft simulation: leadership is not just declared, it is exercised.

-

A bit more about method_missing


+At 12002ms, the old leader P1 recovers. Importantly, it does not try to reclaim control. Instead, it receives heartbeats from P2 and answers with heartbeatAck messages, rejoining the cluster as a follower. That is one of the most useful teaching moments in the log, because it makes the term-based leadership model concrete: the recovered node does not become leader again just because it used to be one.

-method_missing is one of the key tools that make the RCM DSL feel natural. In plain Ruby, if you call a method that does not exist, you get a NoMethodError. But before Ruby raises that error, it checks whether the object implements method_missing. If it does, Ruby calls that instead and lets the object decide what to do.
+At 20000ms, P3 crashes. The cluster continues running with P2 as leader and P1 as follower for the rest of the 60-second simulation. The log remains dominated by periodic heartbeats from P2 and acknowledgments from P1, showing that the system stays stable even after a second failure.

-In RCM, you can write things like:
+This single scenario demonstrates several core Raft properties in one replay:

- -
given { hostname is :earth }
+
    +
  • Stable startup leadership
  • +
  • Heartbeats and follower acknowledgments
  • +
  • Log replication
  • +
  • Leader failure detection
  • +
  • Majority-based reelection
  • +
  • Safe reintegration of a recovered former leader
  • +
  • Continued service after a later follower crash
  • +

+It is also a good example of why a simulator is useful for distributed systems. In a real production system, reconstructing this sort of sequence would require stitching together logs from multiple nodes. Here, the message flow, the crashes, the recoveries, and the Lamport/vector timestamps are all visible in one place.
+
+

Protocol API


+
+The simulator was designed from the ground up to be extensible. Users can implement their own protocols in Java by extending the VSAbstractProtocol base class. Each protocol has its own class in the protocols.implementations package.
+
+

Class Hierarchy


+
+
+VSAbstractEvent
+  +-- VSAbstractProtocol (base class for all protocols)
+        +-- VSDummyProtocol
+        +-- VSPingPongProtocol
+        +-- VSBroadcastProtocol
+        +-- VSInternalTimeSyncProtocol
+        +-- VSExternalTimeSyncProtocol
+        +-- VSBerkeleyTimeProtocol
+        +-- VSOnePhaseCommitProtocol
+        +-- VSTwoPhaseCommitProtocol
+        +-- VSBasicMulticastProtocol
+        +-- VSReliableMulticastProtocol
 

-Inside that block, calls such as hostname and is don't map to normal Ruby methods. Instead, RCM's DSL objects see those calls in method_missing, and interpret them as "check the current hostname" and "compare it to this symbol". This lets the DSL stay small and flexible: adding a new keyword can be as simple as handling another case in method_missing, without changing the Ruby syntax at all.
+

Implementing a Custom Protocol



-Put differently: you can write what looks like a tiny English sentence (hostname is :earth) and Ruby breaks it into method calls (hostname, then is) that RCM can interpret dynamically. Those "barewords" are not special syntax; they are just regular Ruby method names that the DSL catches and turns into configuration logic at runtime.
+Each protocol class must implement the following methods:

-Here's a simplified sketch of how such a condition object could look in Ruby:
+
    +
  • A public constructor: Must specify whether the client or the server initiates requests, using VSAbstractProtocol.HAS_ON_CLIENT_START or VSAbstractProtocol.HAS_ON_SERVER_START.
  • +
  • onClientInit() / onServerInit(): Called once before the protocol is first used. Used to initialize protocol variables and attributes via the VSPrefs methods (e.g. initVector, initLong). Variables initialized this way appear in the process editor and can be configured by the user.
  • +
  • onClientReset() / onServerReset(): Called each time the simulation is reset.
  • +
  • onClientStart() / onServerStart(): Called when the client/server initiates a request. Typically creates and sends a VSMessage object.
  • +
  • onClientRecv(VSMessage) / onServerRecv(VSMessage): Called when a message arrives.
  • +
  • onClientSchedule() / onServerSchedule(): Called when a scheduled alarm fires.
  • +
  • toString(): Optional. Customizes log output for this protocol.
  • +

+

Available API Methods


+
+Methods inherited from VSAbstractProtocol:
+
+
    +
  • sendMessage(VSMessage message): Sends a protocol message (automatically updates Lamport and Vector timestamps)
  • +
  • hasOnServerStart(): Whether the server or client initiates requests
  • +
  • isServer() / isClient(): Whether the current process has the protocol activated as server/client
  • +
  • scheduleAt(long time): Creates an alarm that fires at the given local process time, triggering onClientSchedule() or onServerSchedule()
  • +
  • removeSchedules(): Cancels all pending alarms in the current context
  • +
  • getNumProcesses(): Returns the total number of processes in the simulation
  • +

+Process methods available via the inherited process attribute:
+
+
    +
  • getTime() / setTime(long): Get/set the local process time
  • +
  • getGlobalTime(): Get the current global simulation time
  • +
  • getClockVariance() / setClockVariance(float): Get/set the clock drift
  • +
  • getLamportTime() / setLamportTime(long): Get/set the Lamport timestamp
  • +
  • getVectorTime() / updateVectorTime(VSVectorTime): Get/update the vector timestamp
  • +
  • getProcessID(): Get the process PID
  • +
  • isCrashed() / isCrashed(boolean): Check or set crash state
  • +
  • getRandomPercentage(): Get a random value between 0 and 100
  • +

+Message methods (VSMessage):
+
+
    +
  • new VSMessage(): Create a new message
  • +
  • getMessageID(): Get the message NID
  • +
  • setBoolean(key, value) / getBoolean(key): Set/get boolean data
  • +
  • setInteger(key, value) / getInteger(key): Set/get integer data
  • +
  • setLong(key, value) / getLong(key): Set/get long data
  • +
  • setString(key, value) / getString(key): Set/get string data
  • +
  • getSendingProcess(): Get a reference to the sending process
  • +
  • isServerMessage(): Whether it's a server or client message
  • +

+

Example: Reliable Multicast Implementation


+
+Here is a condensed example showing key parts of the Reliable Multicast Protocol implementation:

-
class HostCondition
-  def initialize
-    @current_hostname = Socket.gethostname.to_sym
-  end
+
public class VSReliableMulticastProtocol extends VSAbstractProtocol {
+    public VSReliableMulticastProtocol() {
+        // The client initiates requests
+        super(VSAbstractProtocol.HAS_ON_CLIENT_START);
+        super.setClassname(super.getClass().toString());
+    }
 
-  def method_missing(name, *args, &)
-    case name
-    when :hostname
-      @left = @current_hostname
-      self               # allow chaining: hostname is :earth
-    when :is
-      @left == args.first
-    else
-      super
-    end
-  end
-end
+    private ArrayList<Integer> pids;
 
-HostCondition.new.hostname.is(:earth)
+    // Initialize protocol variables (editable in the process editor)
+    public void onClientInit() {
+        Vector<Integer> vec = new Vector<Integer>();
+        vec.add(1); vec.add(3);
+        super.initVector("pids", vec, "PIDs of participating processes");
+        super.initLong("timeout", 2500, "Time until resend", "ms");
+    }
+
+    // Send multicast to all servers that haven't ACKed yet
+    public void onClientStart() {
+        if (pids.size() != 0) {
+            long timeout = super.getLong("timeout") + process.getTime();
+            super.scheduleAt(timeout);
+            VSMessage message = new VSMessage();
+            message.setBoolean("isMulticast", true);
+            super.sendMessage(message);
+        }
+    }
+
+    // Handle ACK from a server
+    public void onClientRecv(VSMessage recvMessage) {
+        if (pids.size() != 0 && recvMessage.getBoolean("isAck")) {
+            Integer pid = recvMessage.getIntegerObj("pid");
+            if (pids.contains(pid))
+                pids.remove(pid);
+            super.log("ACK from Process " + pid + " received!");
+            if (pids.size() == 0) {
+                super.log("ACKs from all processes received!");
+                super.removeSchedules();
+            }
+        }
+    }
+
+    // Retry on timeout
+    public void onClientSchedule() { onClientStart(); }
+}
 

-RCM's real code is more sophisticated, but the idea is the same: Ruby happily calls method_missing for unknown methods like hostname and is, and the DSL turns those calls into a value (true/false) that decides whether the rest of the configuration should run.
-
-

Ruby metaprogramming: further reading


+

Project Statistics



-If you want to dive deeper into the ideas behind RCM's DSL, these books are great starting points:
+The original VS-Sim project (August 2008) was written in Java 6 and consisted of:

    -
  • "Metaprogramming Ruby 2" by Paolo Perrotta
  • -
  • "The Well-Grounded Rubyist" by David A. Black (and others)
  • -
  • "Eloquent Ruby" by Russ Olsen
  • +
  • 61 source files across 12 Java packages
  • +
  • Approximately 15,710 lines of code
  • +
  • 2.2 MB of generated Javadoc documentation
  • +
  • 142 KB compiled JAR file
  • +
  • 10 built-in protocols
  • +
  • 163 configurable settings

-They all cover Ruby's object model, blocks, method_missing, and other metaprogramming techniques in much more detail than I can in a single blog post.
+The modernized successor ds-sim (version 1.1.0) has been updated to Java 21 and translated to English:

-

Safety, dry runs, and debugging


+
    +
  • 146 source files (117 main + 29 test) across 19 Java packages
  • +
  • Approximately 27,900 lines of code (22,400 main + 5,500 test)
  • +
  • 12 built-in protocols
  • +
  • 208 unit tests
  • +
  • 269 configurable settings
  • +

+ds-sim source code on Codeberg
+vs-sim source code on Codeberg (original German version, 2008)

-RCM has a --dry mode: it logs what it would do without actually touching the file system. I use this when iterating on new configurations or refactoring existing ones. Combined with the built‑in logging and debug output, it's straightforward to see which resources were scheduled and in which order.
+Other related posts are:

-Because RCM is just Ruby, there's no separate agent protocol or daemon. The same process parses the DSL, resolves dependencies, and performs the actions. If something goes wrong, you can drop into the code, add a quick debug statement, and re‑run your configuration.
+2026-03-01 Loadbars 0.13.0 released
+2022-12-24 (Re)learning Java - My takeaways
+2022-03-06 The release of DTail 4.0.0
+2016-11-20 Object oriented programming with ANSI C

-

RCM vs Puppet and other big tools


+E-Mail your comments to paul@nospam.buetow.org

-RCM does not try to compete with Puppet, Chef, or Ansible on scale. Those tools shine when you manage hundreds or thousands of machines, have multiple teams contributing modules, and need centralised orchestration, reporting, and role‑based access control. They also come with their own DSLs, servers/agents, certificate handling, and a long list of resource types and modules. Ansible may be more similar to RCM than the other tools, but it's still much more complex than RCM.
+Back to the main site
+
+
+
+ + Distributed Systems Simulator - Part 2: Built-in Protocols + + https://foo.zone/gemfeed/2026-04-01-distributed-systems-simulator-part-2.html + 2026-04-01T00:00:00+03:00 + + Paul Buetow aka snonux + paul@dev.buetow.org + + This is the second blog post of the Distributed Systems Simulator series. This part covers all 10 built-in protocols with examples. + +
+

Distributed Systems Simulator - Part 2: Built-in Protocols



-For my personal use cases, that layer is mostly overhead. I want:
+Published at 2026-04-01T00:00:00+03:00

-
    -
  • No extra daemon, message bus, or master node.
  • -
  • No separate DSL to learn besides Ruby itself.
  • -
  • A codebase small enough that I can understand and change all of it in an evening.
  • -
  • Behaviour I can inspect just by reading the Ruby code.
  • -

-In that space RCM wins: it is small, transparent, and tuned for one person (me!) with a handful of personal machines or my Laptops. I still think tools like Puppet are the right choice for larger organisations and shared infrastructure, but RCM gives me a tiny, focused alternative for my own systems.
+This is the second blog post of the Distributed Systems Simulator series. This part covers all 10 built-in protocols with examples.

-

Cutting RCM 0.1.0


+ds-sim on Codeberg (modernized, English-translated version)

-As of this post I'm tagging and releasing **RCM 0.1.0**. About 99% of the code has been written by me so far, and before AI agents take over more of the boilerplate and wiring work, it felt like a good moment to cut a release and mark this mostly‑human baseline.
+These are all the posts of this series:

-Future changes will very likely involve more automated help, but 0.1.0 is the snapshot of the original, hand‑crafted version of the tool.
+2026-03-31 Distributed Systems Simulator - Part 1: Introduction and GUI
+2026-04-01 Distributed Systems Simulator - Part 2: Built-in Protocols (You are currently reading this)
+2026-04-02 Distributed Systems Simulator - Part 3: Advanced Examples and Protocol API

-

What's next


+Screenshot: The Distributed Systems Simulator running a Broadcast protocol simulation with 6 processes. The visualization shows message lines between process bars, with blue indicating delivered messages and green indicating messages still in transit.

-RCM already does what I need on my machines, but there are a few ideas I want to explore:
+

Table of Contents




-

Feature overview (for now)


+

Protocols and Examples



-Here is a quick overview of what RCM can do today, grouped by area:
+The simulator comes with 10 built-in protocols. As described earlier, protocols are distinguished between server-side and client-side. Servers can respond to client messages, and clients can respond to server messages. Each process can support any number of protocols on both the client and server side. Users can also implement their own protocols using the simulator's Protocol API (see the Protocol API section).

-
    -
  • File management: file '/path', manage directory, from template, line '...'
  • -
  • Packages: package 'name' resources for installing and updating packages (currently focused on Fedora/DNF)
  • -
  • Conditions and flow: given { ... } blocks, predicates such as hostname is :earth
  • -
  • Notifications and dependencies: requires between resources, notify for follow‑up actions
  • -
  • Safety and execution modes: backups in .rcmbackup/, --dry runs, debug logging
  • -

-Some small examples adapted from RCM's own tests:
+The program directory contains a saved-simulations folder with example simulations for each protocol as serialized .dat files.

-

Template rendering into a file


+

Dummy Protocol



- -
configure do
-  file './.file_example.rcmtmp' do
-    from template
-    'One plus two is <%= 1 + 2 %>!'
-  end
-end
+The Dummy Protocol serves only as a template for creating custom protocols. When using the Dummy Protocol, only log messages are output when events occur. No further actions are performed.
+
+

Ping-Pong Protocol


+
+Visualization: The Ping-Pong Protocol showing two processes (P1 and P2) exchanging messages in a continuous back-and-forth pattern. Blue lines represent delivered messages bouncing between the process bars over a 15-second simulation.
+
+In the Ping-Pong Protocol, two processes -- Client P1 and Server P2 -- constantly send messages back and forth. The Ping-Pong client starts the first request, to which the server responds to the client. The client then responds again, and so on. Each message includes a counter that is incremented at each station and logged in the log window.
+
+
+Programmed Ping-Pong Events:
+
+| Time (ms) | PID | Event                          |
+|-----------|-----|--------------------------------|
+| 0         | 1   | Ping-Pong Client activate      |
+| 0         | 2   | Ping-Pong Server activate      |
+| 0         | 1   | Ping-Pong Client request start |
 

-

Ensuring a line is absent from a file


+It is important that Process 1 activates its Ping-Pong client before starting a Ping-Pong client request. Before a process can start a request, it must have the corresponding protocol activated. This also applies to all other protocols.

- -
configure do
-  file './.file_example.rcmtmp' do
-    line 'Whats up?'
-    is absent
-  end
-end
-
+**Ping-Pong Storm Variant**

-

Guarding a configuration run on the current hostname


+Visualization: The Ping-Pong Storm variant with three processes. P1 is the client, P2 and P3 are both servers. The visualization shows an exponentially growing number of messages as each client message generates two server responses, creating a dense web of blue and green message lines.

- -
configure do
-  given { hostname Socket.gethostname }
-  ...
-end
+By adding a third process P3 as an additional Ping-Pong server, a Ping-Pong "Storm" can be realized. Since every client message now receives two server responses, the number of messages doubles with each round, creating an exponential message flood.
+
+
+Programmed Ping-Pong Storm Events:
+
+| Time (ms) | PID | Event                          |
+|-----------|-----|--------------------------------|
+| 0         | 1   | Ping-Pong Client activate      |
+| 0         | 2   | Ping-Pong Server activate      |
+| 0         | 3   | Ping-Pong Server activate      |
+| 0         | 1   | Ping-Pong Client request start |
 

-

Creating and deleting directories, and purging a directory tree


+

Broadcast Protocol



- -
configure do
-  directory './.directory_example.rcmtmp' do
-    is present
-  end
+Visualization: The Broadcast Protocol with 6 processes (P1-P6). Dense crossing message lines show how a broadcast from P1 propagates to all processes, with each process re-broadcasting to others. Blue lines indicate delivered messages, green lines indicate messages still in transit.
+
+The Broadcast Protocol behaves similarly to the Ping-Pong Protocol. The difference is that the protocol tracks -- using a unique Broadcast ID -- which messages have already been sent. Each process re-broadcasts all received messages to others, provided it has not already sent them.
+
+In this case, no distinction is made between client and server, so that the same action is performed when a message arrives at either side. This makes it possible, using multiple processes, to create a broadcast. P1 is the client and starts a request at 0ms and 2500ms. The simulation duration is exactly 5000ms. Since a client can only receive server messages and a server can only receive client messages, every process in this simulation is both server and client.
+
+
+Programmed Broadcast Events:
 
-  directory delete do
-    path './.directory_example.rcmtmp'
-    is absent
-  end
-end
+| Time (ms) | PID | Event                            |
+|-----------|-----|----------------------------------|
+| 0         | 1-6 | Broadcast Client activate        |
+| 0         | 1-6 | Broadcast Server activate        |
+| 0         | 1   | Broadcast Client request start   |
+| 2500      | 1   | Broadcast Client request start   |
 

-

Managing file and directory modes and ownership


+

Internal Synchronization Protocol



- -
configure do
-  touch './.mode_example.rcmtmp' do
-    mode 0o600
-  end
-
-  directory './.mode_example_dir.rcmtmp' do
-    mode 0o705
-  end
-end
+Visualization: Internal Synchronization with 2 processes. P1 (client, clock drift 0.1) shows a faster-running clock reaching 15976ms by simulation end. The blue message lines show P1 periodically synchronizing with P2 (server, no drift), with the time corrections visible as slight adjustments in P1's timeline.
+
+The Internal Synchronization Protocol is used for synchronizing the local process time, which can be applied when a process time is running incorrectly due to clock drift. When the client wants to synchronize its (incorrect) local process time t_c with a server, it sends a client request. The server responds with its own local process time t_s, allowing the client to calculate a new, more accurate time for itself.
+
+After receiving the server response, the client P1 calculates its new local process time as:
+
+
+t_c := t_s + 1/2 * (t'_min + t'_max)
 

-

Using a chained, more natural language style for notifications


+This synchronizes P1's local time with an error of less than 1/2 * (t'_max - t'_min), where t'_min and t'_max are the assumed minimum and maximum transmission times configured in the protocol settings.

-This will just print out something, not changing anything:
+In the example, the client process has a clock drift of 0.1 and the server has 0.0. The client starts a request at local process times 0ms, 5000ms, and 10000ms. By simulation end, P1's time is synchronized to 15976ms (an error of -976ms from the global 15000ms).

- -
configure do
-  notify hello dear world do
-    thank you to be part of you
-  end
-end
+
+Programmed Internal Sync Events:
+
+| Time (ms) | PID | Event                              |
+|-----------|-----|------------------------------------|
+| 0         | 1   | Internal Sync Client activate      |
+| 0         | 2   | Internal Sync Server activate      |
+| 0         | 1   | Internal Sync Client request start |
+| 5000      | 1   | Internal Sync Client request start |
+| 10000     | 1   | Internal Sync Client request start |
 

-

Touching files and updating their timestamps


+Protocol variables (client-side):

- -
configure do
-  touch './.touch_example.rcmtmp'
-end
+
    +
  • Min. transmission time (Long: 500): The assumed t'_min in milliseconds
  • +
  • Max. transmission time (Long: 2000): The assumed t'_max in milliseconds
  • +

+These can differ from the act