and it's about native-loop, a new concurrency package for Scala Native 0.4
http://...
But what is "idiomatic Scala"?
val raw_data:Ptr[Byte] = malloc(sizeof[Long])
val int_ptr:Ptr[Int] = raw_data.asInstanceOf[Int]
// the ! operator updates a pointer's contents on the left-hand side
!int_ptr = 0
// on the right-hand side, it dereferences the pointer to read its value
printf(c"pointer at %p has value %d\n", int_ptr, !int_ptr)
!int_ptr = 1
printf(c"pointer at %p has value %d\n", int_ptr, !int_ptr)
//this will segfault
free(raw_data)
printf(c"pointer at %p has value %d\n", int_ptr, !int_ptr)
not shown: arrays and pointer arithmetic
@extern
object Quicksort {
type Comparator = CFuncPtr2[Ptr[Byte],Ptr[Byte],Int]
def qsort(array:Ptr[Byte],num:CSize,size:CSize,cmp:Comparator):Unit = extern
}
object App {
type MyStruct = CStruct3[CString,CString,Int]
val myStructComp = new Comparator {
def apply(left:Ptr[Byte],right:[Byte]):Int = {
val l = left.asInstanceOf[Ptr[MyStruct]]
val r = right.asInstanceOf[Ptr[MyStruct]]
if (l._3 < r._3) {
-1
} else if (l._3 == l._3) {
0
else {
1
}
}
}
}
@extern
object Quicksort {
type Comparator = CFuncPtr2[Ptr[Byte],Ptr[Byte],Int]
def qsort(array:Ptr[Byte],num:CSize,size:CSize,cmp:Comparator):Unit = extern
}
object App {
type MyStruct = CStruct3[CString,CString,Int]
val myStructComp = new Comparator {
def apply(left:Ptr[Byte],right:[Byte]):Int = {
val l = left.asInstanceOf[Ptr[MyStruct]]
val r = right.asInstanceOf[Ptr[MyStruct]]
l - r
}
}
def main(args:Array[String]):Unit = {
// ...
val data:Ptr[MyStruct] = ???
val data_size = ???
qsort(data,data_size,sizeof[MyStruct],myStructComp)
// ...
}
}
We'll see more of this soon!
libuv abstracts over different operating systems and different kinds of IO
We just need to adapt a queue-based EC to libuv's lifecycle of callbacks
object ExecutionContext {
def global: ExecutionContextExecutor = QueueExecutionContext
private object QueueExecutionContext extends ExecutionContextExecutor {
def execute(runnable: Runnable): Unit = queue += runnable
def reportFailure(t: Throwable): Unit = t.printStackTrace()
}
private val queue: ListBuffer[Runnable] = new ListBuffer
private[runtime] def loop(): Unit = { // this runs after main() returns
while (queue.nonEmpty) {
val runnable = queue.remove(0)
try {
runnable.run()
} catch {
case t: Throwable =>
QueueExecutionContext.reportFailure(t)
}
}
}
}
trait EventLoopLike extends ExecutionContextExecutor {
def addExtension(e:LoopExtension):Unit
def run(mode:Int = UV_RUN_DEFAULT):Unit
}
trait LoopExtension {
def activeRequests():Int
}
object EventLoop extends EventLoopLike {
val loop = uv_default_loop()
private val taskQueue = ListBuffer[Runnable]()
def execute(runnable: Runnable): Unit = taskQueue += runnable
def reportFailure(t: Throwable): Unit = {
println(s"Future failed with Throwable $t:")
t.printStackTrace()
}
// ...
// ...
private def dispatchStep(handle:PrepareHandle) = {
while (taskQueue.nonEmpty) {
val runnable = taskQueue.remove(0)
try {
runnable.run()
} catch {
case t: Throwable => reportFailure(t)
}
}
if (taskQueue.isEmpty && !extensionsWorking) {
println("stopping dispatcher")
LibUV.uv_prepare_stop(handle)
}
}
private val dispatcher_cb = CFunctionPtr.fromFunction1(dispatchStep)
private def initDispatcher(loop:LibUV.Loop):PrepareHandle = {
val handle = stdlib.malloc(uv_handle_size(UV_PREPARE_T))
check(uv_prepare_init(loop, handle), "uv_prepare_init")
check(uv_prepare_start(handle, dispatcher_cb), "uv_prepare_start")
return handle
}
private val dispatcher = initDispatcher(loop)
// ...
private val extensions = ListBuffer[LoopExtension]()
private def extensionsWorking():Boolean = {
extensions.exists( _.activeRequests > 0)
}
def addExtension(e:LoopExtension):Unit = {
extensions.append(e)
}
object Timer extends LoopExtension {
EventLoop.addExtension(this)
var serial = 0L
var timers = mutable.HashMap[Long,Promise[Unit]]()
override def activeRequests():Int =
timers.size
def delay(dur:Duration):Future[Unit] = ???
val timerCB:TimerCB = ???
}
@extern
object TimerImpl {
type Timer: Ptr[Long] // why long and not byte?
def uv_timer_init(loop:Loop, handle:TimerHandle):Int = extern
def uv_timer_start(handle:TimerHandle, cb:TimerCB,
timeout:Long, repeat:Long):Int = extern
def uv_timer_stop(handle:TimerHandle):Int = extern
}
object Timer extends LoopExtension {
EventLoop.addExtension(this)
var serial = 0L
var timers = mutable.HashMap[Long,Promise[Unit]]()
override def activeRequests():Int =
timers.size
def delay(dur:Duration):Future[Unit] = ???
val timerCB:TimerCB = ???
}
@extern
object TimerImpl {
type Timer: Ptr[Long] // why long and not byte?
def uv_timer_init(loop:Loop, handle:TimerHandle):Int = extern
def uv_timer_start(handle:TimerHandle, cb:TimerCB,
timeout:Long, repeat:Long):Int = extern
def uv_timer_stop(handle:TimerHandle):Int = extern
}
def delay(dur:Duration):Future[Unit] = {
val promise = Promise[Unit]()
serial += 1
val timer_id = serial
timers(timer_id) = promise
val millis = dur.toMillis
val timer_handle = stdlib.malloc(uv_handle_size(UV_TIMER_T))
uv_timer_init(EventLoop.loop,timer_handle)
val timer_data = timer_handle.asInstanceOf[Ptr[Long]]
!timer_data = timer_id
uv_timer_start(timer_handle, timerCB, millis, 0)
promise.future
}
val timerCB = new TimerCB {
def apply(handle:TimerHandle):Unit = {
println("callback fired!")
val timer_data = handle.asInstanceOf[Ptr[Long]]
val timer_id = !timer_data
val timer_promise = timers(timer_id)
timers.remove(timer_id)
println(s"completing promise ${timer_id}")
timer_promise.success(())
}
}
val timerCB = new TimerCB {
def apply(handle:TimerHandle):Unit = {
println("callback fired!")
val timer_data = handle.asInstanceOf[Ptr[Long]]
val timer_id = !timer_data
val timer_promise = timers(timer_id)
timers.remove(timer_id)
println(s"completing promise ${timer_id}")
timer_promise.success(())
}
}
object Main {
implicit val ec:ExecutionContext = EventLoop
def main(args:Array[String]):Unit = {
println("hello!")
Timer.delay(3 seconds).onComplete { _ =>
println("goodbye!")
}
EventLoop.run()
}
}
@extern
object LibUV {
def uv_pipe_init(loop:Loop, handle:PipeHandle, ipc:Int):Int = extern
def uv_pipe_open(handle:PipeHandle, fd:Int):Int = extern
def uv_read_start(client:PipeHandle, allocCB:AllocCB, readCB:ReadCB): Int = extern
type Loop = ???
type PipeHandle = ???
type AllocCB = ???
type ReadCB = ???
}
@extern
object LibUV {
def uv_pipe_init(loop:Loop, handle:PipeHandle, ipc:Int):Int = extern
def uv_pipe_open(handle:PipeHandle, fd:Int):Int = extern
def uv_read_start(client:PipeHandle, allocCB:AllocCB, readCB:ReadCB): Int = extern
type Loop = Ptr[Byte]
type PipeHandle = Ptr[Byte]
type AllocCB = CFunctionPtr3[PipeHandle,CSize,Ptr[Buffer],Unit]
type ReadCB = CFunctionPtr3[PipeHandle,CSSize,Ptr[Buffer],Unit]
def uv_handle_size(h_type:Int): CSize = extern
type Buffer = CStruct2[Ptr[Byte],CSize]
}
trait Pipe[I,O] {
def map[U](f: O => U):Pipe[O,U]
def feed(i:I):Unit
}
case class SourcePipe(fd:Int) extends Pipe[String,String]
object SourcePipe {
val loop = uv_default_loop()
def stream(fd:Int):SourcePipe = {
val handle = malloc(uv_handle_size(UV_PIPE_T)) // no need to cast
uv_pipe_init(loop,handle,0)
uv_pipe_open(handle,fd)
uv_read_start(handle,???,???) // we need to supply these callbacks
}
}
trait Pipe[I,O] {
def map[U](f: O => U):Pipe[O,U]
def feed(i:I):Unit
}
case class SourcePipe(fd:Int) extends Pipe[String,String]
object SourcePipe {
val loop = uv_default_loop()
def stream(fd:Int):SourcePipe = {
val handle = malloc(uv_handle_size(UV_PIPE_T)) // no need to cast
uv_pipe_init(loop,handle,0)
uv_pipe_open(handle,fd)
uv_read_start(handle,???,???) // we need to supply these callbacks
}
}
trait Pipe[I,O] {
def map[U](f: O => U):Pipe[O,U]
def feed(i:I):Unit
}
case class SourcePipe(fd:Int) extends Pipe[String,String]
object SourcePipe {
val loop = uv_default_loop()
var handlers:mutable.Map[Int,SourcePipe] // our "dispatcher"
def stream(fd:Int):SourcePipe = {
val handle = malloc(uv_handle_size(UV_PIPE_T)) // no need to cast
uv_pipe_init(loop, handle, 0)
uv_pipe_open(handle, fd)
// store the fd in the pipe's custom data slot
val pipeDataPointer = handle.cast[Ptr[Int]]
!pipeDataPointer = fd
uv_read_start(handle, ???, ???) // need to fill these in
val result = SourcePipe(fd)
handlers(fd) = result
result
}
}
def on_alloc(client:PipeHandle, size:CSize, buffer:Ptr[Buffer]):Unit = {
val bufferData = malloc(4096)
!buffer._1 = bufferData
!buffer._2 = 4096
}
val allocCB = CFunctionPtr.fromFunction3(on_alloc)
def on_read(handle:PipeHandle,size:CSize,buffer:Ptr[Buffer]):Unit = {
val pipeDataPointer = handle.cast[Ptr[Int]]
val fd = !pipeDataPointer
println(s"read $size bytes from fd $fd")
if (size < 0) {
println("size < 0, closing")
handlers.remove(fd)
} else {
val tempBuffer = stdlib.malloc(size + 1)
strncpy(tempBuffer, !buffer._1, size + 1)
val stringData = fromCString(tempBuffer)
handlers(fd).feed(stringData)
stdlib.free(tempBuffer)
}
}
val readCB = CFunctionPtr.fromFunction3(on_read)
def on_read(handle:PipeHandle,size:CSize,buffer:Ptr[Buffer]):Unit = {
val pipeDataPointer = handle.cast[Ptr[Int]]
val fd = !pipeDataPointer
println(s"read $size bytes from fd $fd")
if (size < 0) {
println("size < 0, closing")
handlers.remove(fd)
} else {
val stringData = bytesToString(!buffer._1, size)
handlers(fd).feed(stringData)
stdlib.free(tempBuffer)
}
}
val readCB = CFunctionPtr.fromFunction3(on_read)
def bytesToString(data:Ptr[Byte],len:Long):String = {
val bytes = new Array[Byte](len.toInt)
var c = 0
while (c < len) {
bytes(c) = !(data + c)
c += 1
}
new String(bytes)
}
case class SourcePipe(fd:Int) extends Pipe[String,String] {
var destinations:List[Pipe[String,_]] = List.empty
def feed(t:String) = {
for (dest <- destinations) {
dest.feed(t)
}
}
def map[T](f: String => T) = {
val m = MapPipe(f)
destinations = destinations + m
m
}
}
case class MapPipe[I,O](f: I => O) extends Pipe[I,O] {
var destinations:List[Pipe[O]] = List.empty
def feed(t:I) = {
val o = f(i)
for (dest <- destinations) {
dest.feed(o)
}
}
// ...
}
def main(args:Array[String]):Unit = {
val STDIN = 0
SourcePipe(STDIN).map { line =>
print(s"read '${line.trim()}' from standard input")
}
uv_loop_run(uv_default_loop,0)
}
object PipeIO extends LoopExtension {
type ItemHandler = ((String,PipeHandle,Long) => Unit)
type DoneHandler = ((PipeHandle,Long) => Unit)
type Handlers = (ItemHandler, DoneHandler)
var streams = mutable.HashMap[Long,Handlers]()
var serial = 0L
override def activeRequests:Int = {
streams.size
}
def stream(fd:Int)(itemHandler:ItemHandler,
doneHandler:DoneHandler):Long = ???
def streamUntilDone(fd:Int)(handler:ItemHandler):Future[Long] = ???
}
def main(args:Array[String]):Unit = {
println("hello Scala Days!")
Future.successful(()).onComplete { v =>
println(s"Future has completed with value $v")
}
println("about to invoke event loop")
Loop.run()
}
val resp = Zone { implicit z =>
for (arg <- args) {
val url = toCString(arg)
val resp = Curl.get(url)
resp.onComplete {
case Success(data) =>
println(s"got back response for ${arg} - body of length ${data.body.size}")
println(s"headers:")
for (h <- data.headers) {
println(s"request header: $h")
}
println(s"body: ${data.body}")
case Failure(f) =>
println("request failed",f)
}
}
}
loop.run()
def main(args:Array[String]):Unit = {
Service()
.getAsync("/async/") { r => Future(OK(
Map("asyncMessage" -> s"got (async routed) request $r")
))}
.get("/") { r => OK(
Map("message" -> s"got (routed) request $r")
)}
.run(9999)
println("done")
}
object Parsing {
def http_parser_init(p:Ptr[Parser],parser_type:Int):Unit = extern
def http_parser_settings_init(s:Ptr[ParserSettings]):Unit = extern
def http_parser_execute(p:Ptr[Parser],s:Ptr[ParserSettings],
data:Ptr[Byte],len:Long):Long = extern
def http_method_str(method:CChar):CString = extern
type Parser = CStruct8[
Long, // private data
Long, // private data
UShort, // major version
UShort, // minor version
UShort, // status (request only)
CChar, // method
CChar, // Error (last bit upgrade)
Ptr[Byte] // user data
]
//...
object Parsing {
type HttpCB = CFunctionPtr1[Ptr[Parser],Int]
type HttpDataCB = CFunctionPtr3[Ptr[Parser],CString,Long,Int]
type ParserSettings = CStruct8[
HttpCB, // on_message_begin
HttpDataCB, // on_url
HttpDataCB, // on_status
HttpDataCB, // on_header_field
HttpDataCB, // on_header_value
HttpCB, // on_headers_complete
HttpDataCB, // on_body
HttpCB // on_message_complete
]
//...
type ConnectionState
type RequestState
type Router
type Response
object Server extends Parsing with LoopExtension {
import LibUVConstants._, LibUV._,HttpParser._
implicit val ec = EventLoop
val loop = EventLoop.loop
var serial = 1L
override val requests = mutable.Map[Long,RequestState]()
var activeRequests = 0
val urlCB:HttpDataCB = CFunctionPtr.fromFunction3(onURL)
val onKeyCB:HttpDataCB = CFunctionPtr.fromFunction3(onHeaderKey)
val onValueCB:HttpDataCB = CFunctionPtr.fromFunction3(onHeaderValue)
val completeCB:HttpCB = CFunctionPtr.fromFunction1(onMessageComplete)
val parserSettings = malloc(sizeof[ParserSettings]).cast[Ptr[ParserSettings]]
http_parser_settings_init(parserSettings)
!parserSettings._2 = urlCB
!parserSettings._4 = onKeyCB
!parserSettings._5 = onValueCB
!parserSettings._8 = completeCB
// We'll supply the definitions of these callbacks soon!
var router:Function1[Request[String],Route] = null
def init(port:Int, f:Request[String] => Route):Unit = {
EventLoop.addExtension(this)
router = f
val addr = malloc(64)
check(uv_ip4_addr(c"0.0.0.0", 9999, addr),"uv_ip4_addr")
val server = malloc(uv_handle_size(UV_TCP_T)).cast[TCPHandle]
check(uv_tcp_init(loop, server), "uv_tcp_init")
check(uv_tcp_bind(server, addr, 0), "uv_tcp_bind")
check(uv_listen(server, 4096, connectCB), "uv_listen")
this.activeRequests = 1
}
def onConnect(server:TCPHandle, status:Int):Unit = {
val client = malloc(uv_handle_size(UV_TCP_T)).cast[TCPHandle]
val id = serial
serial += 1
val state = malloc(sizeof[ConnectionState]).cast[Ptr[ConnectionState]]
!state._1 = serial
!state._2 = client
http_parser_init(state._3,HTTP_REQUEST)
!(state._3)._8 = state.cast[Ptr[Byte]]
!(client.cast[Ptr[Ptr[Byte]]]) = state.cast[Ptr[Byte]]
check(uv_tcp_init(loop, client), "uv_tcp_init (client)")
check(uv_accept(server, client), "uv_accept")
check(uv_read_start(client, allocCB, readCB), "uv_read_start")
}
def onAlloc(handle:TCPHandle, size:CSize, buffer:Ptr[Buffer]):Unit = {
val buf = stdlib.malloc(4096)
buf(4095) = 0
!buffer._1 = buf
!buffer._2 = 4095
}
def onRead(handle:TCPHandle, size:CSize, buffer:Ptr[Buffer]):Unit = {
val state_ptr = handle.cast[Ptr[Ptr[ConnectionState]]]
val parser = (!state_ptr)._3
val message_id = !(!state_ptr)._1
println(s"conn $message_id: read message of size $size")
if (size < 0) {
uv_close(handle, null)
stdlib.free(!buffer._1)
} else {
http_parser_execute(parser,parserSettings,!buffer._1,size)
stdlib.free(!buffer._1)
}
}
trait Parsing {
import LibUV._,HttpParser._
val requests:mutable.Map[Long,RequestState]
def handleRequest(id:Long,handle:TCPHandle,request:RequestState):Unit
type ConnectionState = CStruct3[Long,TCPHandle,Parser]
val HTTP_REQUEST = 0
val HTTP_RESPONSE = 1
val HTTP_BOTH = 2
def bytesToString(data:Ptr[Byte],len:Long):String = {
val bytes = new Array[Byte](len.toInt)
var c = 0
while (c < len) {
bytes(c) = !(data + c)
c += 1
}
new String(bytes)
}
//...
trait Parsing {
import LibUV._,HttpParser._
val requests:mutable.Map[Long,RequestState]
def handleRequest(id:Long,handle:TCPHandle,request:RequestState):Unit
type ConnectionState = CStruct3[Long,TCPHandle,Parser]
val HTTP_REQUEST = 0
val HTTP_RESPONSE = 1
val HTTP_BOTH = 2
def bytesToString(data:Ptr[Byte],len:Long):String = {
val bytes = new Array[Byte](len.toInt)
var c = 0
while (c < len) {
bytes(c) = !(data + c)
c += 1
}
new String(bytes)
}
//...
def onURL(p:Ptr[Parser],data:CString,len:Long):Int = {
val state = (!p._8).cast[Ptr[ConnectionState]]
val message_id = !state._1
val url = bytesToString(data,len)
val m = !p._6
val method = fromCString(http_method_str(m))
requests(message_id) = RequestState(url,method)
0
}
def onHeaderKey(p:Ptr[Parser],data:CString,len:Long):Int = {
val state = (!p._8).cast[Ptr[ConnectionState]]
val message_id = !state._1
val request = requests(message_id)
val k = bytesToString(data,len)
request.lastHeader = k
requests(message_id) = request
0
}
def onHeaderValue(p:Ptr[Parser],data:CString,len:Long):Int = {
val state = (!p._8).cast[Ptr[ConnectionState]]
val message_id = !state._1
val request = requests(message_id)
val v = bytesToString(data,len)
request.headerMap(request.lastHeader) = v
requests(message_id) = request
0
}
def onMessageComplete(p:Ptr[Parser]):Int = {
val state = (!p._8).cast[Ptr[ConnectionState]]
val message_id = !state._1
val tcpHandle = !state._2
val request = requests(message_id)
handleRequest(message_id,tcpHandle,request)
0
}
What Goes Here?
@extern
object CurlBindings {
// we model the curl handle as an opaque pointer of unknown size
type Curl = Ptr[Byte]
// allocate and initialize a curl handle
def easy_init():Curl = extern
// set an option
def easy_setopt(handle: Curl, option: CInt, parameter: Any): CInt = extern
// retrieve any of many named fields
def easy_getinfo(handle: Curl, info: CInt, parameter: Any): CInt = extern
// run the query
def easy_perform(easy_handle: Curl): CInt = extern
// clean up resources
def easy_cleanup(handle: Curl): Unit = extern
// flags and constants
val WRITEDATA = 10001
val URL = 10002
val PORT = 10003
// ...
}
val url:String = "https://www.example.com"
// initialize a curl handle
val curl = easy_init()
// convert the url to a zero-terminated CString
// (CString is an alias for Ptr[Byte])
// we need an implicit Zone allocator for this
val url_cstring = toCString(url)
// set the url on the handle
easy_setopt(curl, URL, url_cstring)
// set callbacks
easy_setopt(curl, HEADERCALLBACK, headerCB)
easy_setopt(curl, HEADERDATA, req_id_ptr.cast[Ptr[Byte]])
easy_setopt(curl, WRITECALLBACK, writeCB)
easy_setopt(curl, WRITEDATA, req_id_ptr.cast[Ptr[Byte]])
// run the query
val res = easy_perform(curl)
// finish
easy_cleanup(curl)
val url:String = "https://www.example.com"
// initialize a curl handle
val curl = easy_init()
// convert the url to a zero-terminated CString
// (CString is an alias for Ptr[Byte])
// we need an implicit Zone allocator for this
val url_cstring = toCString(url)
// set the url on the handle
easy_setopt(curl, URL, url_cstring)
// set callbacks
easy_setopt(curl, HEADERCALLBACK, headerCB)
easy_setopt(curl, HEADERDATA, req_id_ptr.cast[Ptr[Byte]])
easy_setopt(curl, WRITECALLBACK, writeCB)
easy_setopt(curl, WRITEDATA, req_id_ptr.cast[Ptr[Byte]])
// run the query
val res = easy_perform(curl)
// finish
easy_cleanup(curl)
type MultiCurl = Ptr[Byte]
def multi_init():MultiCurl = extern
def multi_add_handle(multi:MultiCurl, easy:Curl):Int = extern
def multi_setopt(multi:MultiCurl, option:CInt, parameter:Any):CInt = extern
def multi_assign(
multi:MultiCurl,
socket:Ptr[Byte],
socket_data:Ptr[Byte]):Int = extern
def multi_socket_action(
multi:MultiCurl,
socket:Ptr[Byte],
events:Int,
numhandles:Ptr[Int]):Int = extern
def multi_info_read(multi:MultiCurl, message:Ptr[Int]):Ptr[CurlMessage] = extern
def multi_cleanup(multi:MultiCurl):Int = extern
def multi_assign(
multi:MultiCurl,
socket:Ptr[Byte],
socket_data:Ptr[Byte]):Int = extern
def multi_socket_action(
multi:MultiCurl,
socket:Ptr[Byte],
events:Int,
numhandles:Ptr[Int]):Int = extern
type SocketCallback = Function5[Curl, // curl easy handle
Ptr[Byte], // socket
CurlAction,// socket state
Ptr[Byte], // custom CurlMulti data pointer
Ptr[Byte], // custom per-socket data pointer
CInt] // returns Int
type TimerCallback = Function3[MultiCurl, // curl multi handle
Long, // time to set next timer period
Ptr[Byte], // custom CurlMulti data pointer
CInt] // returns Int
type CurlAction = CInt
val POLL_NONE:CurlAction = 0
val POLL_IN:CurlAction = 1
val POLL_OUT:CurlAction = 2
val POLL_INOUT:CurlAction = 3
val POLL_REMOVE:CurlAction = 4
curl creates one or more sockets.
curl notifies us that it has created new sockets.
we create pollhandles for libuv and start polling
libuv sees that a socket is ready and invokes curl
curl performs the appropriate transfer
curl checks to see if the request is complete.
If the request is complete, curl completes the request
When there are no more requests, we're done
val req_promises:mutable.Map[Long,Promise[ResponseState]] = mutable.HashMap.empty
def get(url:CString, headers:Seq[String] = Seq.empty)
(implicit ec:ExecutionContext):Future[ResponseState] = {
req_count += 1
activeRequests += 1
val req_id = req_count
val promise = Promise[ResponseState]()
req_promises(req_id) = promise
CurlInternals.beginRequest(req_id, url, headers)
promise.future
}
def beginRequest(reqId:Long, url:CString, headers:Seq[String]):Unit = {
val curlHandle = easy_init()
val req_id_ptr = malloc(sizeof[Long]).cast[Ptr[Long]]
!req_id_ptr = reqId
requests(reqId) = ResponseState()
easy_setopt(curlHandle, URL, url)
easy_setopt(curlHandle, WRITECALLBACK, writeCB)
easy_setopt(curlHandle, WRITEDATA, req_id_ptr.cast[Ptr[Byte]])
easy_setopt(curlHandle, HEADERCALLBACK, headerCB)
easy_setopt(curlHandle, HEADERDATA, req_id_ptr.cast[Ptr[Byte]])
easy_setopt(curlHandle, PRIVATEDATA, req_id_ptr.cast[Ptr[Byte]])
multi_add_handle(multi, curlHandle)
}
// libuv provides helper functions to get the size of opaque structures
val timer_size = uv_handle_size(UV_TIMER_T)
// allocate a TimerHandle (really just a Ptr[Byte])
val timer_handle:TimerHandle = malloc(timer_size)
def set_timeout(curl:MultiCurl, timeout_ms:Long, data:Ptr[Byte]):Int = {
val time = if (timeout_ms < 1) {
// set timeout to minimum of 1, 0 causes problems
1
} else {
timeout_ms
}
// we have a single global timer - this can set or reset its period
uv_timer_start(timer_handle, timerCB, time, 0), "uv_timer_start")
// complete any requests that have finished
cleanup_requests()
0
}
// called by libuv when the timer fires
def on_timeout(handle:TimerHandle):Unit = {
val running_handles = stackalloc[Int]
multi_socket_action(multi,-1.cast[Ptr[Byte]],0,running_handles)
println(s"on_timer fired, ${!running_handles} sockets running")
}
val timerCB = CFunctionPtr.fromFunction1(on_timeout)
def state_change(curl:Curl, socket:Ptr[Byte], action:Int, data:Ptr[Byte],
socket_data:Ptr[Byte]):Int = {
val pollHandle = if (socket_data == null) {
val newHandle = malloc(uv_handle_size(UV_POLL_T)).cast[Ptr[Ptr[Byte]]
!newHandle = socket
uv_poll_init_socket(loop, newHandle, socket)
multi_assign(multi, socket, newHandle.cast[Ptr[Byte]])
newHandle
} else {
socket_data.cast[Ptr[Ptr[Byte]]]
}
val events = action match {
case POLL_NONE => None
case POLL_IN => Some(UV_READABLE)
case POLL_OUT => Some(UV_WRITABLE)
case POLL_INOUT => Some(UV_READABLE | UV_WRITABLE)
case POLL_REMOVE => None
}
// update or stop the poll handle as needed
events match {
case Some(ev) =>
uv_poll_start(pollHandle, ev, readyCB)
case None =>
uv_poll_stop(pollHandle)
cleanup_requests()
}
0
}
def on_socket_ready(pollHandle:PollHandle, status:Int, events:Int):Unit = {
println(s"ready_for_curl fired with status ${status} and events ${events}")
val socket = !(pollHandle.cast[Ptr[Ptr[Byte]]])
val actions = (events & 1) | (events & 2) // Whoa, nelly!
val running_handles = stackalloc[Int]
val result = multi_socket_action(multi, socket, actions, running_handles)
println("multi_socket_action",result)
}
val readyCB = CFunctionPtr.fromFunction3(on_socket_ready)
def cleanup_requests():Unit = {
val messages = stackalloc[Int]
val privateDataPtr= stackalloc[Ptr[Long]]
var message:Ptr[CurlMessage] = multi_info_read(multi,messages)
while (message != null) {
val handle:Curl = !message._2
easy_getinfo(handle, GET_PRIVATEDATA, privateDataPtr)
val privateData = !privateDataPtr
val reqId = !privateData
val reqData = requests.remove(reqId).get
Curl.complete_request(reqId,reqData)
message = multi_info_read(multi,messages)
}
}
def complete_request(reqId:Long, data:ResponseState):Unit = {
val reqId = !request._4
activeRequests -= 1
println(s"completing reqId ${reqId}")
val promise = Curl.req_promises.remove(reqId).get
promise.success(data)
}