zinit 0.3.9

Process supervisor with dependency management
Documentation
# Port Conflict Detection

## Overview

Zinit includes built-in port conflict detection to prevent multiple services from attempting to bind to the same TCP port. When a service declares that it uses specific ports and another service is already using those ports, the startup will be blocked with a clear error message.

## Declaring Ports

Services can declare the TCP ports they use through multiple interfaces:

### Via CLI (ServiceConfigBuilder)

```rust
use zinit::client::client::ServiceConfigBuilder;

let config = ServiceConfigBuilder::new("web_server")
    .exec("python3 -m http.server 8080")
    .port(8080)
    .build();
```

You can declare multiple ports:

```rust
let config = ServiceConfigBuilder::new("multi_port_service")
    .exec("/usr/bin/myapp")
    .port(8080)
    .port(8443)
    .build();
```

### Via Rhai Scripting

```rhai
let svc = ZinitFactory::service()
    .name("web_server")
    .exec("python3 -m http.server 8080")
    .port(8080)
    .build();
```

Multiple ports in Rhai:

```rhai
let svc = ZinitFactory::service()
    .name("multi_port_service")
    .exec("/usr/bin/myapp")
    .port(8080)
    .port(8443)
    .build();
```

### Via TOML Configuration

```toml
[services.web_server]
exec = "python3 -m http.server 8080"
ports = [8080]

[services.multi_port]
exec = "/usr/bin/myapp"
ports = [8080, 8443]
```

## Port Conflict Behavior

### Detection Time

Port conflicts are checked when a service is being started. The check happens:

1. After dependency checks (waiting_on)
2. After conflict checks (ConflictsWith)
3. Before the actual process spawning

### Error Messages

When a port conflict is detected, the service startup is blocked with a clear error:

```
Error: port conflict on 8080, 9000: already in use by web_server, db_service
```

This message tells you:
- Which ports are conflicted (8080, 9000)
- Which running services are using them (web_server, db_service)

### Blocking Reason

The `BlockedReason::PortConflict` variant is returned with:
- `ports: Vec<u16>` - The list of conflicting port numbers
- `services: Vec<String>` - The names of services using those ports

## Implementation Details

### Server-Side Checking

The port conflict detection happens in the supervisor's `can_start` method when evaluating whether a service can be started. The check:

1. Retrieves the service's declared ports from its configuration
2. Iterates through all running services
3. Checks if any running service declares the same ports
4. Returns an error if conflicts are found

**File**: `src/server/graph.rs:can_start()`

```rust
// Check port conflicts with running services
let port_conflict = if let Some(config) = service.service_config() {
    if !config.service.ports.is_empty() {
        // ... find conflicting services ...
    }
};

if let Some((ports, services)) = port_conflict {
    return Err(BlockedReason::PortConflict {
        ports,
        services,
    });
}
```

### Port Declaration Storage

Ports are stored in the service definition:

```rust
pub struct ServiceDef {
    pub name: String,
    pub exec: String,
    // ... other fields ...
    /// TCP ports used by this service
    #[serde(default)]
    pub ports: Vec<u16>,
}
```

**File**: `src/sdk/config.rs:ServiceDef`

## Error Handling

### Client-Side

When starting a service via the client that has a port conflict:

```rust
match z.service_start("web_server") {
    Ok(()) => println!("Service started"),
    Err(e) => eprintln!("Failed: {}", e),
    // Output: Failed: port conflict on 8080: already in use by other_service
}
```

### CLI

```bash
$ zinit start conflicting_service
✗ Error: port conflict on 8080: already in use by web_server

$ zinit status conflicting_service
State: Blocked (port conflict on 8080: already in use by web_server)
```

### Rhai Scripts

```rhai
let result = z.start("web_server");
if result.is_err() {
    print("Port conflict detected!");
}
```

## Best Practices

### 1. Declare All Ports

List all ports your service uses, including those used by child processes:

```toml
[services.app]
exec = "my_app"
# Include main port and any internal communication ports
ports = [8080, 9000]
```

### 2. Use Unique Ports

Ensure each service uses different ports:

```toml
[services.api]
ports = [8080]

[services.database]
ports = [5432]

[services.cache]
ports = [6379]
```

### 3. Dynamic Port Assignment

For services that use dynamic ports, don't declare them (or declare known ports):

```rhai
// If port is dynamically chosen, you might not declare it
let svc = ZinitFactory::service()
    .name("dynamic_service")
    .exec("./app --port 0")  // OS assigns port
    .build();
```

### 4. Handle Port Conflicts

Check for port conflicts in startup scripts:

```bash
# Reserve a port before starting zinit
PORT=8080
netstat -tln | grep $PORT && {
    echo "Port $PORT already in use"
    exit 1
}
```

## Limitations and Future Work

### Current Limitations

1. **TCP Only**: Only TCP port conflicts are detected. UDP ports are not checked.
2. **Declared Ports Only**: Only ports explicitly declared in the service config are checked.
3. **No Port Ranges**: Individual ports must be listed; ranges like `8000-8100` are not supported.
4. **No External Services**: Ports used by services not managed by zinit are not detected.

### Future Enhancements

- UDP port conflict detection
- Port range support (e.g., `ports = [8000-8100]`)
- System port scanning to detect external service conflicts
- Port allocation hints/suggestions for conflicting services
- Health check integration to verify port availability

## Troubleshooting

### Service Won't Start Due to Port Conflict

**Problem**: A service fails to start with "port conflict" error

**Solution**:
1. Check which service is using the port: `zinit status <service>`
2. Stop the conflicting service: `zinit stop <other_service>`
3. Restart your service: `zinit start <service>`

### Unknown Port Usage

**Problem**: A port shows in use but you don't know by what

**Solution**:
1. Check all declared ports: `zinit list --details`
2. Use system tools: `lsof -i :<port>` or `netstat -tln | grep <port>`
3. Check service configuration files

### False Positive Conflict

**Problem**: Port conflict detected but services aren't actually using those ports

**Solution**:
1. Verify the service declaration matches actual port usage
2. Check if port is released cleanly on shutdown
3. Use `netstat` to confirm port is actually in use

## See Also

- [Service Configuration]./SERVICE_SPECS.md - Complete service definition format
- [Rhai Scripting]../rhai/ - Full Rhai API reference
- [Internals]./Internals.md - Supervisor and graph implementation details