Troubleshooting

HORUS ships three utility scripts: install.sh, uninstall.sh, and uninstall.ps1 for Windows PowerShell (no bash or MSYS2 needed). This page covers updating, recovering from a broken installation, and debugging HORUS applications at runtime.

Quick Reference

ScriptUse WhenWhat It Does
./install.shInstall or updateFull installation from source
./uninstall.shRemove HORUS (Linux/macOS)Complete removal
.\uninstall.ps1Remove HORUS (Windows)Complete removal, native PowerShell

Quick Diagnostic Steps

When your HORUS application isn't working:

  1. Check the Monitor: Run horus monitor to see active nodes, topics, and message flow. The monitor ships as a plugin — if it reports "The monitor plugin is not installed", run horus install horus-monitor first.
  2. Examine Logs: Look for error messages in your terminal output
  3. Verify Topics: Ensure publisher and subscriber use exact same topic names
  4. Check Shared Memory: Look in /dev/shm/horus_default/ (or /dev/shm/horus_*/) for stale HORUS memory regions
  5. Test Individually: Run nodes one at a time to isolate the problem

Updating HORUS

To update to the latest version:

cd /path/to/horus
git pull
./install.sh

To preview changes before updating:

git fetch
git log HEAD..@{u}  # See what's new
git pull
./install.sh

If you have uncommitted changes:

git stash
git pull
./install.sh
git stash pop  # Restore your changes

Manual Recovery

Use when: Build errors, corrupted cache, installation broken

Quick Steps

# Navigate to HORUS source directory
cd /path/to/horus

# 1. Clean build artifacts
cargo clean

# 2. Remove the cached HORUS source — both roots `horus run` searches
rm -rf ~/.horus/cache    # what install.sh writes: horus@<version> + pre-compiled deps
rm -rf ~/.cache/horus    # XDG cache root — searched first by `horus run`

# 3. Fresh install
./install.sh

When to Use Recovery

Symptoms requiring recovery:

  1. Build fails:

    error: could not compile `horus_core`
    
  2. Corrupted cache:

    error: failed to load source for dependency `horus_core`
    
  3. Binary doesn't work:

    $ horus --help
    Segmentation fault
    
  4. Version mismatches:

    error: the package `horus` depends on `horus_core 0.1.0`,
    but `horus_core 0.1.3` is installed
    
  5. Broken after system updates:

    • Rust updated
    • System libraries changed
    • GCC/Clang updated

What Gets Removed

By cargo clean:

  • target/ directory (build artifacts)

By rm -rf ~/.horus/cache and rm -rf ~/.cache/horus:

  • The cached HORUS source tree (horus@<version>/) and its pre-compiled deps
  • Cached packages downloaded from the registry

Never removed by the steps above (safe):

  • ~/.horus/config.toml (user settings)
  • ~/.config/horus/auth.json (registry auth, on Linux)
  • Project-local .horus/ directories
  • Your source code

Full Reset (Nuclear Option)

If the quick steps don't work, do a complete reset:

# Remove everything HORUS-related
cargo clean
rm -rf ~/.horus          # installer's source cache (horus@<version>), recordings, env files
rm -rf ~/.config/horus   # registry credentials live here, not under ~/.horus
rm -rf ~/.cache/horus    # XDG cache root — searched first by `horus run`
rm -f ~/.cargo/bin/horus

# Fresh install
./install.sh

Installation Issues

Problem: "Rust not installed"

$ ./install.sh
 Error: Rust is not installed

Solution:

# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Then try again
./install.sh

Problem: "C compiler not found"

Solution:

# Ubuntu/Debian/Raspberry Pi OS - Install ALL required packages
sudo apt update
sudo apt install -y build-essential pkg-config \
  libssl-dev libudev-dev libasound2-dev \
  libx11-dev libxrandr-dev libxi-dev libxcursor-dev libxinerama-dev \
  libwayland-dev wayland-protocols libxkbcommon-dev \
  libvulkan-dev libfontconfig-dev libfreetype-dev \
  libv4l-dev

