|
17 | 17 |
|
18 | 18 | package io.bazel.worker
|
19 | 19 |
|
| 20 | +import com.google.devtools.build.lib.worker.ProtoWorkerMessageProcessor |
| 21 | +import com.google.devtools.build.lib.worker.WorkRequestHandler.WorkRequestCallback |
| 22 | +import com.google.devtools.build.lib.worker.WorkRequestHandler.WorkRequestHandlerBuilder |
20 | 23 | import com.google.devtools.build.lib.worker.WorkerProtocol.WorkRequest
|
21 |
| -import com.google.devtools.build.lib.worker.WorkerProtocol.WorkResponse |
22 |
| -import src.main.kotlin.io.bazel.worker.GcScheduler |
23 |
| -import java.io.InputStream |
| 24 | +import java.io.IOException |
| 25 | +import java.io.PrintWriter |
24 | 26 | import java.time.Duration
|
25 |
| -import java.util.concurrent.ExecutorCompletionService |
26 |
| -import java.util.concurrent.ExecutorService |
27 |
| -import java.util.concurrent.Executors |
28 |
| -import java.util.concurrent.TimeUnit |
29 |
| -import java.util.concurrent.atomic.AtomicLong |
30 | 27 |
|
31 | 28 | /**
|
32 | 29 | * PersistentWorker satisfies Bazel persistent worker protocol for executing work.
|
33 | 30 | *
|
34 | 31 | * Supports multiplex (https://docs.bazel.build/versions/master/multiplex-worker.html) provided
|
35 | 32 | * the work is thread/coroutine safe.
|
36 |
| - * |
37 |
| - * @param executor thread pool for executing tasks. |
38 |
| - * @param captureIO to avoid writing stdout and stderr while executing. |
39 |
| - * @param cpuTimeBasedGcScheduler to trigger gc cleanup. |
40 | 33 | */
|
41 |
| -class PersistentWorker( |
42 |
| - private val captureIO: () -> IO, |
43 |
| - private val executor: ExecutorService, |
44 |
| - private val cpuTimeBasedGcScheduler: GcScheduler, |
45 |
| -) : Worker { |
46 |
| - constructor( |
47 |
| - executor: ExecutorService, |
48 |
| - captureIO: () -> IO, |
49 |
| - ) : this( |
50 |
| - captureIO, |
51 |
| - executor, |
52 |
| - GcScheduler {}, |
53 |
| - ) |
54 |
| - |
55 |
| - constructor() : this( |
56 |
| - IO.Companion::capture, |
57 |
| - Executors.newCachedThreadPool(), |
58 |
| - CpuTimeBasedGcScheduler(Duration.ofSeconds(10)), |
59 |
| - ) |
60 |
| - |
61 |
| - override fun start(execute: Work) = |
62 |
| - WorkerContext.run { |
63 |
| - captureIO().use { io -> |
64 |
| - val running = AtomicLong(0) |
65 |
| - val completion = ExecutorCompletionService<WorkResponse>(executor) |
66 |
| - val producer = |
67 |
| - executor.submit { |
68 |
| - io.input.readRequestAnd { request -> |
69 |
| - running.incrementAndGet() |
70 |
| - completion.submit { |
71 |
| - doTask( |
72 |
| - name = "request ${request.requestId}", |
73 |
| - task = request.workTo(execute), |
74 |
| - ).asResponseTo(request.requestId, io) |
75 |
| - } |
76 |
| - } |
77 |
| - } |
78 |
| - val consumer = |
79 |
| - executor.submit { |
80 |
| - while (!producer.isDone || running.get() > 0) { |
81 |
| - // poll time is how long before checking producer liveliness. Too long, worker hangs |
82 |
| - // when being shutdown -- too short, and it starves the process. |
83 |
| - completion.poll(1, TimeUnit.SECONDS)?.run { |
84 |
| - running.decrementAndGet() |
85 |
| - get().writeDelimitedTo(io.output) |
86 |
| - io.output.flush() |
87 |
| - } |
88 |
| - cpuTimeBasedGcScheduler.maybePerformGc() |
89 |
| - } |
90 |
| - } |
91 |
| - producer.get() |
92 |
| - consumer.get() |
93 |
| - io.output.close() |
| 34 | +class PersistentWorker : Worker { |
| 35 | + override fun start(execute: Work): Int { |
| 36 | + return WorkerContext.run { |
| 37 | + val realStdErr = System.err |
| 38 | + try { |
| 39 | + val workerHandler = |
| 40 | + WorkRequestHandlerBuilder( |
| 41 | + WorkRequestCallback { request: WorkRequest, pw: PrintWriter -> |
| 42 | + return@WorkRequestCallback doTask( |
| 43 | + name = "request ${request.requestId}", |
| 44 | + task = request.workTo(execute), |
| 45 | + ).asResponse(pw) |
| 46 | + }, |
| 47 | + realStdErr, |
| 48 | + ProtoWorkerMessageProcessor(System.`in`, System.out), |
| 49 | + ).setCpuUsageBeforeGc(Duration.ofSeconds(10)).build() |
| 50 | + workerHandler.processRequests() |
| 51 | + } catch (e: IOException) { |
| 52 | + this.error(e, { "Unknown IO exception" }) |
| 53 | + e.printStackTrace(realStdErr) |
| 54 | + return@run 1 |
94 | 55 | }
|
95 | 56 | return@run 0
|
96 | 57 | }
|
| 58 | + } |
97 | 59 |
|
98 | 60 | private fun WorkRequest.workTo(execute: Work): (sub: WorkerContext.TaskContext) -> Status =
|
99 | 61 | { ctx -> execute(ctx, argumentsList.toList()) }
|
100 | 62 |
|
101 |
| - private fun InputStream.readRequestAnd(action: (WorkRequest) -> Unit) { |
102 |
| - while (true) { |
103 |
| - WorkRequest |
104 |
| - .parseDelimitedFrom(this) |
105 |
| - ?.run(action) |
106 |
| - ?: return |
107 |
| - } |
| 63 | + private fun TaskResult.asResponse(pw: PrintWriter): Int { |
| 64 | + pw.print(log.out.toString()) |
| 65 | + return status.exit |
108 | 66 | }
|
109 |
| - |
110 |
| - private fun TaskResult.asResponseTo( |
111 |
| - id: Int, |
112 |
| - io: IO, |
113 |
| - ): WorkResponse = |
114 |
| - WorkResponse |
115 |
| - .newBuilder() |
116 |
| - .apply { |
117 |
| - val cap = io.readCapturedAsUtf8String() |
118 |
| - // append whatever falls through standard out. |
119 |
| - output = |
120 |
| - listOf( |
121 |
| - log.out.toString(), |
122 |
| - cap, |
123 |
| - ).joinToString("\n").trim() |
124 |
| - exitCode = status.exit |
125 |
| - requestId = id |
126 |
| - }.build() |
127 | 67 | }
|
0 commit comments