Skip to content

Commit 642f9fd

Browse files
authored
Merge pull request #15 from scala-native/use-scala-native-loop
Use default Scala Native ExecutionContext instead of custom
2 parents 0b3f780 + 948baa4 commit 642f9fd

File tree

12 files changed

+63
-122
lines changed

12 files changed

+63
-122
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ Extensible event loop and async-oriented IO for Scala Native; powered by libuv.
66
If you're looking for the new 0.4 rewrite, check the `04` branch. The current state of master is mostly extracted from the book [Modern Systems Programming in Scala Native](https://pragprog.com/book/rwscala/modern-systems-programming-with-scala-native).
77

88
## What is it?
9-
native-loop provides a real, asynchronous ExecutionContext/Future implementation for Scala Native.
9+
10+
scala-native-loop provides a real, asynchronous ExecutionContext implementation for Scala Native.
1011
It's backed by libuv, the same C library that the node.js ecosystem runs on; in addition to basic
1112
Future dispatching, we can also use libuv to provide other basic functionality, like:
1213

@@ -19,7 +20,7 @@ Future dispatching, we can also use libuv to provide other basic functionality,
1920
To provide a working API for practical, async Scala Native programs, we have two subprojects,
2021
`client` and `server`, which provide an async HTTP client and server, respectively, by integrating addtional C libraries: [nodejs/http-parser](https://github.com/nodejs/http-parser) for request parsing, and [curl](https://github.com/curl/curl) for a full featured client with HTTPS support.
2122

22-
That said - providing a full-featured ecosystem in a single library isn't feasible - instead, we provide a `LoopExtension` trait that allows other C libaries to be integrated to the underlying event loop, in the same way that libcurl and http-parser are integrated; this opens up the possiblity of fully asynchronous bindings for postgres, redis, and many others.
23+
That said - providing a full-featured ecosystem in a single library isn't feasible - instead, we provide a `LoopExtension` trait that allows other C libraries to be integrated to the underlying event loop, in the same way that libcurl and http-parser are integrated; this opens up the possiblity of fully asynchronous bindings for postgres, redis, and many others.
2324

2425
## Why is this here?
2526

build.sbt

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ licenses := Seq(
33
"Apache 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")
44
)
55
publishMavenStyle := true
6-
publishArtifact in Test := false
6+
Test / publishArtifact := false
77
pomIncludeRepository := { _ =>
88
false
99
}
10-
publishTo in ThisBuild := {
10+
ThisBuild / publishTo := {
1111
val nexus = "https://oss.sonatype.org/"
1212
if (isSnapshot.value)
1313
Some("snapshots" at nexus + "content/repositories/snapshots")
@@ -34,58 +34,60 @@ lazy val commonSettings = Seq(
3434
version := "0.1.1-SNAPSHOT",
3535
scalaVersion := "2.11.12",
3636
scalacOptions ++= Seq(
37-
"-feature"
37+
"-feature",
38+
"-Ywarn-unused-import",
39+
"-Xfatal-warnings"
3840
),
39-
skip in publish := true,
40-
skip in publishLocal := true
41+
publish / skip := true,
42+
publishLocal / skip := true
4143
)
4244

43-
lazy val core = (project in file("core"))
45+
lazy val core = project.in(file("core"))
4446
.settings(name := "native-loop-core")
45-
.settings(commonSettings: _*)
46-
.settings(skip in publish := false)
47-
.settings(skip in publishLocal := false)
47+
.settings(commonSettings)
48+
.settings(publish / skip := false)
49+
.settings(publishLocal / skip := false)
4850
.enablePlugins(ScalaNativePlugin)
4951

50-
lazy val pipe = (project in file("pipe"))
51-
.settings(commonSettings: _*)
52+
lazy val pipe = project.in(file("pipe"))
53+
.settings(commonSettings)
5254
.enablePlugins(ScalaNativePlugin)
5355
.dependsOn(core)
5456

55-
lazy val client = (project in file("client"))
56-
.settings(commonSettings: _*)
57+
lazy val client = project.in(file("client"))
58+
.settings(commonSettings)
5759
.enablePlugins(ScalaNativePlugin)
5860
.dependsOn(core)
5961

60-
lazy val server = (project in file("server"))
61-
.settings(commonSettings: _*)
62+
lazy val server = project.in(file("server"))
63+
.settings(commonSettings)
6264
.enablePlugins(ScalaNativePlugin)
6365
.dependsOn(core)
6466

65-
lazy val scalaJsCompat = (project in file("scalajs-compat"))
67+
lazy val scalaJsCompat = project.in(file("scalajs-compat"))
6668
.settings(name := "native-loop-js-compat")
67-
.settings(commonSettings: _*)
68-
.settings(skip in publish := false)
69-
.settings(skip in publishLocal := false)
69+
.settings(commonSettings)
70+
.settings(publish / skip := false)
71+
.settings(publishLocal / skip := false)
7072
.enablePlugins(ScalaNativePlugin)
7173
.dependsOn(core)
7274

73-
lazy val serverExample = (project in file("examples/server"))
74-
.settings(commonSettings: _*)
75+
lazy val serverExample = project.in(file("examples/server"))
76+
.settings(commonSettings)
7577
.enablePlugins(ScalaNativePlugin)
7678
.dependsOn(core, server, client)
7779

78-
lazy val pipeExample = (project in file("examples/pipe"))
79-
.settings(commonSettings: _*)
80+
lazy val pipeExample = project.in(file("examples/pipe"))
81+
.settings(commonSettings)
8082
.enablePlugins(ScalaNativePlugin)
8183
.dependsOn(core, pipe, client)
8284

83-
lazy val curlExample = (project in file("examples/curl"))
84-
.settings(commonSettings: _*)
85+
lazy val curlExample = project.in(file("examples/curl"))
86+
.settings(commonSettings)
8587
.enablePlugins(ScalaNativePlugin)
8688
.dependsOn(core, client)
8789

88-
lazy val timerExample = (project in file("examples/timer"))
89-
.settings(commonSettings: _*)
90+
lazy val timerExample = project.in(file("examples/timer"))
91+
.settings(commonSettings)
9092
.enablePlugins(ScalaNativePlugin)
9193
.dependsOn(core)

client/curl.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package scala.scalanative.loop
22
import scala.scalanative.unsafe._
3-
import scala.scalanative.unsigned._
43
import scala.collection.mutable
54
import scala.scalanative.libc.stdlib._
65
import scala.scalanative.libc.string._

core/loop.scala

Lines changed: 24 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,38 @@
11
package scala.scalanative.loop
22
import scala.scalanative.unsafe._
3-
import scala.scalanative.libc.stdlib
4-
5-
import scala.collection.mutable.ListBuffer
6-
import scala.concurrent.{ExecutionContext, ExecutionContextExecutor}
7-
import scala.concurrent.Future
8-
import scala.concurrent.Promise
9-
import scala.util.{Try, Success}
10-
import scala.Option
11-
import LibUV.Buffer
12-
import LibUVConstants.check
13-
14-
object EventLoop extends ExecutionContextExecutor {
15-
import LibUV._, LibUVConstants._
16-
17-
val loop = uv_default_loop()
18-
private val taskQueue = ListBuffer[Runnable]()
19-
val handle = stdlib.malloc(uv_handle_size(UV_PREPARE_T))
203

21-
private def initDispatcher(loop: LibUV.Loop): PrepareHandle = {
22-
check(uv_prepare_init(loop, handle), "uv_prepare_init")
23-
return handle
24-
}
25-
26-
val prepareCallback = new PrepareCB {
27-
def apply(handle: PrepareHandle) = {
28-
while (taskQueue.nonEmpty) {
29-
val runnable = taskQueue.remove(0)
30-
try {
31-
runnable.run()
32-
} catch {
33-
case t: Throwable => reportFailure(t)
34-
}
35-
}
36-
if (taskQueue.isEmpty) {
37-
LibUV.uv_prepare_stop(handle)
38-
}
39-
}
40-
}
4+
object EventLoop {
5+
import LibUV._, LibUVConstants._
416

42-
private val dispatcher = initDispatcher(loop)
7+
val loop: LibUV.Loop = uv_default_loop()
438

449
// Schedule loop execution after main ends
4510
scalanative.runtime.ExecutionContext.global.execute(
4611
new Runnable {
12+
/**
13+
* This is the implementation of the event loop
14+
* that integrates with libuv. The logic is the
15+
* following:
16+
* - First we run all Scala futures in the default
17+
* execution context
18+
* - Then in loop:
19+
* - we check if they generated IO calls on
20+
* the event loop
21+
* - If it's the case we run libuv's event loop
22+
* using UV_RUN_ONCE that blocks only once
23+
* - We run the default execution context again
24+
* in case the callbacks generated new Futures
25+
*/
4726
def run(): Unit = {
48-
val returnCode = EventLoop.run()
49-
if (returnCode != 0) {
50-
Zone { implicit z =>
51-
System.err.println(fromCString(uv_err_name(returnCode)))
52-
}
53-
System.exit(returnCode)
27+
scala.scalanative.runtime.loop()
28+
while (uv_loop_alive(loop) != 0) {
29+
uv_run(loop, UV_RUN_ONCE)
30+
scala.scalanative.runtime.loop()
5431
}
32+
uv_loop_close(loop)
5533
}
5634
}
5735
)
58-
59-
def execute(runnable: Runnable): Unit = {
60-
taskQueue += runnable
61-
check(uv_prepare_start(handle, prepareCallback), "uv_prepare_start")
62-
}
63-
64-
def reportFailure(t: Throwable): Unit = {
65-
t.printStackTrace()
66-
}
67-
68-
def run(mode: Int = UV_RUN_DEFAULT): Int = {
69-
var continue = 1
70-
while (continue != 0) {
71-
continue = uv_run(loop, mode)
72-
}
73-
continue
74-
}
7536
}
7637

7738
@link("uv")
@@ -103,6 +64,8 @@ object LibUV {
10364

10465
def uv_default_loop(): Loop = extern
10566
def uv_loop_size(): CSize = extern
67+
def uv_loop_alive(loop: Loop): CInt = extern
68+
def uv_loop_close(loop: Loop): CInt = extern
10669
def uv_is_active(handle: Ptr[Byte]): Int = extern
10770
def uv_handle_size(h_type: Int): CSize = extern
10871
def uv_req_size(r_type: Int): CSize = extern

core/timer.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@ package scala.scalanative.loop
22
import scala.scalanative.unsafe._
33
import scala.scalanative.libc.stdlib
44
import scala.collection.mutable
5-
import scala.concurrent.{ExecutionContext, ExecutionContextExecutor}
65
import scala.concurrent.Future
76
import scala.concurrent.Promise
8-
import scala.util.{Try, Success}
9-
import scala.Option
107
import scala.concurrent.duration._
118
import LibUV._, LibUVConstants._
129

examples/curl/main.scala

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import scala.scalanative.loop._
2-
import scala.concurrent._
3-
import scala.concurrent.duration._
42
import LibCurlConstants._
3+
import scala.concurrent.ExecutionContext.Implicits.global
54

65
object Main {
7-
implicit val ec: ExecutionContext = EventLoop
8-
96
def main(args: Array[String]): Unit = {
107
Curl.startRequest(GET, "http://www.example.com", Seq()).map { response =>
118
println(s"got response: $response")
129
}
13-
14-
EventLoop.run()
1510
}
1611
}

examples/server/main.scala

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import scala.scalanative.loop._
2-
import scala.concurrent._
3-
import scala.concurrent.duration._
42

53
object Main {
6-
implicit val ec: ExecutionContext = EventLoop
7-
84
def main(args: Array[String]): Unit = {
95
Server.init(9999) { (r, c) =>
106
println(s"received request $r on connection $c")
@@ -16,7 +12,5 @@ object Main {
1612
"hello!"
1713
)
1814
}
19-
20-
EventLoop.run()
2115
}
2216
}

examples/timer/main.scala

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import scala.scalanative.loop._
2-
import scala.concurrent._
32
import scala.concurrent.duration._
3+
import scala.concurrent.ExecutionContext.Implicits.global
44

55
object Main {
6-
implicit val ec: ExecutionContext = EventLoop
7-
86
def main(args: Array[String]): Unit = {
97
Timer
108
.delay(3.seconds)
@@ -19,7 +17,5 @@ object Main {
1917
.onComplete { _ =>
2018
println("done")
2119
}
22-
23-
EventLoop.run()
2420
}
2521
}

pipe/pipe.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package scala.scalanative.loop
22
import scala.scalanative.unsafe._
33
import scala.scalanative.libc.stdlib
4-
import scala.scalanative.libc.string
54

65
import scala.collection.mutable
7-
import scala.util.{Try, Success, Failure}
8-
import scala.concurrent.{Future, ExecutionContext}
6+
import scala.concurrent.Future
97
import scala.concurrent.{Promise}
108

119
case class Handle(serial: Long, handle: Ptr[Byte]) {
12-
import LibUV._, LibUVConstants._
10+
import LibUV._
1311

1412
def stream(
1513
itemHandler: StreamIO.ItemHandler,

scalajs-compat/Handles.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
package scala.scalajs.js.timers
1414

15-
import scalanative.unsafe.Zone
1615
import scalanative.loop.LibUV.TimerHandle
1716

1817
/** <span class="badge badge-non-std" style="float: right;">Non-Standard</span>

0 commit comments

Comments
 (0)