Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure that KotlionBuilder workers use a clean directory for each compilation. #298

Merged
merged 5 commits into from
Apr 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.bazel.kotlin.builder;

Expand Down Expand Up @@ -40,15 +41,15 @@ public interface KotlinBuilderComponent {
BazelWorker worker();

@Component.Builder
interface Builder {
public interface Builder {
@BindsInstance
KotlinBuilderComponent.Builder toolchain(KotlinToolchain toolchain);

KotlinBuilderComponent build();
}

@dagger.Module
class Module {
public class Module {
@Provides
public KotlinCompilerPluginArgsEncoder providePluginArgEncoder(KotlinToolchain toolchain) {
return new KotlinCompilerPluginArgsEncoder(
Expand Down
130 changes: 61 additions & 69 deletions src/main/kotlin/io/bazel/kotlin/builder/tasks/BazelWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,27 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.bazel.kotlin.builder.tasks

import com.google.devtools.build.lib.worker.WorkerProtocol
import io.bazel.kotlin.builder.utils.rootCause
import io.bazel.kotlin.builder.utils.WorkingDirectoryContext
import io.bazel.kotlin.builder.utils.wasInterrupted
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InterruptedIOException
import java.io.PrintStream
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

/**
Expand All @@ -41,7 +43,7 @@ interface CommandLineProgram {
* @param args command line arguments
* @return program exit code, i.e. 0 for success, non-zero for failure
*/
fun apply(args: List<String>): Int
fun apply(workingDir: Path, args: List<String>): Int
}

/**
Expand All @@ -52,25 +54,27 @@ interface CommandLineProgram {
* compatible with being run as a normal single-invocation command.
*
* @param <T> delegate program type
</T> */
*/
class BazelWorker(
private val delegate: CommandLineProgram,
private val output: PrintStream,
private val mnemonic: String
) : CommandLineProgram {
) {
companion object {
private const val INTERUPTED_STATUS = 0
private const val ERROR_STATUS = 1
const val OK = 0
const val INTERRUPTED_STATUS = 1
const val ERROR_STATUS = 2
}

override fun apply(args: List<String>): Int {
fun apply(args: List<String>): Int {
return if (args.contains("--persistent_worker"))
runAsPersistentWorker(args)
else delegate.apply(loadArguments(args, false))
runAsPersistentWorker()
else WorkingDirectoryContext.newContext().use { ctx ->
delegate.apply(ctx.dir, loadArguments(args, false))
}
}

@Suppress("UNUSED_PARAMETER")
private fun runAsPersistentWorker(ignored: List<String>): Int {
private fun runAsPersistentWorker(): Int {
val realStdIn = System.`in`
val realStdOut = System.out
val realStdErr = System.err
Expand All @@ -81,57 +85,33 @@ class BazelWorker(
System.setIn(emptyIn)
System.setOut(ps)
System.setErr(ps)
val invocationWorker = InvocationWorker(delegate, buffer)
while (true) {
val request = WorkerProtocol.WorkRequest.parseDelimitedFrom(realStdIn) ?: return 0
val exitCode = try {
delegate.apply(loadArguments(request.argumentsList, true))
} catch (e: RuntimeException) {
val innerExitCode = if (wasInterrupted(e)) INTERUPTED_STATUS
else ERROR_STATUS.also {
System.err.println(
"ERROR: Worker threw uncaught exception with args: ${request.argumentsList.joinToString(
" "
)}"
)
e.printStackTrace(System.err)
}
WorkerProtocol.WorkResponse.newBuilder()
.setOutput(buffer.toString())
.setExitCode(innerExitCode)
.build()
.writeDelimitedTo(realStdOut)
realStdOut.flush()
return innerExitCode
}
val status =
WorkerProtocol.WorkRequest.parseDelimitedFrom(realStdIn)?.let { request ->
invocationWorker.invoke(loadArguments(request.argumentsList, true))
}?.also { (status, log) ->
with(WorkerProtocol.WorkResponse.newBuilder()) {
exitCode = status
output = log
build().writeDelimitedTo(realStdOut)
restingbull marked this conversation as resolved.
Show resolved Hide resolved
}
}?.let { (status, _) -> status } ?: OK

WorkerProtocol.WorkResponse.newBuilder()
.setOutput(buffer.toString())
.setExitCode(exitCode)
.build()
.writeDelimitedTo(realStdOut)
realStdOut.flush()
buffer.reset()
System.gc() // be a good little worker process and consume less memory when idle
if (status != OK) {
return status
}
System.gc()
}
}
}
}
} catch (e: IOException) {
if (wasInterrupted(e)) {
return INTERUPTED_STATUS
}
throw e
} catch (e: RuntimeException) {
if (wasInterrupted(e)) {
return INTERUPTED_STATUS
}
throw e
} finally {
System.setIn(realStdIn)
System.setOut(realStdOut)
System.setErr(realStdErr)
}
throw RuntimeException("drop through")
return OK
}

private fun loadArguments(args: List<String>, isWorker: Boolean): List<String> {
Expand All @@ -142,10 +122,10 @@ class BazelWorker(
val pathElement = lastArg.substring(1)
val flagFile = Paths.get(pathElement)
if (isWorker && lastArg.startsWith("@@") || Files.exists(flagFile)) {
if (!isWorker && !mnemonic.isEmpty()) {
if (!isWorker && mnemonic.isNotEmpty()) {
output.printf(
"HINT: %s will compile faster if you run: " + "echo \"build --strategy=%s=worker\" >>~/.bazelrc\n",
mnemonic, mnemonic
"HINT: %s will compile faster if you run: " + "echo \"build --strategy=%s=worker\" >>~/.bazelrc\n",
mnemonic, mnemonic
)
}
try {
Expand All @@ -158,13 +138,25 @@ class BazelWorker(
}
return args
}
}

private fun wasInterrupted(e: Throwable): Boolean {
val cause = e.rootCause
if (cause is InterruptedException || cause is InterruptedIOException) {
output.println("Terminating worker due to interrupt signal")
return true
}
return false
}
class InvocationWorker(
private val delegate: CommandLineProgram,
private val buffer: ByteArrayOutputStream
) {

fun invoke(args: List<String>): Pair<Int, String> = WorkingDirectoryContext.newContext()
.use { wdCtx ->
return try {
delegate.apply(wdCtx.dir, args)
} catch (e: RuntimeException) {
if (e.wasInterrupted()) BazelWorker.INTERRUPTED_STATUS
else BazelWorker.ERROR_STATUS.also {
System.err.println(
"ERROR: Worker threw uncaught exception with args: ${args}"
)
e.printStackTrace(System.err)
}
} to buffer.toString()
}
}