24
24
import java .io .StringWriter ;
25
25
import java .lang .management .ManagementFactory ;
26
26
import java .time .Duration ;
27
+ import java .util .ArrayDeque ;
27
28
import java .util .List ;
29
+ import java .util .Map ;
30
+ import java .util .Optional ;
31
+ import java .util .Queue ;
32
+ import java .util .concurrent .ConcurrentHashMap ;
28
33
import java .util .concurrent .atomic .AtomicReference ;
29
34
import java .util .function .BiFunction ;
30
35
34
39
* (https://docs.bazel.build/versions/master/multiplex-worker.html).
35
40
*/
36
41
public class WorkRequestHandler implements AutoCloseable {
37
-
38
42
/** Contains the logic for reading {@link WorkRequest}s and writing {@link WorkResponse}s. */
39
43
public interface WorkerMessageProcessor {
40
44
/** Reads the next incoming request from this worker's stdin. */
41
- public WorkRequest readWorkRequest () throws IOException ;
45
+ WorkRequest readWorkRequest () throws IOException ;
42
46
43
47
/**
44
48
* Writes the provided {@link WorkResponse} to this worker's stdout. This function is also
45
49
* responsible for flushing the stdout.
46
50
*/
47
- public void writeWorkResponse (WorkResponse workResponse ) throws IOException ;
51
+ void writeWorkResponse (WorkResponse workResponse ) throws IOException ;
48
52
49
53
/** Clean up. */
50
- public void close () throws IOException ;
54
+ void close () throws IOException ;
55
+ }
56
+
57
+ /** Holds information necessary to properly handle a request, especially for cancellation. */
58
+ static class RequestInfo {
59
+ /**
60
+ * The builder for the response to this request. Since only one response must be sent per
61
+ * request, this builder must be accessed through takeBuilder(), which zeroes this field and
62
+ * returns the builder.
63
+ */
64
+ private WorkResponse .Builder responseBuilder = WorkResponse .newBuilder ();
65
+
66
+ /**
67
+ * Returns the response builder. If called more than once on the same instance, subsequent calls
68
+ * will return {@code null}.
69
+ */
70
+ synchronized Optional <WorkResponse .Builder > takeBuilder () {
71
+ WorkResponse .Builder b = responseBuilder ;
72
+ responseBuilder = null ;
73
+ return Optional .ofNullable (b );
74
+ }
51
75
}
52
76
77
+ /** Requests that are currently being processed. Visible for testing. */
78
+ final Map <Integer , RequestInfo > activeRequests = new ConcurrentHashMap <>();
79
+
80
+ /** WorkRequests that have been received but could not be processed yet. */
81
+ private final Queue <WorkRequest > availableRequests = new ArrayDeque <>();
82
+
53
83
/** The function to be called after each {@link WorkRequest} is read. */
54
84
private final BiFunction <List <String >, PrintWriter , Integer > callback ;
55
85
@@ -58,6 +88,7 @@ public interface WorkerMessageProcessor {
58
88
59
89
final WorkerMessageProcessor messageProcessor ;
60
90
91
+
61
92
private final CpuTimeBasedGcScheduler gcScheduler ;
62
93
63
94
/**
@@ -160,34 +191,61 @@ public void processRequests() throws IOException {
160
191
if (request == null ) {
161
192
break ;
162
193
}
163
- if (request .getRequestId () != 0 ) {
164
- Thread t = createResponseThread (request );
165
- t .start ();
166
- } else {
167
- respondToRequest (request );
194
+ availableRequests .add (request );
195
+ startRequestThreads ();
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Starts threads for as many outstanding requests as possible. This is the only method that adds
201
+ * to {@code activeRequests}.
202
+ */
203
+ private synchronized void startRequestThreads () {
204
+ while (!availableRequests .isEmpty ()) {
205
+ // If there's a singleplex request in process, don't start more processes.
206
+ if (activeRequests .containsKey (0 )) {
207
+ return ;
168
208
}
209
+ WorkRequest request = availableRequests .peek ();
210
+ // Don't start new singleplex requests if there are other requests running.
211
+ if (request .getRequestId () == 0 && !activeRequests .isEmpty ()) {
212
+ return ;
213
+ }
214
+ availableRequests .remove ();
215
+ Thread t = createResponseThread (request );
216
+ activeRequests .put (request .getRequestId (), new RequestInfo ());
217
+ t .start ();
169
218
}
170
219
}
171
220
172
221
/** Creates a new {@link Thread} to process a multiplex request. */
173
- public Thread createResponseThread (WorkRequest request ) {
222
+ Thread createResponseThread (WorkRequest request ) {
174
223
Thread currentThread = Thread .currentThread ();
224
+ String threadName =
225
+ request .getRequestId () > 0
226
+ ? "multiplex-request-" + request .getRequestId ()
227
+ : "singleplex-request" ;
175
228
return new Thread (
176
229
() -> {
230
+ RequestInfo requestInfo = activeRequests .get (request .getRequestId ());
177
231
try {
178
- respondToRequest (request );
232
+ respondToRequest (request , requestInfo );
179
233
} catch (IOException e ) {
180
234
e .printStackTrace (stderr );
181
235
// In case of error, shut down the entire worker.
182
236
currentThread .interrupt ();
237
+ } finally {
238
+ activeRequests .remove (request .getRequestId ());
239
+ // A good time to start more requests, especially if we finished a singleplex request
240
+ startRequestThreads ();
183
241
}
184
242
},
185
- "multiplex-request-" + request . getRequestId () );
243
+ threadName );
186
244
}
187
245
188
246
/** Handles and responds to the given {@link WorkRequest}. */
189
247
@ VisibleForTesting
190
- void respondToRequest (WorkRequest request ) throws IOException {
248
+ void respondToRequest (WorkRequest request , RequestInfo requestInfo ) throws IOException {
191
249
try (StringWriter sw = new StringWriter ();
192
250
PrintWriter pw = new PrintWriter (sw )) {
193
251
int exitCode ;
@@ -198,14 +256,15 @@ void respondToRequest(WorkRequest request) throws IOException {
198
256
exitCode = 1 ;
199
257
}
200
258
pw .flush ();
201
- WorkResponse workResponse =
202
- WorkResponse .newBuilder ()
203
- .setOutput (sw .toString ())
204
- .setExitCode (exitCode )
205
- .setRequestId (request .getRequestId ())
206
- .build ();
207
- synchronized (this ) {
208
- messageProcessor .writeWorkResponse (workResponse );
259
+ Optional <WorkResponse .Builder > optBuilder = requestInfo .takeBuilder ();
260
+ if (optBuilder .isPresent ()) {
261
+ WorkResponse .Builder builder = optBuilder .get ();
262
+ builder .setRequestId (request .getRequestId ());
263
+ builder .setOutput (builder .getOutput () + sw .toString ()).setExitCode (exitCode );
264
+ WorkResponse response = builder .build ();
265
+ synchronized (this ) {
266
+ messageProcessor .writeWorkResponse (response );
267
+ }
209
268
}
210
269
gcScheduler .maybePerformGc ();
211
270
}
0 commit comments