-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from Bencodes/worker-support
- Loading branch information
Showing
17 changed files
with
366 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") | ||
|
||
kt_jvm_library( | ||
name = "worker", | ||
srcs = glob(["*.kt"]), | ||
visibility = ["//visibility:public"], | ||
deps = [ | ||
"@rules_android_lint_dependencies//:com_squareup_moshi_moshi", | ||
"@rules_android_lint_dependencies//:com_squareup_moshi_moshi_kotlin", | ||
"@rules_android_lint_dependencies//:com_squareup_okio_okio_jvm", | ||
"@rules_android_lint_dependencies//:io_reactivex_rxjava3_rxjava", | ||
"@rules_android_lint_dependencies//:org_reactivestreams_reactive_streams", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
internal class InvocationWorker( | ||
private val args: Array<String>, | ||
private val workerMessageProcessor: Worker.WorkRequestCallback, | ||
) : Worker { | ||
|
||
override fun processRequests(): Int { | ||
// Handle a single work request | ||
return workerMessageProcessor.processWorkRequest(args.toList(), System.err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import io.reactivex.rxjava3.core.BackpressureStrategy | ||
import io.reactivex.rxjava3.core.Flowable | ||
import io.reactivex.rxjava3.core.Scheduler | ||
import io.reactivex.rxjava3.schedulers.Schedulers | ||
import java.io.BufferedOutputStream | ||
import java.io.ByteArrayOutputStream | ||
import java.io.IOException | ||
import java.io.PrintStream | ||
|
||
internal class PersistentWorker( | ||
/** | ||
* WorkerIO instance wrapping the standard output streams | ||
*/ | ||
private val workerIO: WorkerIO, | ||
|
||
/** | ||
* Rxjava Scheduler to execute work requests on. | ||
*/ | ||
private val scheduler: Scheduler, | ||
|
||
/** | ||
* Instance of CpuTimeBasedGcScheduler that will run periodically | ||
*/ | ||
private val persistentWorkerCpuTimeBasedGcScheduler: PersistentWorkerCpuTimeBasedGcScheduler, | ||
|
||
/** | ||
* Instance of CpuTimeBasedGcScheduler that will run periodically | ||
*/ | ||
private val workRequestProcessor: Worker.WorkerMessageProcessor, | ||
|
||
/** | ||
* Instance of CpuTimeBasedGcScheduler that will run periodically | ||
*/ | ||
private val workerWorkRequestCallback: Worker.WorkRequestCallback, | ||
) : Worker { | ||
|
||
constructor( | ||
workerMessageProcessor: Worker.WorkRequestCallback, | ||
) : this( | ||
workerIO = WorkerIO(), | ||
scheduler = Schedulers.io(), | ||
persistentWorkerCpuTimeBasedGcScheduler = PersistentWorkerCpuTimeBasedGcScheduler(), | ||
workRequestProcessor = WorkerJsonMessageProcessor( | ||
System.`in`, | ||
System.out, | ||
), | ||
workerWorkRequestCallback = workerMessageProcessor, | ||
) | ||
|
||
/** | ||
* Initiate the worker and begin processing work requests | ||
*/ | ||
override fun processRequests(): Int { | ||
return workerIO.use { io -> | ||
// Start by redirecting the system streams so that nothing | ||
// corrupts the streams that the worker uses | ||
io.redirectSystemStreams() | ||
|
||
// Process requests as they come in using RxJava | ||
Flowable.create( | ||
{ emitter -> | ||
while (!emitter.isCancelled) { | ||
try { | ||
val request: WorkRequest = workRequestProcessor.readWorkRequest() | ||
emitter.onNext(request) | ||
} catch (e: IOException) { | ||
emitter.onError(e) | ||
} | ||
} | ||
}, | ||
BackpressureStrategy.BUFFER, | ||
).subscribeOn(scheduler).parallel().runOn(scheduler) | ||
// Execute the work and map the result to a work response | ||
.map { request -> return@map this.respondToRequest(request) } | ||
// Run the garbage collector periodically so that we are a good responsible worker | ||
.doOnNext { persistentWorkerCpuTimeBasedGcScheduler.maybePerformGc() } | ||
.doOnError { it.printStackTrace() }.sequential().observeOn(scheduler) | ||
.blockingSubscribe { response -> | ||
workRequestProcessor.writeWorkResponse(response) | ||
} | ||
return@use 0 | ||
} | ||
} | ||
|
||
private fun respondToRequest(request: WorkRequest): WorkResponse { | ||
ByteArrayOutputStream().use { baos -> | ||
// Create a print stream that the execution can write logs to | ||
val printStream = PrintStream(BufferedOutputStream(ByteArrayOutputStream())) | ||
var exitCode: Int | ||
try { | ||
// Sanity check the work request arguments | ||
val arguments = | ||
requireNotNull(request.arguments) { "Request with id ${request.requestId} does not have arguments!" } | ||
require(arguments.isNotEmpty()) { "Request with id ${request.requestId} does not have arguments!" } | ||
exitCode = workerWorkRequestCallback.processWorkRequest(arguments, printStream) | ||
} catch (e: Exception) { | ||
e.printStackTrace(printStream) | ||
exitCode = 1 | ||
} finally { | ||
printStream.flush() | ||
} | ||
|
||
val output = arrayOf(baos.toString()) | ||
.asSequence() | ||
.map { it.trim() } | ||
.filter { it.isNotEmpty() } | ||
.joinToString("\n") | ||
return WorkResponse( | ||
exitCode = exitCode, | ||
output = output, | ||
requestId = request.requestId, | ||
) | ||
} | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
lint/internal/worker/PersistentWorkerCpuTimeBasedGcScheduler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import com.sun.management.OperatingSystemMXBean | ||
import java.lang.management.ManagementFactory | ||
import java.time.Duration | ||
import java.util.concurrent.atomic.AtomicReference | ||
|
||
internal class PersistentWorkerCpuTimeBasedGcScheduler( | ||
/** | ||
* After this much CPU time has elapsed, we may force a GC run. Set to [Duration.ZERO] to | ||
* disable. | ||
*/ | ||
private val cpuUsageBeforeGc: Duration = Duration.ofSeconds(10), | ||
) { | ||
|
||
private val cpuTime: Duration | ||
get() = if (!cpuUsageBeforeGc.isZero) Duration.ofNanos(bean.processCpuTime) else Duration.ZERO | ||
|
||
/** The total process CPU time at the last GC run (or from the start of the worker). */ | ||
private val cpuTimeAtLastGc: AtomicReference<Duration> = AtomicReference(cpuTime) | ||
|
||
/** Call occasionally to perform a GC if enough CPU time has been used. */ | ||
fun maybePerformGc() { | ||
if (!cpuUsageBeforeGc.isZero) { | ||
val currentCpuTime = cpuTime | ||
val lastCpuTime = cpuTimeAtLastGc.get() | ||
// Do GC when enough CPU time has been used, but only if nobody else beat us to it. | ||
if (currentCpuTime.minus(lastCpuTime) > cpuUsageBeforeGc | ||
&& cpuTimeAtLastGc.compareAndSet(lastCpuTime, currentCpuTime) | ||
) { | ||
System.gc() | ||
// Avoid counting GC CPU time against CPU time before next GC. | ||
cpuTimeAtLastGc.compareAndSet(currentCpuTime, cpuTime) | ||
} | ||
} | ||
} | ||
|
||
companion object { | ||
/** Used to get the CPU time used by this process. */ | ||
private val bean = ManagementFactory.getOperatingSystemMXBean() as OperatingSystemMXBean | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import com.squareup.moshi.Json | ||
|
||
data class WorkRequest( | ||
/** | ||
* Request ID associated with the work request | ||
*/ | ||
@Json(name = "requestId") | ||
val requestId: Int = 0, | ||
|
||
/** | ||
* The work request arguments | ||
*/ | ||
@Json(name = "arguments") | ||
val arguments: List<String>, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import com.squareup.moshi.Json | ||
|
||
data class WorkResponse( | ||
/** | ||
* The request ID for the work request | ||
*/ | ||
@Json(name = "requestId") | ||
val requestId: Int, | ||
|
||
/** | ||
* Exit status for the work request | ||
*/ | ||
@Json(name = "exitCode") | ||
val exitCode: Int, | ||
|
||
/** | ||
* Standard output that was collected during the work request | ||
*/ | ||
@Json(name = "output") | ||
val output: String, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import java.io.IOException | ||
import java.io.PrintStream | ||
|
||
interface Worker { | ||
|
||
fun processRequests(): Int | ||
|
||
interface WorkRequestCallback { | ||
|
||
/** | ||
* Processes an individual work request. | ||
*/ | ||
fun processWorkRequest(args: List<String>, printStream: PrintStream): Int | ||
} | ||
|
||
interface WorkerMessageProcessor { | ||
|
||
@Throws(IOException::class) | ||
fun readWorkRequest(): WorkRequest | ||
|
||
@Throws(IOException::class) | ||
fun writeWorkResponse(workResponse: WorkResponse) | ||
} | ||
|
||
companion object { | ||
|
||
/** | ||
* Creates the appropriate worker instance using the provided worker arguments. | ||
* | ||
* If `--persistent_worker` exists in the arguments, an instance of PersistentWorker will | ||
* be returned. Otherwise an instance of InvocationWorker will be returned. | ||
*/ | ||
fun fromArgs( | ||
args: Array<String>, | ||
workerMessageProcessor: WorkRequestCallback, | ||
): Worker { | ||
return when { | ||
"--persistent_worker" in args -> PersistentWorker(workerMessageProcessor) | ||
else -> InvocationWorker(args, workerMessageProcessor) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import java.io.InputStream | ||
import java.io.PrintStream | ||
|
||
internal class WorkerIO( | ||
val input: InputStream = System.`in`, | ||
val output: PrintStream = System.out, | ||
val err: PrintStream = System.err, | ||
) : AutoCloseable { | ||
|
||
fun redirectSystemStreams(): WorkerIO { | ||
System.setOut(err) | ||
return this | ||
} | ||
|
||
override fun close() { | ||
System.setOut(output) | ||
} | ||
} |
Oops, something went wrong.