# Fedora/RHEL
sudo dnf groupinstall "Development Tools"
sudo dnf install -y pkg-config openssl-devel systemd-devel alsa-lib-devel \
  libX11-devel libXrandr-devel libXi-devel libXcursor-devel libXinerama-devel \
  wayland-devel wayland-protocols-devel libxkbcommon-devel \
  vulkan-devel fontconfig-devel freetype-devel \
  libv4l-devel

Problem: Build fails with linker errors

error: linking with `cc` failed: exit status: 1
error: could not find native static library `X11`, perhaps an -L flag is missing?

Solution:

# Install ALL missing system libraries (most common cause)
# Ubuntu/Debian/Raspberry Pi OS
sudo apt update
sudo apt install -y build-essential pkg-config \
  libssl-dev libudev-dev libasound2-dev \
  libx11-dev libxrandr-dev libxi-dev libxcursor-dev libxinerama-dev \
  libwayland-dev wayland-protocols libxkbcommon-dev \
  libvulkan-dev libfontconfig-dev libfreetype-dev \
  libv4l-dev

# Or run manual recovery (see Manual Recovery section)
cargo clean && rm -rf ~/.horus/cache ~/.cache/horus && ./install.sh

Update Issues

Problem: "Build failed" during update

Solution:

# Try manual recovery
cargo clean && rm -rf ~/.horus/cache ~/.cache/horus && ./install.sh

Problem: "Already up to date" but binary broken

Solution:

# Force rebuild
./install.sh

Runtime Issues

"horus: command not found"

Solution:

# Add to PATH (add to ~/.bashrc or ~/.zshrc)
export PATH="$HOME/.cargo/bin:$PATH"

# Then reload shell
source ~/.bashrc  # or restart terminal

# Verify
which horus
horus --help

Binary exists but doesn't run

$ horus --help
Segmentation fault

Solution:

# Full recovery
cargo clean && rm -rf ~/.horus/cache ~/.cache/horus && ./install.sh

Version mismatch errors

error: the package `horus` depends on `horus_core 0.1.0`,
but `horus_core 0.1.3` is installed

Why this happens:

  • You updated the horus CLI to a new version
  • Your project's .horus/ directory still has cached dependencies from the old version
  • The cached Cargo.lock references incompatible library versions

Option 1: horus run --clean (recommended - fast & easy)

# Clean cached build artifacts and dependencies
horus run --clean

# This clears .horus/cache/, .horus/bin/ and any top-level target/,
# forcing a fresh build with the new version.
# Note: it does NOT remove .horus/target/ — see Option 2 for that.

Alternative Solutions:

Option 2: Manual cleanup

# Remove the entire .horus directory
rm -rf .horus/

# Next run will rebuild from scratch
horus run

Option 3: Manual recovery (for persistent issues)

# Only needed if --clean doesn't work
# This reinstalls HORUS libraries globally
cd /path/to/horus
cargo clean && rm -rf ~/.horus/cache ~/.cache/horus && ./install.sh

For multiple projects:

# Clean all projects in your workspace
find ~/your-projects -type d -name ".horus" -exec rm -rf {}/target/ \;

"HORUS source not found" (Rust projects)

Error: HORUS source not found. This can happen after running 'horus clean -a'.

To fix this, either:
 1. Re-run the install script: ./install.sh (or curl the installer)
 2. Set HORUS_SOURCE environment variable to your HORUS source directory
 3. Clone HORUS to ~/softmata/horus or ~/horus

Solution:

# Option 1: Set HORUS_SOURCE (recommended for non-standard installations)
export HORUS_SOURCE=/path/to/horus
echo 'export HORUS_SOURCE=/path/to/horus' >> ~/.bashrc

