Skip to content

Modernize agent I/O with CompletableFuture and BufferedReader.#79

Draft
DomiKoPL wants to merge 1 commit intoCodinGame:masterfrom
DomiKoPL:modernize-agent-io
Draft

Modernize agent I/O with CompletableFuture and BufferedReader.#79
DomiKoPL wants to merge 1 commit intoCodinGame:masterfrom
DomiKoPL:modernize-agent-io

Conversation

@DomiKoPL
Copy link

Motivation

During the last contest we observed a high timeout rate, especially during league openings, the final stretch, and the rerun. Players at the top of Legend got eliminated by timeouts that were clearly caused by platform load rather than their own code.

One likely contributor is the polling loop in RefereeAgent.getOutput(), which used InputStream.available() to check whether data was ready to read, sleeping 1 ms between checks. available() only returns bytes already in the kernel buffer — it is not aware of bytes that are in transit or buffered elsewhere. Under server load, this may cause the loop to spin past the timeout deadline even when the bot had already responded in time, producing spurious timeouts.

See also: #78 (soft-timeout approach tackling the same problem).

Changes

  • Non-blocking timeouts via CompletableFuture.orTimeout — replaces the available() busy-wait loop with a blocking BufferedReader.readLine() offloaded to a dedicated I/O thread. The future times out precisely after timeout ms regardless of server load.
  • Dedicated ExecutorService (AGENT_IO_EXECUTOR) — a single daemon thread handles all blocking reads, avoiding ForkJoinPool starvation that CompletableFuture.supplyAsync with the common pool would risk.
  • BufferedReader instead of raw InputStream byte-reads — simpler line-based reading replaces the manual byte-by-byte loop with CR/LF bookkeeping.
  • Removed processStdout / getOutputStream() — the raw InputStream field is no longer needed; only the BufferedReader is used.
  • Deduplicated RefereeAgent.getOutput() — ~50 lines of duplicated timing logic removed; now delegates to Agent.getOutput() with the appropriate buffer size limit.

- Replace busy-wait polling loop in RefereeAgent.getOutput() with
  CompletableFuture + orTimeout for precise, non-blocking timeouts
- Use a dedicated single-thread ExecutorService (AGENT_IO_EXECUTOR)
  for blocking I/O to avoid ForkJoinPool starvation
- Switch to BufferedReader (readLine) instead of raw InputStream reads
- Remove unused processStdout InputStream field and abstract
  getOutputStream() method — only the BufferedReader is needed now
- Deduplicate timeout logic: RefereeAgent now delegates to
  Agent.getOutput() instead of reimplementing it
- Clean up unused imports (InputStreamReader, TimeoutException)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant