private void processManagerLoop() {
		while (true) {
			if (cmdCount == 0) {
				Msg.debug(this, "Starting new DMG server process");
			}
			else {
				Msg.debug(this, "Re-starting DMG server process, cmd count: " + cmdCount);
			}
			this.process = createProcess();
			if (process == null) {
				Msg.error(this, "Failed to create new DMG server process, exiting cmd loop");
				return;
			}

			BufferedReader inputReader =
				new BufferedReader(new InputStreamReader(process.getInputStream()));
			PrintWriter outputWriter = new PrintWriter(process.getOutputStream());
			startReaderThread(new BufferedReader(new InputStreamReader(process.getErrorStream())));

			// hack to send the inital "open" to the dmg server process
			Cmd startupCmd = new Cmd("open " + file.getAbsolutePath(), 0);

			try {
				while (++cmdCount % dmgServerMaxCmdPerSession != 0) {
					Cmd cmd = (startupCmd != null) ? startupCmd : cmdQueue.take();
					startupCmd = null;

					synchronized (cmd) {
						try {
							if (cmd.cmdStr != null) {
								watchdog.arm();
								outputWriter.println(cmd.cmdStr);
								outputWriter.flush();

								int expectedResponseCount = cmd.expectedResponseCount;
								if (expectedResponseCount == Cmd.UNKNOWN_RESPONSE_COUNT) {
									expectedResponseCount = readInt(inputReader);
								}
								else if (expectedResponseCount < 0) {
									int nestedResponseCount = -expectedResponseCount;
									expectedResponseCount =
										readInt(inputReader) * nestedResponseCount;
								}
								cmd.results = new ArrayList<>(expectedResponseCount);
								for (int i = 0; i < expectedResponseCount; i++) {
									String s = inputReader.readLine();
									if (s == null) {
										throw new IOException(
											"EOF while reading results from DMG Server");
									}
									cmd.results.add(s);
								}
							}
						}
						catch (IOException ioe) {
							cmd.error = ioe;
							break;// break cmd loop, destroy process and start a new one
						}
						finally {
							watchdog.disarm();
							cmd.notifyAll();
						}
					}
					if (cmd.cmdStr == null) {
						return;// shutdown, exit entire cmd loop
					}
				}
			}
			catch (InterruptedException ie) {
				Msg.error(this, "IntrError", ie);
				return;// shutdown the server processing loop
			}
			finally {
				Msg.info(this, "DMG server process destroyed");
				process.destroy();
				try {
					int exitCode = process.waitFor();
					Msg.debug(this, "DMG Server process exited with: " + exitCode);
				}
				catch (InterruptedException e) {
					// ignore
				}
				process = null;
			}
		}
	}
Manages the DMG server process and communication with it.
<p>
All IO to the server process takes place in a single background worker thread
(see {@link #processManagerLoop()}) that receives {@link Cmd commands} from
other threads via a {@link #cmdQueue queue}, writes the cmd to the stdin of
the server process, and then reads the responses from its stdout, and then
packages up the response and notifies the waiting caller thread that the
results are ready.
<p>
This ensures that there are no synchronous issues with communicating with the
server process and that the logic to restart the server if it fails is all in
one place.
<p>
A {@link Watchdog} implementation is used to force timeouts on IO operations
by killing the server process.
Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dmg/DmgServerProcessManager.java