Skip to content

Commit d1a8783

Browse files
committed
Switch PluginFrontend to domain sockets on macOS
1 parent f5c8495 commit d1a8783

File tree

2 files changed

+54
-5
lines changed

2 files changed

+54
-5
lines changed

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

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package protocbridge.frontend
22

3+
import org.newsclub.net.unix.AFUNIXServerSocket
4+
import protocbridge.{ExtraEnv, ProtocCodeGenerator}
5+
6+
import java.net.ServerSocket
37
import java.nio.file.attribute.PosixFilePermission
48
import java.nio.file.{Files, Path}
59
import java.{util => ju}
10+
import scala.concurrent.ExecutionContext.Implicits.global
11+
import scala.concurrent.{Future, blocking}
612

713
/** PluginFrontend for macOS.
814
*
@@ -12,16 +18,58 @@ import java.{util => ju}
1218
* widely available on macOS, this is the simplest and most reliable solution
1319
* for macOS.
1420
*/
15-
object MacPluginFrontend extends SocketBasedPluginFrontend {
21+
object MacPluginFrontend extends PluginFrontend {
22+
case class InternalState(
23+
tempDirPath: Path,
24+
socketPath: Path,
25+
serverSocket: ServerSocket,
26+
shellScript: Path
27+
)
28+
29+
override def prepare(
30+
plugin: ProtocCodeGenerator,
31+
env: ExtraEnv
32+
): (Path, InternalState) = {
33+
val tempDirPath = Files.createTempDirectory("protopipe-")
34+
val socketPath = tempDirPath.resolve("socket")
35+
val serverSocket = AFUNIXServerSocket.bindOn(socketPath, true)
36+
val sh = createShellScript(socketPath)
37+
38+
Future {
39+
blocking {
40+
// Accept a single client connection from the shell script.
41+
val client = serverSocket.accept()
42+
try {
43+
val response =
44+
PluginFrontend.runWithInputStream(
45+
plugin,
46+
client.getInputStream,
47+
env
48+
)
49+
client.getOutputStream.write(response)
50+
} finally {
51+
client.close()
52+
}
53+
}
54+
}
55+
(sh, InternalState(tempDirPath, socketPath, serverSocket, sh))
56+
}
57+
58+
override def cleanup(state: InternalState): Unit = {
59+
state.serverSocket.close()
60+
if (sys.props.get("protocbridge.debug") != Some("1")) {
61+
Files.delete(state.tempDirPath)
62+
Files.delete(state.shellScript)
63+
}
64+
}
1665

17-
protected def createShellScript(port: Int): Path = {
66+
private def createShellScript(socketPath: Path): Path = {
1867
val shell = sys.env.getOrElse("PROTOCBRIDGE_SHELL", "/bin/sh")
19-
// We use 127.0.0.1 instead of localhost for the (very unlikely) case that localhost is missing from /etc/hosts.
2068
val scriptName = PluginFrontend.createTempFile(
2169
"",
2270
s"""|#!$shell
2371
|set -e
24-
|nc 127.0.0.1 $port
72+
|nc -U "$socketPath"
2573
""".stripMargin
2674
)
2775
val perms = new ju.HashSet[PosixFilePermission]

build.sbt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ lazy val bridge: Project = project
2929
"org.scalatest" %% "scalatest" % "3.2.17" % "test",
3030
"org.scalacheck" %% "scalacheck" % "1.17.0" % "test",
3131
"org.scala-lang.modules" %% "scala-collection-compat" % "2.11.0" % "test",
32-
"io.get-coursier" %% "coursier" % coursierVersion % "test"
32+
"io.get-coursier" %% "coursier" % coursierVersion % "test",
33+
"com.kohlschutter.junixsocket" % "junixsocket-core" % "2.10.0"
3334
),
3435
scalacOptions ++= (if (scalaVersion.value.startsWith("2.13."))
3536
Seq("-Wconf:origin=.*JavaConverters.*:s")

0 commit comments

Comments
 (0)