Skip to content

Commit ac07080

Browse files
committed
Fix race condition. Also make sure to flush output stream before writing the worker response
There was a race condition where a task could be cancelled with the worker output streams still set to null, which would cause a null pointer exception in the worker. We also weren't always flushing the print stream before responding to Bazel about the work request. This fixes that.
1 parent 3704408 commit ac07080

File tree

1 file changed

+22
-14
lines changed

1 file changed

+22
-14
lines changed

src/main/scala/higherkindness/rules_scala/common/worker/WorkerMain.scala

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,18 @@ abstract class WorkerMain[S](stdin: InputStream = System.in, stdout: PrintStream
117117
// close them after the async work in the Future is all done.
118118
// If we do something synchronous with Using, then there's a race condition where the
119119
// streams can get closed before the Future is completed.
120-
var outStream: ByteArrayOutputStream = null
121-
var out: PrintStream = null
120+
var maybeOutStream: Option[ByteArrayOutputStream] = None
121+
var maybeOut: Option[PrintStream] = None
122+
123+
def flushOut(): Unit = {
124+
maybeOut.map(_.flush())
125+
}
122126

123127
val workTask = CancellableTask {
124-
outStream = new ByteArrayOutputStream
125-
out = new PrintStream(outStream)
128+
val outStream = new ByteArrayOutputStream()
129+
val out = new PrintStream(outStream)
130+
maybeOutStream = Some(outStream)
131+
maybeOut = Some(out)
126132
try {
127133
work(ctx, args, out, sandboxDir, verbosity)
128134
0
@@ -137,24 +143,25 @@ abstract class WorkerMain[S](stdin: InputStream = System.in, stdout: PrintStream
137143
.andThen {
138144
// Work task succeeded or failed in an expected way
139145
case Success(code) =>
140-
out.flush()
141-
writeResponse(requestId, Some(outStream), Some(code))
146+
flushOut()
147+
writeResponse(requestId, maybeOutStream, Some(code))
142148
logVerbose(s"WorkResponse $requestId sent with code $code")
143149

144150
case Failure(e: ExecutionException) =>
145151
e.getCause() match {
146152
// Task successfully cancelled
147153
case cancelError: InterruptedException =>
154+
flushOut()
148155
writeResponse(requestId, None, None, wasCancelled = true)
149156
logVerbose(
150157
s"Cancellation WorkResponse sent for request id: $requestId in response to an" +
151158
" InterruptedException",
152159
)
153160
// Work task threw a non-fatal error
154161
case e =>
155-
e.printStackTrace(out)
156-
out.flush()
157-
writeResponse(requestId, Some(outStream), Some(-1))
162+
maybeOut.map(e.printStackTrace(_))
163+
flushOut()
164+
writeResponse(requestId, maybeOutStream, Some(-1))
158165
logVerbose(
159166
"Encountered an uncaught exception that was wrapped in an ExecutionException while" +
160167
s" proccessing the Future for WorkRequest $requestId. This usually means a non-fatal" +
@@ -165,6 +172,7 @@ abstract class WorkerMain[S](stdin: InputStream = System.in, stdout: PrintStream
165172

166173
// Task successfully cancelled
167174
case Failure(e: CancellationException) =>
175+
flushOut()
168176
writeResponse(requestId, None, None, wasCancelled = true)
169177
logVerbose(
170178
s"Cancellation WorkResponse sent for request id: $requestId in response to a" +
@@ -173,15 +181,15 @@ abstract class WorkerMain[S](stdin: InputStream = System.in, stdout: PrintStream
173181

174182
// Work task threw an uncaught exception
175183
case Failure(e) =>
176-
e.printStackTrace(out)
177-
out.flush()
178-
writeResponse(requestId, Some(outStream), Some(-1))
184+
maybeOut.map(e.printStackTrace(_))
185+
flushOut()
186+
writeResponse(requestId, maybeOutStream, Some(-1))
179187
logVerbose(s"Uncaught exception in Future while proccessing WorkRequest $requestId:")
180188
e.printStackTrace(System.err)
181189
}(scala.concurrent.ExecutionContext.global)
182190
.andThen { case _ =>
183-
out.close()
184-
outStream.close()
191+
maybeOut.map(_.close())
192+
maybeOutStream.map(_.close())
185193
}(scala.concurrent.ExecutionContext.global)
186194

187195
// putIfAbsent will return a non-null value if there was already a value in the map

0 commit comments

Comments
 (0)