# Option 2: Install HORUS to a standard location
# The CLI checks these paths automatically, in order:
#   - /horus
#   - ~/softmata/horus
#   - ~/horus
#   - /opt/horus
#   - /usr/local/horus
#
# It also falls back to the installer's source cache, which has two roots
#   (the exact-version directory is preferred, then any horus@* tree):
#   - ~/.cache/horus/horus@<version>   (XDG: $XDG_CACHE_HOME/horus, or ~/Library/Caches/horus on macOS)
#   - ~/.horus/cache/horus@<version>   (what install.sh actually writes)

# Verify HORUS source is found
horus build

Why this happens:

  • horus run needs to find HORUS core libraries for Rust compilation
  • It auto-detects standard installation paths
  • For custom installations, set $HORUS_SOURCE

Topic Creation Errors

Symptom: Application crashes on startup with:

Error: Failed to create `Topic<MyMessage>`
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value'

Common Causes:

  1. Stale Shared Memory from Previous Run

    • HORUS uses the /dev/shm/horus_<namespace>/ directory for communication (horus_default unless HORUS_NAMESPACE is set)
    • If your app crashes, these files persist

    Fix: Clean shared memory:

    # Remove all HORUS shared memory (scans every horus_* namespace)
    horus clean --shm
    
  2. Insufficient Permissions on /dev/shm

    Fix: Check permissions:

    ls -la /dev/shm/horus_default/topics/
    # Should show your user as owner
    
    # If not, remove with sudo
    sudo rm -rf /dev/shm/horus_default/
    
    # Fix permissions (if needed)
    sudo chmod 1777 /dev/shm
    
  3. Disk Space Full on /dev/shm

    Fix: Check available space:

    df -h /dev/shm
    
  4. Conflicting Topic Names

    • Two Topics with same name but different types

    Fix: Use unique topic names:

    // BAD: Same name, different types
    let topic1: Topic<f32> = Topic::new("data")?;
    let topic2: Topic<String> = Topic::new("data")?;  // CONFLICT!
    
    // GOOD: Different names
    let topic1: Topic<f32> = Topic::new("sensor_data")?;
    let topic2: Topic<String> = Topic::new("status_data")?;
    

General Code Fix:

// Topic names become file paths on the underlying shared memory system.
// Use simple, descriptive names with dots (not slashes):
let topic = Topic::new("sensor_data")?;
let topic = Topic::new("camera.front.raw")?;

"No such file or directory" when creating Topic

Symptom: Application crashes with:

thread 'main' panicked at 'Failed to create publisher 'camera': No such file or directory'

Cause: You're using slashes (/) in your topic name. While slashes work on Linux (parent directories are created automatically), they fail on macOS where shared memory uses shm_open() which doesn't support embedded slashes.

HORUS topic names map to files under /dev/shm/horus_<namespace>/topics/ on Linux (the namespace is default unless HORUS_NAMESPACE is set):

Topic: "sensors.camera"  →  /dev/shm/horus_default/topics/sensors.camera  (cross-platform)
Topic: "sensors/camera"  →  /dev/shm/horus_default/topics/sensors/camera  (Linux only, fails on macOS)

Fix: Use dots instead of slashes for cross-platform compatibility:

// NOT RECOMMENDED - fails on macOS
let topic: Topic<f32> = Topic::new("sensors/camera")?;
let topic: Topic<Twist> = Topic::new("robot/cmd_vel")?;

// RECOMMENDED - works on all platforms
let topic: Topic<f32> = Topic::new("sensors.camera")?;
let topic: Topic<Twist> = Topic::new("robot.cmd_vel")?;

Coming from ROS? ROS uses slashes (/sensor/lidar) because it uses network-based naming. HORUS uses dots because topic names map directly to shared memory file names. See Topic Naming for details.


Topic Not Found / No Messages Received

Symptom: Subscriber node never receives messages even though publisher is sending.

// recv() always returns None
if let Some(data) = self.data_sub.recv() {
    println!("Got data");  // Never prints
}

