|
9 | 9 |
|
10 | 10 | package org.scalajs.jsenv.nodejs |
11 | 11 |
|
| 12 | +import scala.annotation.tailrec |
| 13 | + |
12 | 14 | import java.io.{Console => _, _} |
13 | 15 | import java.net._ |
14 | 16 |
|
@@ -153,8 +155,17 @@ abstract class AbstractNodeJSEnv( |
153 | 155 |
|
154 | 156 | protected trait NodeComJSRunner extends ComJSRunner with JSInitFiles { |
155 | 157 |
|
| 158 | + /* Manipulation of the socket must be protected by synchronized, except |
| 159 | + * calls to `close()`. |
| 160 | + */ |
156 | 161 | private[this] val serverSocket = |
157 | 162 | new ServerSocket(0, 0, InetAddress.getByName(null)) // Loopback address |
| 163 | + |
| 164 | + /* Those 3 fields are assigned *once* under synchronization in |
| 165 | + * `awaitConnection()`. |
| 166 | + * Read access must be protected by synchronized, or be done after a |
| 167 | + * successful call to `awaitConnection()`. |
| 168 | + */ |
158 | 169 | private var comSocket: Socket = _ |
159 | 170 | private var jvm2js: DataOutputStream = _ |
160 | 171 | private var js2jvm: DataInputStream = _ |
@@ -280,34 +291,67 @@ abstract class AbstractNodeJSEnv( |
280 | 291 | } |
281 | 292 |
|
282 | 293 | def close(): Unit = { |
| 294 | + /* Close the socket first. This will cause any existing and upcoming |
| 295 | + * calls to `awaitConnection()` to be canceled and throw a |
| 296 | + * `SocketException` (unless it has already successfully completed the |
| 297 | + * `accept()` call). |
| 298 | + */ |
283 | 299 | serverSocket.close() |
284 | | - if (jvm2js != null) |
285 | | - jvm2js.close() |
286 | | - if (js2jvm != null) |
287 | | - js2jvm.close() |
288 | | - if (comSocket != null) |
289 | | - comSocket.close() |
| 300 | + |
| 301 | + /* Now wait for a possibly still-successful `awaitConnection()` to |
| 302 | + * complete before closing the sockets. |
| 303 | + */ |
| 304 | + synchronized { |
| 305 | + if (comSocket != null) { |
| 306 | + jvm2js.close() |
| 307 | + js2jvm.close() |
| 308 | + comSocket.close() |
| 309 | + } |
| 310 | + } |
290 | 311 | } |
291 | 312 |
|
292 | 313 | /** Waits until the JS VM has established a connection or terminates |
293 | 314 | * |
294 | 315 | * @return true if the connection was established |
295 | 316 | */ |
296 | | - private def awaitConnection(): Boolean = { |
297 | | - serverSocket.setSoTimeout(acceptTimeout) |
298 | | - while (comSocket == null && isRunning) { |
299 | | - try { |
300 | | - comSocket = serverSocket.accept() |
301 | | - jvm2js = new DataOutputStream( |
302 | | - new BufferedOutputStream(comSocket.getOutputStream())) |
303 | | - js2jvm = new DataInputStream( |
304 | | - new BufferedInputStream(comSocket.getInputStream())) |
305 | | - } catch { |
306 | | - case to: SocketTimeoutException => |
| 317 | + private def awaitConnection(): Boolean = synchronized { |
| 318 | + if (comSocket != null) { |
| 319 | + true |
| 320 | + } else { |
| 321 | + @tailrec |
| 322 | + def acceptLoop(): Option[Socket] = { |
| 323 | + if (!isRunning) { |
| 324 | + None |
| 325 | + } else { |
| 326 | + try { |
| 327 | + Some(serverSocket.accept()) |
| 328 | + } catch { |
| 329 | + case to: SocketTimeoutException => acceptLoop() |
| 330 | + } |
| 331 | + } |
307 | 332 | } |
308 | | - } |
309 | 333 |
|
310 | | - comSocket != null |
| 334 | + serverSocket.setSoTimeout(acceptTimeout) |
| 335 | + val optComSocket = acceptLoop() |
| 336 | + |
| 337 | + optComSocket.fold { |
| 338 | + false |
| 339 | + } { comSocket0 => |
| 340 | + val jvm2js0 = new DataOutputStream( |
| 341 | + new BufferedOutputStream(comSocket0.getOutputStream())) |
| 342 | + val js2jvm0 = new DataInputStream( |
| 343 | + new BufferedInputStream(comSocket0.getInputStream())) |
| 344 | + |
| 345 | + /* Assign those three fields together, without the possibility of |
| 346 | + * an exception happening in the middle (see #3408). |
| 347 | + */ |
| 348 | + comSocket = comSocket0 |
| 349 | + jvm2js = jvm2js0 |
| 350 | + js2jvm = js2jvm0 |
| 351 | + |
| 352 | + true |
| 353 | + } |
| 354 | + } |
311 | 355 | } |
312 | 356 |
|
313 | 357 | override protected def finalize(): Unit = close() |
|
0 commit comments