Skip to content

Replace manual handle memory management with GC allocated space #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ lazy val commonSettings = Seq(
),
libraryDependencies += "com.lihaoyi" %%% "utest" % "0.8.3" % Test,
testFrameworks += new TestFramework("utest.runner.Framework"),
nativeConfig ~= {_.withMultithreading(false)},
)

lazy val core = project
Expand Down
36 changes: 20 additions & 16 deletions core/src/main/scala/scala/scalanative/loop/Poll.scala
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
package scala.scalanative.loop

import scala.scalanative.libc.stdlib
import LibUV._, LibUVConstants._
import scala.scalanative.unsafe.Ptr
import scala.scalanative.unsafe.{Ptr, sizeOf}
import scala.scalanative.runtime.BlobArray
import internals.HandleUtils

class RWResult(val result: Int, val readable: Boolean, val writable: Boolean)
@inline class Poll(val ptr: Ptr[Byte]) extends AnyVal {
@inline class Poll(private val data: BlobArray) extends AnyVal {
private def handle: Ptr[Byte] = data.atUnsafe(0)

def start(in: Boolean, out: Boolean)(callback: RWResult => Unit): Unit = {
HandleUtils.setData(ptr, callback)
HandleUtils.setData(handle, callback)
var events = 0
if (out) events |= UV_WRITABLE
if (in) events |= UV_READABLE
uv_poll_start(ptr, events, Poll.pollReadWriteCB)
uv_poll_start(handle, events, Poll.pollReadWriteCB)
}

def startReadWrite(callback: RWResult => Unit): Unit = {
HandleUtils.setData(ptr, callback)
uv_poll_start(ptr, UV_READABLE | UV_WRITABLE, Poll.pollReadWriteCB)
HandleUtils.setData(handle, callback)
uv_poll_start(handle, UV_READABLE | UV_WRITABLE, Poll.pollReadWriteCB)
}

def startRead(callback: Int => Unit): Unit = {
HandleUtils.setData(ptr, callback)
uv_poll_start(ptr, UV_READABLE, Poll.pollReadCB)
HandleUtils.setData(handle, callback)
uv_poll_start(handle, UV_READABLE, Poll.pollReadCB)
}

def startWrite(callback: Int => Unit): Unit = {
HandleUtils.setData(ptr, callback)
uv_poll_start(ptr, UV_WRITABLE, Poll.pollWriteCB)
HandleUtils.setData(handle, callback)
uv_poll_start(handle, UV_WRITABLE, Poll.pollWriteCB)
}

def stop(): Unit = {
uv_poll_stop(ptr)
HandleUtils.close(ptr)
uv_poll_stop(handle)
HandleUtils.close(handle)
}
}

Expand Down Expand Up @@ -72,8 +74,10 @@ object Poll {
private lazy val size = uv_handle_size(UV_POLL_T)

def apply(fd: Int): Poll = {
val pollHandle = stdlib.malloc(size)
uv_poll_init(EventLoop.loop, pollHandle, fd)
new Poll(pollHandle)
// GC managed memory, but scans only user data
val data = BlobArray.alloc(size.toInt)
data.setScannableLimitUnsafe(sizeOf[Ptr[_]])
uv_poll_init(EventLoop.loop, data.atUnsafe(0), fd)
new Poll(data)
}
}
17 changes: 11 additions & 6 deletions core/src/main/scala/scala/scalanative/loop/Timer.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package scala.scalanative.loop

import scala.scalanative.libc.stdlib
import scala.concurrent.{Future, Promise}
import scala.concurrent.duration._
import LibUV._, LibUVConstants._
import scala.scalanative.unsafe.Ptr
import scala.scalanative.unsafe.{Ptr, sizeOf}
import scala.scalanative.runtime.BlobArray
import internals.HandleUtils

@inline final class Timer private (private val ptr: Ptr[Byte]) extends AnyVal {
@inline final class Timer private (private val data: BlobArray) extends AnyVal {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:
Should we have a registry for active timers? It makes sense for the repeated timers running for the long time in the background. If user does not keep any handle to them, then they can be GCed at some point. How often due users use them in such way?
For safety reasons it might be best to keep them as long as the libUV might trigger the callback. Unfortunetlly we need to bridge 2 memory spaces - the SN space and libUV space.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps @lolgab would know the answer?

private def ptr = data.atUnsafe(0)
def clear(): Unit = {
uv_timer_stop(ptr)
HandleUtils.close(ptr)
Expand All @@ -29,17 +30,21 @@ object Timer {
repeat: Long,
callback: () => Unit
): Timer = {
val timerHandle = stdlib.malloc(uv_handle_size(UV_TIMER_T))
// GC managed memory, but scans only user data
val data = BlobArray.alloc(uv_handle_size(UV_TIMER_T).toInt)
data.setScannableLimitUnsafe(sizeOf[Ptr[_]])

val timerHandle = data.atUnsafe(0)
uv_timer_init(EventLoop.loop, timerHandle)
HandleUtils.setData(timerHandle, callback)
val timer = new Timer(timerHandle)
val timer = new Timer(data)
val withClearIfTimeout: () => Unit =
if (repeat == 0L) { () =>
{
callback()
timer.clear()
}
} else callback
HandleUtils.setData(timerHandle, withClearIfTimeout)
uv_timer_start(timerHandle, timeoutCB, timeout, repeat)
timer
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ package internals
import scala.scalanative.runtime._
import scala.scalanative.runtime.Intrinsics._
import scala.scalanative.unsafe.Ptr
import scala.scalanative.libc.stdlib
import LibUV._

private[loop] object HandleUtils {
private val references = new java.util.IdentityHashMap[Object, Int]()

@inline def getData[T <: Object](handle: Ptr[Byte]): T = {
// data is the first member of uv_loop_t
val ptrOfPtr = handle.asInstanceOf[Ptr[Ptr[Byte]]]
Expand All @@ -24,23 +21,16 @@ private[loop] object HandleUtils {
// data is the first member of uv_loop_t
val ptrOfPtr = handle.asInstanceOf[Ptr[Ptr[Byte]]]
if (obj != null) {
references.put(obj, references.get(obj) + 1)
val rawptr = castObjectToRawPtr(obj)
!ptrOfPtr = fromRawPtr[Byte](rawptr)
} else {
!ptrOfPtr = null
}
}
private val onCloseCB: CloseCB = (handle: UVHandle) => {
stdlib.free(handle)
}
@inline def close(handle: Ptr[Byte]): Unit = {
if (getData(handle) != null) {
uv_close(handle, onCloseCB)
val data = getData[Object](handle)
val current = references.get(data)
if (current > 1) references.put(data, current - 1)
else references.remove(data)
val data = getData[Object](handle)
if (data != null) {
uv_close(handle, null)
setData(handle, null)
}
}
Expand Down