|
| 1 | +# Java Runner Client: Process Management Technical Details |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Java Runner Client (JRC) spawns and manages single Java processes via Node.js `child_process.spawn()`. Each profile corresponds to one independent Java process. The term "managed process" refers to JRC's internal wrapper tracking that process, not a separate secondary process. |
| 6 | + |
| 7 | +## Process Launch Architecture |
| 8 | + |
| 9 | +### Single Java Process Spawning |
| 10 | + |
| 11 | +When you start a Java process, JRC: |
| 12 | + |
| 13 | +1. **Builds Arguments** (`buildArgs()` method): |
| 14 | + - Collects all enabled JVM arguments from the profile (e.g., `-Xmx2048m`, `-Xms512m`) |
| 15 | + - Adds system properties via `-D` flags (e.g., `-Dapp.name=value`) |
| 16 | + - Appends `-jar` with the JAR path |
| 17 | + - Adds program arguments passed to the Java application |
| 18 | + - Example output: `java -Xmx2048m -Dkey=value -jar /path/to/app.jar arg1 arg2` |
| 19 | + |
| 20 | +2. **Spawns Direct Process**: |
| 21 | + ```typescript |
| 22 | + proc = spawn(cmd, args, { |
| 23 | + cwd, env: process.env, |
| 24 | + shell: false, // Direct execution, no shell overhead |
| 25 | + detached: false, |
| 26 | + stdio: ['pipe', 'pipe', 'pipe'] // Captures stdin, stdout, stderr |
| 27 | + }) |
| 28 | + ``` |
| 29 | + - Uses `shell: false` for efficiency (direct JVM launch, no shell wrapper) |
| 30 | + - Inherits environment variables from the Electron app |
| 31 | + - Pipes all I/O streams so ProcessManager can capture real-time output |
| 32 | + |
| 33 | +### The "Managed Process" Wrapper |
| 34 | + |
| 35 | +The `ManagedProcess` interface is **not a second process** — it's a lightweight wrapper object tracking the spawned process: |
| 36 | + |
| 37 | +```typescript |
| 38 | +interface ManagedProcess { |
| 39 | + process: ChildProcess // Node.js child process handle |
| 40 | + profileId: string // Profile identifier |
| 41 | + profileName: string // Human-readable name |
| 42 | + jarPath: string // JAR file path |
| 43 | + startedAt: number // Start timestamp |
| 44 | + intentionallyStopped: boolean // User explicitly stopped it? |
| 45 | +} |
| 46 | +``` |
| 47 | + |
| 48 | +Managed processes are stored in a `Map<string, ManagedProcess>` (keyed by profile ID) to: |
| 49 | +- Track which profiles have active processes |
| 50 | +- Store execution metadata |
| 51 | +- Enable lifecycle management (stop, restart, monitor) |
| 52 | + |
| 53 | +## Real-Time I/O Capturing |
| 54 | + |
| 55 | +ProcessManager captures stdout and stderr in real-time: |
| 56 | + |
| 57 | +```typescript |
| 58 | +proc.stdout?.on('data', (chunk: string) => this.pushOutput(profile.id, chunk, 'stdout', managed)) |
| 59 | +proc.stderr?.on('data', (chunk: string) => this.pushOutput(profile.id, chunk, 'stderr', managed)) |
| 60 | +``` |
| 61 | + |
| 62 | +Key features: |
| 63 | +- Each output line receives a **persistent, incrementing counter ID** (survives process restarts) |
| 64 | +- IDs are sequential numbers (1, 2, 3...) rather than composite strings |
| 65 | +- Duplicate detection throws an error if ID collision occurs |
| 66 | +- Line IDs are tracked in a Set per-profile for O(1) lookup |
| 67 | +- UI receives output via Electron IPC (`CONSOLE_LINE` message) |
| 68 | + |
| 69 | +### Deduplication & Tracking |
| 70 | + |
| 71 | +The ProcessManager maintains persistent state per-profile: |
| 72 | +- **`lineCounters`** Map: Incrementing counter that doesn't reset on restart |
| 73 | +- **`seenLineIds`** Map: Set of all seen IDs to detect collisions |
| 74 | +- Auto-cleanup triggers at 10k entries to prevent unbounded memory growth |
| 75 | +- Each line gets unique sequential ID, ensuring no duplicates across 5000+ entries |
| 76 | + |
| 77 | +## Process Lifecycle Management |
| 78 | + |
| 79 | +### Real-Time Monitoring |
| 80 | + |
| 81 | +```typescript |
| 82 | +proc.on('exit', (code, signal) => { /* handling */ }) |
| 83 | +proc.on('error', (err) => { /* handling */ }) |
| 84 | +``` |
| 85 | + |
| 86 | +- Detects natural termination or crashes |
| 87 | +- Records exit code and signal information |
| 88 | +- Stores metadata in activity log (max 500 entries, UUID-based) |
| 89 | +- Broadcasts state update to UI |
| 90 | + |
| 91 | +### Graceful Shutdown |
| 92 | + |
| 93 | +**Windows** (primary method): |
| 94 | +``` |
| 95 | +taskkill /PID {pid} /T /F |
| 96 | +``` |
| 97 | +- `/T`: Kill process tree (includes child processes) |
| 98 | +- `/F`: Force termination if graceful fails |
| 99 | + |
| 100 | +**Unix** (tiered approach): |
| 101 | +1. Send `SIGTERM` (graceful shutdown) |
| 102 | +2. Wait 5 seconds |
| 103 | +3. Send `SIGKILL` if still running (force termination) |
| 104 | + |
| 105 | +### Auto-Restart Logic |
| 106 | + |
| 107 | +If process exits with non-zero code AND `autoRestart` is enabled: |
| 108 | +1. Waits `autoRestartInterval` seconds (default: 10s) |
| 109 | +2. Uses stored profile snapshot (captures config state at start) |
| 110 | +3. Automatically restarts with identical settings |
| 111 | +4. Profile snapshots update whenever user saves configuration |
| 112 | + |
| 113 | +## Process Discovery & Scanning |
| 114 | + |
| 115 | +JRC can enumerate all Java processes system-wide (not just managed ones): |
| 116 | + |
| 117 | +**Windows**: PowerShell WMI query |
| 118 | +- Retrieves command line for all processes |
| 119 | +- Extracts memory usage, thread count, start time |
| 120 | +- Identifies Java processes by `java.exe` presence |
| 121 | +- Fallback to `tasklist /fo csv` if WMI unavailable |
| 122 | + |
| 123 | +**Unix**: `ps -eo pid,comm,args` |
| 124 | +- Parses system process list |
| 125 | +- Identifies JAR files via `-jar` flag regex |
| 126 | +- Extracts memory and thread info from procfs |
| 127 | + |
| 128 | +Each discovered process is marked as `managed: boolean` (whether JRC started it). |
| 129 | + |
| 130 | +## Technical Efficiency |
| 131 | + |
| 132 | +### Direct Execution |
| 133 | +- `shell: false` avoids shell wrapper overhead |
| 134 | +- Significant performance improvement for frequent restarts |
| 135 | +- Direct JVM launch with minimal context overhead |
| 136 | + |
| 137 | +### Persistent State |
| 138 | +- Line counters survive process restarts (not tied to ManagedProcess lifecycle) |
| 139 | +- Activity log maintains historical execution data |
| 140 | +- Profile snapshots capture exact configuration at start time |
| 141 | + |
| 142 | +### Memory Management |
| 143 | +- Activity log auto-pops entries beyond 500 |
| 144 | +- Line ID tracking Set auto-clears at 10k entries |
| 145 | +- Map-based storage with O(1) lookups for process state |
| 146 | + |
| 147 | +### Environment Isolation |
| 148 | +- Each process can inherit custom working directories |
| 149 | +- System properties configurable via `-D` flags |
| 150 | +- Separate stdout/stderr streams prevent log mixing |
| 151 | + |
| 152 | +## IPC Communication Flow |
| 153 | + |
| 154 | +``` |
| 155 | +┌─────────────────────────┐ |
| 156 | +│ Java Process │ (stdout/stderr output) |
| 157 | +└────────────┬────────────┘ |
| 158 | + │ [piped to Node.js] |
| 159 | + ↓ |
| 160 | +┌─────────────────────────┐ |
| 161 | +│ ProcessManager │ (captures, formats with line ID) |
| 162 | +└────────────┬────────────┘ |
| 163 | + │ |
| 164 | + ↓ Electron IPC: CONSOLE_LINE |
| 165 | +┌─────────────────────────┐ |
| 166 | +│ Renderer (React) │ (console component) |
| 167 | +└────────────┬────────────┘ |
| 168 | + │ |
| 169 | + ↓ |
| 170 | +┌─────────────────────────┐ |
| 171 | +│ User UI │ (displays real-time output) |
| 172 | +└─────────────────────────┘ |
| 173 | +``` |
| 174 | + |
| 175 | +## Single vs. Multiple Processes |
| 176 | + |
| 177 | +JRC's single-process-per-profile design means: |
| 178 | +- Each profile spawns exactly one Java process |
| 179 | +- Multiple profiles = multiple independent processes |
| 180 | +- No inter-process communication overhead |
| 181 | +- Each process gets isolated I/O, environment, and lifecycle management |
| 182 | +- Simplifies troubleshooting (one PID per configuration) |
| 183 | + |
| 184 | +## Data Structures |
| 185 | + |
| 186 | +| Map | Purpose | Key | Value | Lifecycle | |
| 187 | +|-----|---------|-----|-------|-----------| |
| 188 | +| `processes` | Active managed processes | Profile ID | ManagedProcess | Per-process | |
| 189 | +| `lineCounters` | Persistent line numbering | Profile ID | Counter number | Per-profile (survives restarts) | |
| 190 | +| `seenLineIds` | Duplicate detection | Profile ID | Set of IDs | Per-profile (auto-cleans at 10k) | |
| 191 | +| `restartTimers` | Pending auto-restart timers | Profile ID | Timeout handle | Until restart or stop | |
| 192 | +| `profileSnapshots` | Saved configurations | Profile ID | Profile object | While process running | |
| 193 | +| `activityLog` | Execution history | — | ProcessLogEntry[] | Global (max 500) | |
0 commit comments