1+ import java .io .IOException ;
2+ import java .io .InputStream ;
3+ import java .io .OutputStream ;
4+ import java .net .InetSocketAddress ;
5+ import java .net .ServerSocket ;
6+ import java .net .Socket ;
7+ import java .nio .ByteBuffer ;
8+ import java .nio .charset .StandardCharsets ;
9+ import java .util .UUID ;
10+ import java .util .concurrent .Executor ;
11+ import java .util .concurrent .Executors ;
12+ import java .util .concurrent .ThreadLocalRandom ;
13+
14+ final class Controller {
15+ private final int port ;
16+ private final String boxBinaryPath ;
17+ private final Executor executor ;
18+ private ServerSocket socket ;
19+
20+ private Controller (int port , String boxBinaryPath , Executor executor ) {
21+ this .port = port ;
22+ this .boxBinaryPath = boxBinaryPath ;
23+ this .executor = executor ;
24+ }
25+
26+ public void start () throws IOException {
27+ socket = new ServerSocket ();
28+ socket .bind (new InetSocketAddress (port ));
29+ try {
30+ listen ();
31+ } finally {
32+ socket .close ();
33+ }
34+ }
35+
36+ private void listen () throws IOException {
37+ while (!Thread .currentThread ().isInterrupted () && socket .isBound ()) {
38+ var client = socket .accept ();
39+ executor .execute (() -> {
40+ try {
41+ accept (client );
42+ } catch (IOException failure ) {
43+ System .err .println (
44+ "encountered error while serving " + client .getRemoteSocketAddress ()
45+ );
46+ failure .printStackTrace ();
47+ }
48+ });
49+ }
50+ }
51+
52+ private void accept (Socket client ) throws IOException {
53+ var input = client .getInputStream ();
54+ byte [] initialPacket = input .readNBytes (Long .SIZE * 2 );
55+ var id = readId (initialPacket );
56+ System .err .println ("accepted client with id " + id );
57+ useBox (client );
58+ client .close ();
59+ }
60+
61+ private static final String [] firstNames = {
62+ "Leonard" , "Lionel" , "Elton" , "Nina" , "Art" , "Tracy" , "Freddy" ,
63+ };
64+
65+ private static final String [] lastNames = {
66+ "Cohen" , "Richie" , "John" , "Simone" , "Garfunkel" , "Chapman" , "Mercury"
67+ };
68+
69+ private void useBox (Socket client ) throws IOException {
70+ var payload = createPayload ();
71+ client .getOutputStream ().write (payload );
72+ }
73+
74+ private byte [] createPayload () {
75+ var random = ThreadLocalRandom .current ();
76+ var firstName = firstNames [random .nextInt (firstNames .length )];
77+ var lastName = lastNames [random .nextInt (lastNames .length )];
78+ var fullName = firstName + " " + lastName ;
79+ return fullName .getBytes (StandardCharsets .UTF_8 );
80+ }
81+
82+ private UUID readId (byte [] bytes ) {
83+ var buffer = ByteBuffer .wrap (bytes );
84+ return new UUID (
85+ buffer .getLong (),
86+ buffer .getLong ()
87+ );
88+ }
89+
90+ public void startBox (UUID id ) throws IOException {
91+ System .err .println ("starting box " + id );
92+ var process = startBoxProcess ();
93+ executor .execute (() -> {
94+ redirectBoxOutput (id , process );
95+ redirectBoxErrors (id , process );
96+ });
97+ }
98+
99+ private void redirectBoxOutput (UUID id , Process process ) {
100+ var prefix = "%s: " .formatted (id ).getBytes (StandardCharsets .UTF_8 );
101+ var suffix = "\r \n " .getBytes (StandardCharsets .UTF_8 );
102+ try {
103+ redirect (prefix , suffix , process .getInputStream (), System .out );
104+ } catch (IOException failedRedirect ) {
105+ System .err .println ("encountered error while redirecting " + id );
106+ failedRedirect .printStackTrace ();
107+ }
108+ }
109+
110+ private void redirectBoxErrors (UUID id , Process process ) {
111+ var prefix = "Error in %s: " .formatted (id ).getBytes (StandardCharsets .UTF_8 );
112+ var suffix = "\r \n " .getBytes (StandardCharsets .UTF_8 );
113+ try {
114+ redirect (prefix , suffix , process .getErrorStream (), System .out );
115+ } catch (IOException failedRedirect ) {
116+ System .err .println ("encountered error while redirecting " + id );
117+ failedRedirect .printStackTrace ();
118+ }
119+ }
120+
121+ private static final String boxMemory = System .getProperty ("box.memory" , "2M" );
122+
123+ private Process startBoxProcess () throws IOException {
124+ var ownAddress = ":" + port ;
125+ return new ProcessBuilder ()
126+ .command (
127+ "java" ,
128+ "-Xmx%s" .formatted (boxMemory ),
129+ "-Xms%s" .formatted (boxMemory ),
130+ "-XX:+UseSerialGC" ,
131+ /* program */ "-jar" , boxBinaryPath ,
132+ /* arguments */ UUID .randomUUID ().toString (), ownAddress
133+ ).start ();
134+ }
135+
136+ private static void redirect (
137+ byte [] prefix ,
138+ byte [] suffix ,
139+ InputStream input ,
140+ OutputStream output
141+ ) throws IOException {
142+ byte [] buffer = new byte [1024 ];
143+ int read ;
144+ while ((read = input .read (buffer )) != -1 ) {
145+ synchronized (output ) {
146+ output .write (prefix );
147+ output .write (buffer , 0 , read );
148+ output .write (suffix );
149+ }
150+ }
151+ }
152+
153+ // All logging is done to System.err since output is redirected to System.out
154+ public static void main (String [] arguments ) throws InterruptedException {
155+ validateArguments (arguments );
156+ var executor = Executors .newCachedThreadPool ();
157+ var controller = new Controller (3_1337 , arguments [0 ], executor );
158+ runController (executor , controller );
159+ int boxCount = arguments .length > 1 ? Integer .parseInt (arguments [1 ]) : 3 ;
160+ startSomeBoxes (controller , boxCount );
161+ Thread .sleep (10_000 );
162+ executor .shutdown ();
163+ }
164+
165+ private static void runController (Executor executor , Controller controller ) {
166+ executor .execute (() -> {
167+ try {
168+ controller .start ();
169+ } catch (IOException failure ) {
170+ System .err .println ("encountered error in controller" );
171+ failure .printStackTrace ();
172+ }
173+ });
174+ }
175+
176+ private static void startSomeBoxes (Controller controller , int count ) {
177+ try {
178+ for (int index = 0 ; index < count ; index ++) {
179+ controller .startBox (UUID .randomUUID ());
180+ }
181+ } catch (IOException failure ) {
182+ System .err .println ("failed to start boxes" );
183+ }
184+ }
185+
186+ private static void validateArguments (String [] arguments ) {
187+ if (arguments .length < 1 ) {
188+ System .err .println ("usage: ./run <path-to-box-binary> [box-count]" );
189+ System .exit (-1 );
190+ }
191+ }
192+ }
0 commit comments