Common Causes:

  1. Topic Name Mismatch (Typo)

    • This is the #1 cause

    Fix: Verify exact topic names:

    // Publisher
    let pub_topic: Topic<f32> = Topic::new("sensor_data")?;  // Note: sensor_data
    
    // Subscriber (TYPO! Missing underscore)
    let sub_topic: Topic<f32> = Topic::new("sensordata")?;
    
    // CORRECT:
    let sub_topic: Topic<f32> = Topic::new("sensor_data")?;  // Exact match
    

    Debug with Monitor:

    horus monitor
    

    Check the "Topics" section to see active topic names.

  2. Publisher Hasn't Sent Yet

    • Subscriber starts before publisher sends first message
    • This is normal! First recv() will return None

    Fix: Check multiple ticks:

    impl Node for SubscriberNode {
        fn tick(&mut self) {
            if let Some(msg) = self.topic.recv() {
                // Process message
            } else {
                // No message yet - this is OK on first few ticks
            }
        }
    }
    
  3. Wrong Priority Order

    • Subscriber runs before publisher in same tick

    Fix: Set priorities correctly:

    // Publisher should run first (lower order number)
    scheduler.add(PublisherNode::new()?).order(0).done();
    
    // Subscriber runs after (higher order number)
    scheduler.add(SubscriberNode::new()?).order(1).done();
    

"Type mismatch on topic ..."

A type mismatch is not a cause of silence — it fails loudly, at construction, before recv() is ever reached. It gets its own section because it is easy to mistake for a delivery problem.

Symptom: Topic::new() returns an error on startup:

Communication error: Failed to create topic 'data': type mismatch. Existing type 'f32', attempted 'f64'.
Two processes opened the same topic with different message types.
Fix: use distinct topic names for different message types.

Cause: A topic's message type is fixed by whichever process creates it first. Opening the same name with a different type is rejected, so you get an error rather than zero messages.

Fix: Use the same type on both sides, or pick distinct topic names:

// Publisher
let pub_topic: Topic<f32> = Topic::new("data")?;
pub_topic.send(3.14);

// Subscriber (WRONG TYPE) — Topic::new returns Err here
let sub_topic: Topic<f64> = Topic::new("data")?;  // f64 != f32

// CORRECT:
let sub_topic: Topic<f32> = Topic::new("data")?;  // Same type

Application Hangs / Deadlock

Symptom: Your app starts but freezes with no error messages.

Starting application...
[Nodes initialized]
[Application freezes - no output]

Common Causes:

  1. Infinite Loop in tick()

    // BAD: Never returns!
    fn tick(&mut self) {
        loop {
            // Process data
        }
    }
    
    // GOOD: Tick returns after work
    fn tick(&mut self) {
        self.process_data();
        // Return naturally — scheduler calls tick() again next frame
    }
    
  2. Blocking Operations in tick()

    // BAD: Blocks scheduler
    fn tick(&mut self) {
        std::thread::sleep(Duration::from_secs(10));  // Blocks everything!
    }
    
    // GOOD: Use tick counter for delays
    fn tick(&mut self) {
        self.tick_count += 1;
    
        // Execute every 10 ticks (~100ms at the default 100 Hz tick rate)
        if self.tick_count % 10 == 0 {
            self.slow_operation();
        }
    }
    
  3. Waiting Forever for Messages

    // BAD: Blocking wait
    fn tick(&mut self) {
        while self.data_sub.recv().is_none() {
            // Infinite loop if no messages!
        }
    }
    
    // GOOD: Non-blocking receive
    fn tick(&mut self) {
        if let Some(data) = self.data_sub.recv() {
            // Process data
        }
        // Continue even if no message
    }
    
  4. Circular Priority Dependencies

    • Node A waits for Node B, Node B waits for Node A

    Fix: Ensure data flows one direction:

    // BAD: Circular dependency
    // Node A (priority 0) subscribes to "data_b"
    // Node B (priority 1) subscribes to "data_a"
    // Both wait for each other!
    
    // GOOD: Unidirectional flow
    // Node A (priority 0) publishes to "data_a"
    // Node B (priority 1) subscribes to "data_a", publishes to "data_b"
    // Node C (priority 2) subscribes to "data_b"
    
  5. Debug with Logging

    fn tick(&mut self) {
        hlog!(debug, "Tick started");
        // Your code here
        hlog!(debug, "Tick completed");
    }
    

    If you see "Tick started" but never "Tick completed", the hang is in your code.


Messages Silently Dropped

Symptom: Publisher sends messages but subscriber never receives them, and no error is reported.

Cause: send() is lossy. The usual cause is a full ring buffer — the subscriber isn't draining it fast enough. When the ring is full, send() retries briefly (one immediate retry, then 64 spins and 4 yields) and then drops the message, incrementing the topic's drop counter.

Message size alone rarely causes drops: serialized payloads larger than 4KB are spilled to a shared TensorPool automatically, and a payload that still exceeds the current slot (default 8KB) triggers an automatic slot grow followed by a retry.

A second cause, which dropped_count() cannot see: on a broadcast POD topic (PodShm — a fixed-size message with more than one subscriber) the producer never fails to send. It overwrites the oldest slot instead. A consumer that falls a full ring behind is lapped: it detects the overwrite, resumes roughly half a ring back from the head, and the messages in between are gone. This loss happens on the receive side, so the publisher's dropped_count() stays at 0 throughout. If messages are vanishing on a multi-subscriber topic while the drop counter reads zero, this is why — the fix is the same as below, drain faster or publish slower.

Fix:

1. Confirm the drops and count them:

// dropped_count() = send() calls that gave up after the bounded retry
if self.topic.dropped_count() > 0 {
    hlog!(warn, "{} messages dropped on '{}'",
          self.topic.dropped_count(), self.topic.name());
}

This counter is per-process and lives in the publishing process — it is not written to shared memory, so read it from the node that owns the topic rather than looking for it in horus monitor. (From Python: topic.stats()["send_failures"].)

2. Let the subscriber drain the ring:

Give the subscriber a tick rate at least as high as the publisher's:

scheduler.add(PublisherNode::new()?).order(0).rate(50_u64.hz()).done()?;
scheduler.add(SubscriberNode::new()?).order(1).rate(200_u64.hz()).done()?;

Or drain everything queued on each tick instead of a single message:

fn tick(&mut self) {
    while let Some(msg) = self.topic.recv() {
        self.process(msg);
    }
}

3. Give a bursty publisher a deeper ring:

// Default capacity is one 4KB page worth of slots, clamped to 16..=1024
// and rounded up to a power of two. Ask for more explicitly:
let topic: Topic<SensorData> = Topic::with_capacity("sensor_data", 1024, None)?;

4. For messages you cannot afford to lose on a point-to-point topic, use send_blocking():

(It applies no backpressure on a broadcast topic — those overwrite rather than fill, so the call returns immediately. There, the fix is a faster consumer or a deeper ring.)

use std::time::Duration;

// Waits for space instead of dropping; Err(SendBlockingError::Timeout)
// if the ring stayed full for the whole timeout
if let Err(e) = self.cmd_topic.send_blocking(cmd, Duration::from_millis(5)) {
    hlog!(error, "cmd_vel send timed out: {}", e);
}

If you suspect an oversized payload instead, message size rarely causes drops (see above), but you can check it directly:

use std::mem::size_of;

// POD messages always fit (slot = size_of::<T>())
// Non-POD messages are serialized; payloads over 4KB are spilled to a TensorPool
println!("Message size: {} bytes", size_of::<MyMessage>());

A #[repr(C)] #[derive(Clone, Copy)] POD message skips serialization on the hot path — the ring slot is a memcpy of T — but Clone + Serialize + Deserialize are still required trait bounds on every message type, so the derives stay. If you keep a serde message with a large fixed array, note that serde only derives for arrays up to length 32 — longer ones need serde_arrays (add serde_arrays = "0.2" to your dependencies):

#[derive(Clone, Serialize, Deserialize)]
pub struct LargeMessage {
    #[serde(with = "serde_arrays")]
    pub data: [u8; 4096],  // Fixed 4KB
}

Build and Compilation Issues

"unresolved import" or "cannot find type in this scope"

Symptom: Code won't compile, missing types or functions.

Fix: Add HORUS to horus.toml:

[dependencies]
horus = "*"

Import the prelude:

use horus::prelude::*;  // Provides Twist, LaserScan, CmdVel, etc.

horus::prelude::* re-exports the standard robotics messages (CmdVel, Twist, Imu, LaserScan, BatteryState, …), so horus is the only dependency you need — there is no separate message crate to add.

"trait bound ... is not satisfied"

Symptom: Compiler says your message doesn't implement required traits.

Fix: Add required derives:

// Clone + Serialize + Deserialize are required on every message
// (Debug is optional, but worth having)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MyMessage {
    pub field: f32,
}

Performance Issues

Problem: Slow builds

Solution:

# Use release mode (optimized)
horus run --release

Problem: Large disk usage

Solution:

# Clean old cargo cache
cargo clean

# Remove unused dependencies
cargo install cargo-cache
cargo cache --autoclean

Problem: Large .horus/target/ directory (Rust projects)

Why this happens:

  • Cargo stores build artifacts in .horus/target/
  • Debug builds are unoptimized and larger
  • Incremental compilation caches intermediate files

Solution:

# Clean build artifacts in current project
# (no built-in command reclaims .horus/target/ —
#  neither `horus run --clean` nor `horus clean` touches it)
rm -rf .horus/target/

# Regular cleanup (if working on multiple projects)
find . -type d -name ".horus" -exec rm -rf {}/target/ \;

# Add to .gitignore (already included in horus new templates)
echo ".horus/target/" >> .gitignore

Disk usage typical sizes:

  • .horus/Cargo.toml: ~266 bytes
  • .horus/target/debug/: ~10-100 MB (incremental builds)
  • .horus/target/release/: ~5-50 MB (optimized, no debug symbols)

Best practices:

  • .horus/target/ is in .gitignore by default
  • Clean periodically if disk space is limited
  • Only .horus/Cargo.toml and .horus/Cargo.lock are needed for rebuild

Using the Monitor to Debug

The monitor is your best debugging tool for runtime issues.

Starting the Monitor:

# Terminal 1: Run your application
horus run

# Terminal 2: Start monitor
horus monitor

The monitor ships as a plugin. If horus monitor reports "The monitor plugin is not installed", install it first with horus install horus-monitor.

Monitor Features:

1. Nodes Tab:

  • Shows all running nodes
  • Displays node state (Running, Error, Stopped)
  • Shows tick count and timing
  • Highlights nodes that aren't ticking (stuck)

2. Topics Tab:

  • Lists all active topics
  • Shows message types
  • Displays publisher/subscriber counts
  • 0 publishers = no one is sending
  • 0 subscribers = no one is listening

3. Metrics Tab:

  • IPC Latency: Communication time (should be <1µs)
  • Tick Duration: How long each node takes
  • Message Counts: Total sent/received
    • If sent > 0 but received = 0, subscriber issue
    • If sent = 0, publisher issue

4. Graph Tab:

  • Visual node graph
  • Shows message flow between nodes
  • Disconnected nodes = topic mismatch

Debug Workflow:

1. Check Nodes tab
   -> All nodes Running? (If Error, check logs)

2. Check Topics tab
   -> Topics exist? (If no, topic name typo)
   -> Publishers > 0? (If no, publisher not working)
   -> Subscribers > 0? (If no, subscriber not created)

