Memra

Worked build: the prime service, and its failure modes

◈ 6 cards

Assemble A3 question 2 end to end — interface, implementation, server bootstrap, command-line client — then read the five exceptions that actually happen and know what each one means.

Stage 1 — the contract

One file, shared by both programs: PrimeService extends Remote, with long largestPrimeInRange(long low, long high) throws RemoteException, and a public static final String NAME = "PrimeService" so neither side can invent its own spelling. Compile it once and put the same .class on both classpaths.

Stage 2 — the implementation

PrimeServiceImpl implements PrimeService, and nothing else. Search downwards from high so the first prime found is the largest, stop at max(low, 2), and test each candidate by trial division to its square root. Validate first: low > high throws IllegalArgumentException. Document the empty case — no prime in range returns -1. Nothing in this class mentions the network, so you can test it in a main before RMI is involved at all, and you should.

Stage 3 — the server main

Four statements, in this order, and one field.

  1. impl = new PrimeServiceImpl(); into a static field — a strong reference that outlives main.
  2. PrimeService stub = (PrimeService) UnicastRemoteObject.exportObject(impl, 0);
  3. Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
  4. registry.rebind(PrimeService.NAME, stub); then print a line saying so.

main may now return: RMI's listener threads are non-daemon, so the JVM stays up. Stop the server with Ctrl-C.

Stage 4 — the client main

Read the range from the command line — java PrimeClient 1000 2000 [host] — and default the host to localhost. Then getRegistry, lookup, cast to PrimeService, one call, print the answer. Handle -1 as "no prime in that range" rather than printing a bare -1. Everything the client does is three lines of RMI wrapped in argument parsing; if it is longer than that, the design has drifted chatty.

Stage 5 — the five failures you will actually meet

  • ConnectException at lookup — nothing is listening on that host and port. The server is not running, or you are pointing at the wrong host. Remember that getRegistry is not the line that fails.
  • NotBoundException — the registry is there, the name is not. The two sides spell it differently, which is why NAME is a shared constant.
  • UnmarshalException caused by ClassNotFoundException — the client's classpath has no PrimeService. The stub cannot be reconstructed without the interface it implements.
  • ExportException: Port already in usecreateRegistry(1099) while an old JVM (or an rmiregistry you forgot about) still holds 1099. Find it and kill it; do not just move to another port and hide the problem.
  • ConnectException from a remote client only — the classic. InetAddress.getLocalHost() resolved to 127.0.0.1 because of an /etc/hosts line, so the stub carries 127.0.0.1, and a client on another machine dutifully connects to itself. The fix is -Djava.rmi.server.hostname=<the address other machines can reach> on the server command line. Localhost testing never reveals it.

Worked example — the acceptance run

Start the server in one terminal; it prints its bound name and stays there. In another, run the client for 1000 2000 and expect 1999. Then run the tests that matter more than the happy path: a range with no prime (8 10 → the documented message), a reversed range (2000 1000 → the IllegalArgumentException arriving at the client as the cause of a ServerException), and the client with the server stopped (ConnectException). Four runs, each one exercising a row of the table below — that is your test plan, and A3 asks for one in writing.

symptomexceptioncausefixclient dies atlookupConnectExceptionnothing listeningtherestart the serverregistry found,name notNotBoundExceptionthe two spellingsdiffershare one constantlookup cannot buildthe stubUnmarshalExceptionno interface on theclientship the interfaceserver will notstartExportExceptionport 1099 stillheldkill the old JVMonly remote clientsfailConnectExceptionstub carries127.0.0.1set the hostnamepropertyAlways print getCause() — the real message is nested.
Row three usually arrives wrapped: UnmarshalException on the outside, ClassNotFoundException as its cause — always print getCause(). Rows one and five throw the same exception for opposite reasons, which is why the symptom column exists.
NORMAL ~/memra/learn/comp-348/rmi-prime-service-build utf-8 LF