Skip to content

Commit d2a4c51

Browse files
committed
Add proof of concept
0 parents  commit d2a4c51

File tree

5 files changed

+311
-0
lines changed

5 files changed

+311
-0
lines changed

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
build
2+
out
3+
*.class
4+
*.jar
5+
.idea
6+
*.iml
7+
.DS_Store
8+
.AppleDouble
9+
.LSOverride

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Java Fork Demo
2+
3+
Proof of concept application that dynamically creates virtual machines to
4+
execute tasks.
5+
6+
`./run.sh` *Requires Java 16+*

run.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
javac ./src/Box.java ./src/Controller.java
2+
jar --create --file ./src/box.jar --main-class Box -C src Box.class
3+
java -cp src Controller "$@"

src/Box.java

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import java.io.IOException;
2+
import java.net.InetSocketAddress;
3+
import java.net.Socket;
4+
import java.nio.ByteBuffer;
5+
import java.nio.file.Files;
6+
import java.nio.file.Path;
7+
import java.time.Duration;
8+
import java.util.UUID;
9+
10+
final class Box {
11+
private final UUID id;
12+
private final InetSocketAddress controllerAddress;
13+
private final Duration connectTimeout;
14+
private Socket connection;
15+
16+
private Box(
17+
UUID id,
18+
InetSocketAddress controllerAddress,
19+
Duration connectTimeout
20+
) {
21+
this.id = id;
22+
this.connectTimeout = connectTimeout;
23+
this.controllerAddress = controllerAddress;
24+
}
25+
26+
public void start() throws IOException {
27+
connect();
28+
try {
29+
advertise();
30+
accept();
31+
} finally {
32+
connection.close();
33+
}
34+
}
35+
36+
private void connect() throws IOException {
37+
connection = new Socket();
38+
connection.connect(controllerAddress, (int) connectTimeout.toMillis());
39+
}
40+
41+
public void advertise() throws IOException {
42+
var bytes = ByteBuffer.allocate(Long.SIZE * 2);
43+
bytes.putLong(id.getMostSignificantBits());
44+
bytes.putLong(id.getLeastSignificantBits());
45+
connection.getOutputStream().write(bytes.flip().array());
46+
}
47+
48+
public void accept() throws IOException {
49+
var input = connection.getInputStream();
50+
byte[] buffer = new byte[4096];
51+
while (!Thread.currentThread().isInterrupted() && connection.isConnected()) {
52+
int read = input.read(buffer);
53+
if (read == -1) {
54+
return;
55+
}
56+
if (read != 0) {
57+
System.out.write(buffer, 0, read);
58+
}
59+
}
60+
}
61+
62+
public static void main(String[] arguments) throws IOException {
63+
validateArguments(arguments);
64+
var box = createBoxFromArguments(arguments);
65+
Files.writeString(Path.of(box.id.toString() + ".box.txt"), box.id.toString());
66+
try {
67+
box.start();
68+
} catch (IOException failure) {
69+
System.err.println("error occurred while running box");
70+
failure.printStackTrace();
71+
}
72+
}
73+
74+
private static void validateArguments(String[] arguments) {
75+
if (arguments.length != addressIndex + 1) {
76+
System.out.println("usage: box <id> <controllerAddress>");
77+
System.exit(-1);
78+
}
79+
}
80+
81+
private static final int idIndex = 0;
82+
private static final int addressIndex = 1;
83+
84+
private static Box createBoxFromArguments(String[] arguments) {
85+
var id = UUID.fromString(arguments[idIndex]);
86+
var address = parseAddress(arguments[addressIndex]);
87+
return new Box(id, address, Duration.ofMillis(5000));
88+
}
89+
90+
private static InetSocketAddress parseAddress(String input) {
91+
int portSeparator = input.indexOf(':');
92+
if (portSeparator == -1) {
93+
throw new IllegalArgumentException("port missing: " + input);
94+
}
95+
int port = Integer.parseInt(input.substring(portSeparator + 1));
96+
var host = input.substring(0, portSeparator);
97+
return host.isEmpty()
98+
? new InetSocketAddress(port)
99+
: new InetSocketAddress(host, port);
100+
}
101+
}

src/Controller.java

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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

Comments
 (0)