Skip to content

Commit 44b9062

Browse files
bell-dbthesamet
authored andcommitted
Handle all failures in Future
1 parent a30e7e1 commit 44b9062

File tree

5 files changed

+63
-26
lines changed

5 files changed

+63
-26
lines changed

bridge/src/main/scala/protocbridge/frontend/PluginFrontend.scala

+10-10
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import java.nio.file.{Files, Path}
55

66
import protocbridge.{ProtocCodeGenerator, ExtraEnv}
77

8-
import scala.util.Try
9-
108
/** A PluginFrontend instance provides a platform-dependent way for protoc to
119
* communicate with a JVM based ProtocCodeGenerator.
1210
*
@@ -47,13 +45,7 @@ object PluginFrontend {
4745
gen: ProtocCodeGenerator,
4846
request: Array[Byte]
4947
): Array[Byte] = {
50-
Try {
51-
gen.run(request)
52-
}.recover { case throwable =>
53-
createCodeGeneratorResponseWithError(
54-
throwable.toString + "\n" + getStackTrace(throwable)
55-
)
56-
}.get
48+
gen.run(request)
5749
}
5850

5951
def createCodeGeneratorResponseWithError(error: String): Array[Byte] = {
@@ -116,9 +108,17 @@ object PluginFrontend {
116108
gen: ProtocCodeGenerator,
117109
fsin: InputStream,
118110
env: ExtraEnv
119-
): Array[Byte] = {
111+
): Array[Byte] = try {
120112
val bytes = readInputStreamToByteArrayWithEnv(fsin, env)
121113
runWithBytes(gen, bytes)
114+
} catch {
115+
// This covers all Throwable including OutOfMemoryError, StackOverflowError, etc.
116+
// We need to make a best effort to return a response to protoc,
117+
// otherwise protoc can hang indefinitely.
118+
case throwable: Throwable =>
119+
createCodeGeneratorResponseWithError(
120+
throwable.toString + "\n" + getStackTrace(throwable)
121+
)
122122
}
123123

124124
def createTempFile(extension: String, content: String): Path = {

bridge/src/main/scala/protocbridge/frontend/PosixPluginFrontend.scala

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ object PosixPluginFrontend extends PluginFrontend {
4040
val response = PluginFrontend.runWithInputStream(plugin, fsin, env)
4141
fsin.close()
4242

43+
// Note that the output pipe must be opened after the input pipe is consumed.
44+
// Otherwise, there might be a deadlock that
45+
// - The shell script is stuck writing to the input pipe (which has a full buffer),
46+
// and doesn't open the write end of the output pipe.
47+
// - This thread is stuck waiting for the write end of the output pipe to be opened.
4348
val fsout = Files.newOutputStream(outputPipe)
4449
fsout.write(response)
4550
fsout.close()

bridge/src/test/scala/protocbridge/frontend/OsSpecificFrontendSpec.scala

+38-14
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,14 @@ import scala.util.Random
1010

1111
class OsSpecificFrontendSpec extends AnyFlatSpec with Matchers {
1212

13-
protected def testPluginFrontend(frontend: PluginFrontend): Array[Byte] = {
14-
val random = new Random()
15-
val toSend = Array.fill(123)(random.nextInt(256).toByte)
16-
val toReceive = Array.fill(456)(random.nextInt(256).toByte)
17-
val env = new ExtraEnv(secondaryOutputDir = "tmp")
18-
19-
val fakeGenerator = new ProtocCodeGenerator {
20-
override def run(request: Array[Byte]): Array[Byte] = {
21-
request mustBe (toSend ++ env.toByteArrayAsField)
22-
toReceive
23-
}
24-
}
13+
protected def testPluginFrontend(
14+
frontend: PluginFrontend,
15+
generator: ProtocCodeGenerator,
16+
env: ExtraEnv,
17+
request: Array[Byte]
18+
): Array[Byte] = {
2519
val (path, state) = frontend.prepare(
26-
fakeGenerator,
20+
generator,
2721
env
2822
)
2923
val actualOutput = new ByteArrayOutputStream()
@@ -32,7 +26,7 @@ class OsSpecificFrontendSpec extends AnyFlatSpec with Matchers {
3226
.run(
3327
new ProcessIO(
3428
writeInput => {
35-
writeInput.write(toSend)
29+
writeInput.write(request)
3630
writeInput.close()
3731
},
3832
processOutput => {
@@ -53,4 +47,34 @@ class OsSpecificFrontendSpec extends AnyFlatSpec with Matchers {
5347
frontend.cleanup(state)
5448
actualOutput.toByteArray
5549
}
50+
51+
protected def testSuccess(frontend: PluginFrontend): Unit = {
52+
val random = new Random()
53+
val toSend = Array.fill(123)(random.nextInt(256).toByte)
54+
val toReceive = Array.fill(456)(random.nextInt(256).toByte)
55+
val env = new ExtraEnv(secondaryOutputDir = "tmp")
56+
57+
val fakeGenerator = new ProtocCodeGenerator {
58+
override def run(request: Array[Byte]): Array[Byte] = {
59+
request mustBe (toSend ++ env.toByteArrayAsField)
60+
toReceive
61+
}
62+
}
63+
val response = testPluginFrontend(frontend, fakeGenerator, env, toSend)
64+
response mustBe toReceive
65+
}
66+
67+
protected def testFailure(frontend: PluginFrontend): Unit = {
68+
val random = new Random()
69+
val toSend = Array.fill(123)(random.nextInt(256).toByte)
70+
val env = new ExtraEnv(secondaryOutputDir = "tmp")
71+
72+
val fakeGenerator = new ProtocCodeGenerator {
73+
override def run(request: Array[Byte]): Array[Byte] = {
74+
throw new OutOfMemoryError("test error")
75+
}
76+
}
77+
val response = testPluginFrontend(frontend, fakeGenerator, env, toSend)
78+
response.length must be > 0
79+
}
5680
}

bridge/src/test/scala/protocbridge/frontend/PosixPluginFrontendSpec.scala

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ package protocbridge.frontend
33
class PosixPluginFrontendSpec extends OsSpecificFrontendSpec {
44
if (!PluginFrontend.isWindows) {
55
it must "execute a program that forwards input and output to given stream" in {
6-
testPluginFrontend(PosixPluginFrontend)
6+
testSuccess(PosixPluginFrontend)
7+
}
8+
9+
it must "not hang if there is an OOM in generator" in {
10+
testFailure(PosixPluginFrontend)
711
}
812
}
913
}

bridge/src/test/scala/protocbridge/frontend/WindowsPluginFrontendSpec.scala

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ package protocbridge.frontend
33
class WindowsPluginFrontendSpec extends OsSpecificFrontendSpec {
44
if (PluginFrontend.isWindows) {
55
it must "execute a program that forwards input and output to given stream" in {
6-
testPluginFrontend(WindowsPluginFrontend)
6+
testSuccess(WindowsPluginFrontend)
7+
}
8+
9+
it must "not hang if there is an OOM in generator" in {
10+
testFailure(WindowsPluginFrontend)
711
}
812
}
913
}

0 commit comments

Comments
 (0)