3. Check Metrics tab
   -> Messages sent > 0? (If no, publisher not sending)
   -> Messages received > 0? (If no, subscriber not receiving)
   -> IPC latency sane? (If >1ms, system issue)

4. Check Graph tab
   -> Nodes connected? (If no, topic name mismatch)

Example Debug Session:

# Problem: Subscriber not receiving messages

# Monitor shows:
# Nodes: SensorNode (Running), DisplayNode (Running)
# Topics: "sensor_data" (1 pub, 0 sub)  <-- AHA!

# Issue: No subscribers!
# Fix: Check DisplayNode - likely wrong topic name

Reading Log Output

Log Levels

HORUS nodes can log at different severity levels:

fn tick(&mut self) {
    hlog!(debug, "Detailed info for debugging");
    hlog!(info, "Normal informational message");
    hlog!(warn, "Something unusual happened");
    hlog!(error, "Something went wrong!");
}

Log Format

Console output uses ANSI-colored formatting:

[INFO] [SensorNode] Sensor initialized
│      │            │
│      │            └─ Message
│      └─ Node name
└─ Log level (INFO, WARN, ERROR, DEBUG)

Timestamps are included in the shared memory log buffer (visible in the monitor), formatted as HH:MM:SS.mmm.


Common Patterns and Anti-Patterns

[OK] DO: Check recv() for None

fn tick(&mut self) {
    if let Some(msg) = self.topic.recv() {
        // Process message
    }
    // No message? That's OK, just continue
}

[FAIL] DON'T: Unwrap recv()

fn tick(&mut self) {
    let msg = self.topic.recv().unwrap();  // PANIC if no message!
}

[OK] DO: Use Result for errors

impl Node for MyNode {
    fn init(&mut self) -> Result<()> {
        if self.sensor.is_broken() {
            return Err(Error::node("MyNode", "Sensor initialization failed"));
        }
        Ok(())
    }
}

[FAIL] DON'T: panic!() in nodes

fn init(&mut self) -> Result<()> {
    if self.sensor.is_broken() {
        panic!("Sensor broken");  // DON'T DO THIS
    }
    Ok(())
}

[OK] DO: Keep tick() fast

fn tick(&mut self) {
    // Quick operations only
    let data = self.sensor.read_cached();
    self.topic.send(data);
}

[FAIL] DON'T: Block in tick()

fn tick(&mut self) {
    thread::sleep(Duration::from_millis(100));  // Blocks everything!
    let data = self.network.fetch();  // Network I/O blocks!
}

Best Practices

Regular Maintenance

Weekly (active development):

git pull && ./install.sh  # Pulls latest and rebuilds

After system updates:

# If Rust/GCC updated, run manual recovery
cargo clean && rm -rf ~/.horus/cache ~/.cache/horus && ./install.sh

CI/CD Integration

# In CI pipeline
./install.sh || (cargo clean && rm -rf ~/.horus/cache ~/.cache/horus && ./install.sh)

Debugging Workflow

  1. First: Check horus works

    horus --help
    
  2. If issues: Update

    git pull && ./install.sh
    
  3. If errors: Manual recovery

    cargo clean && rm -rf ~/.horus/cache ~/.cache/horus && ./install.sh
    

Getting Help

If you're still having issues:

  1. Try manual recovery:

    cargo clean && rm -rf ~/.horus/cache ~/.cache/horus && ./install.sh
    
  2. Add Debug Logging:

    // Add hlog!(debug, ...) in your nodes to trace execution
    hlog!(debug, "Node state: {:?}", self.state);
    
  3. Test with Minimal Example:

    • Strip down to simplest possible code
    • Add complexity back one piece at a time
    • Identify what causes the error
  4. Check System Resources:

    # Check available shared memory
    df -h /dev/shm
    
    # Check HORUS files
    ls -lh /dev/shm/horus_default/topics/
    
    # Clean if needed (scans every horus_* namespace)
    horus clean --shm
    
  5. Report the issue:


Next Steps