Back to Hub

Java Job Scheduler Client: Talking to a C Simulation Server

The problem

Distributed scheduling sounds abstract until a C server starts flinging jobs at your client and grades you on what you send back.

This was my client for Macquarie University's COMP6105/3100 distributed systems unit. The simulation server, ds-sim, is written in C. My job was to build a Java client that could authenticate, read job and server state from the wire, and return allocation decisions - acting as the scheduling backend for the simulation.

The interesting work was never "Java talks to C" as a headline. Languages were incidental. The protocol was the contract, and the scheduling logic was where the unit actually tested understanding.

Job Scheduling in Distributed Systems Explained

In this simulation, a scheduler decides which machine runs each incoming job. The server owns the world model: which servers exist, what capacity they have, which jobs are waiting. The client receives that state and must reply with sensible placements before the simulation moves on.

Real schedulers face the same tension in bigger systems. Utilization pushes you to pack work onto capable machines so nothing sits idle. Fairness pushes back - always favoring the biggest box can starve smaller jobs or create uneven wait times. Simplicity matters when you are debugging at 1 a.m. and the server rejects your message because you misread one field.

Cross-language client/server setups are normal here. What matters is that both sides agree on message order, field meaning, and failure behavior. The ds-sim server could stay in C while I iterated on Java scheduling strategies behind the same interface.

One thing that stuck with me while reading around the topic: scheduling shows up everywhere - OS run queues, Kubernetes pod placement, batch job queues - with different constraints but the same shape. This assignment is a toy, but the loop (state in, decision out, consequences on the next turn) is the same loop production systems live in.

How the client fits together

At a high level the client follows a fixed rhythm:

  1. Authenticate. Initial handshake with ds-sim so the server accepts the session.
  2. Receive state. Server messages describe pending jobs and available servers (cores, memory, disk - whatever the config defines).
  3. Decide. The selected algorithm picks a target server for each job.
  4. Respond. The client sends allocations back; the simulation advances and the cycle repeats until jobs are exhausted or the run ends.

I kept protocol parsing and scheduling strategy as separate concerns in code even when the file was one big client.java. Parsing errors and bad allocation logic fail in different ways, and separating them made Stage 2 easier - swap the scheduler without touching the wire format.

Verbose mode (-v) prints every command the client sends and every response from the server. That sounds noisy, but it is the difference between guessing why a handshake failed and seeing the exact line the server rejected. For distributed message flows, I treat verbose tracing as part of the tool, not a demo gimmick.

Scope note: this is coursework against a provided simulator, not a production cluster manager. ds-sim owns the physics of the simulation; I own the client-side policy.

Running it locally means two terminal sessions: one for ds-sim (for example ./ds-server -n -c ../../configs/sample-configs/sample-config02.xml per the unit setup) and one for the Java client. When something hangs, the bug is almost always in the conversation between those two processes - not in either program compiling cleanly on its own.

The three algorithms (and Start-Servers)

The client exposes three schedulers via the -a flag. I implemented all three; Stage 2 required a custom algorithm, which became Start-Servers (ss).

  • lrr (Largest-Round-Robin). Walk the server list favoring the largest capable machines, rotating among them in round-robin order. This chases utilization - big servers stay busy - but job mix matters. A stream of small jobs can behave unevenly when large servers keep getting picked.
  • fc (First-Capable). Scan servers in order and assign each job to the first machine that can run it. Easy to implement and reason about, which makes it a useful baseline. The tradeoff is waste: a small job might land on an oversized server because it appeared first in the list, leaving larger capacity unused for harder work later.
  • ss (Start-Servers). My Stage 2 design. Prefer a server that is already available (idle or ready to take work). If none are free, fall back to the first capable server - similar to fc, but availability comes first. It sits between "always pick the biggest" and "always pick the first match," and it matched how I wanted the simulation to feel: keep machines running when possible without ignoring capability constraints. I chose that rule order because the sim penalizes idle capacity quickly, but blindly picking the first fit still felt wrong when a larger server was free two slots later.

Example run with verbose logging and the custom scheduler:

javac client.java
java client -a ss -v

The README ships a precompiled client.class built on an M1 Mac inside an Ubuntu VM. I learned not to trust that on other machines - recompiling locally with javac client.java before pairing with ds-sim avoids silent bytecode mismatches.

None of these algorithms is universally "best." That was the point of implementing more than one: the assignment grades behavior under the simulator's metrics, and each policy optimizes for a different instinct about utilization, fairness, and simplicity.

What I learned

Protocol discipline beat language familiarity. I could write Java comfortably, but a misplaced field in an outbound message still failed the whole run. Reading the server's responses in verbose mode taught me more about distributed client design than rereading slides.

Algorithm choice is a product decision in miniature. First-Capable is the fast hack. Largest-Round-Robin chases throughput. Start-Servers was my attempt at a middle policy with an explicit rule order: availability first, capability second.

Portable builds matter even in assignments. Bundled class files are convenient for markers; they are fragile for anyone on a different JDK or OS. Compiling from source on the machine that runs the sim is the reliable path.

What I'd improve next

If I revisited the project today, I would split protocol I/O and scheduling into separate classes or modules, add structured logging around decision points, and capture run metrics from ds-sim output so algorithm comparisons are evidence-based instead of gut feel.

A simple message-flow diagram would help future readers more than another paragraph of prose. I would add that to the repo README once I had a screenshot or SVG worth shipping.

This stays a university simulation client - not something I would deploy as infrastructure - but it is a concrete example of client/server scheduling work I am happy to walk through in detail.

Source code