[MLton] getrusage and times?

Wesley W. Terpstra wesley@terpstra.ca
Fri, 15 Jul 2005 03:42:37 +0200

Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Thu, Jul 14, 2005 at 04:13:00PM -0700, Stephen Weeks wrote:
> The call to getrusage comes from the GC keeping track of how much time
> it consumes: see {start,stop}Timing in gc.c.

Ahh, I saw that code, but assumed the if statements had turned it off.

> Unfortunately there is a bug, which has been there at least since
> 20041109, that causes the information to always be collected.  I've
> checked in a (one line) fix.

That would explain why those ifs didn't stop it...
Thank you very much. 

If I may ask, how did you diagnose the problem?

> These must be happening because you are calling acceptNB

Yeah, that's the idea of state threads.

You context switch when you hit a blocking operation, which means that all
blocking operations internally do a nonblocking operation and context switch
away on EAGAIN until the descriptor becomes ready.

I'm using an nifty abstraction that allows things like:
  once (socket s HASINPUT Orelse TIMEOUT delay) 
  print "hello world\n"
which will context switch out of the current process if the condition is 
not true, and will be returned to the ready queue once it is true.

Of course, for normal operation, all the blocking methods do something 
like that internally:

    fun wrapIn f s x = (
      once (socket (s x) HASINPUT Orelse socket (s x) ISBROKEN) ();
      case f x of
          NONE => wrapIn f s x
        | SOME x => x)

    fun recvVec  x = wrapIn recvVecNB  #1 x
    fun accept x = wrapIn acceptNB (fn s => s) x

My library wraps and replaces Socket for the application. It also provides a
few (contention-free) synchronization mechanisms and the scheduler (epoll 
currently). Actually, I am quite happy with how elegant the API turned out.

I attached an example of an annoying echo server, in case you care. =)

> socket.sml calls withNonBlock, which wraps O_NONBLOCK around the call.

Yep, I saw all that.

What I was trying to say was that I think doing this around an accept() 
call is no big deal anyways. The cost of fcntl is insignificant compared
to the costs incurred by accept() (allocating and binding a socket). 
Therefore, even though those are extra system calls, I think that they 
are unimportant compared to the costs they always accompany.

What bothered me was the getrusage()s which were surrounding normal
recv/send IO operations.

> If you wanted to cut down on those, I imagine you could lift out the
> setting to happen once before all the accepts, rather than each time. 
> Although you may have to step outside the basis library to do this.

I'd rather stick to using Socket.acceptNB; it works fine.
Importing 'epoll' is enough system calls for one library.
(Well, I import 'identity' atm too :-)

Wesley W. Terpstra

Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="test.sml"

open State
open Thread
open Timeout
open IoEvent
open Scheduler

type port = (INetSock.inet, Socket.passive Socket.stream) Socket.sock

fun msg s = Word8VectorSlice.full (Word8Vector.tabulate 
  (String.size s, Word8.fromInt o Char.ord o s <\ String.sub))

val delay = Time.fromSeconds 5
val port : port = INetSock.TCP.socket ()
val () = Socket.Ctl.setREUSEADDR (port, true)
val () = Socket.bind (port, INetSock.any 12467)
val () = Socket.listen (port, 100)

fun worker s () = 
    val _ = Socket.sendVec (s, msg "hello and welcome!\n");
    val got = Word8VectorSlice.full (Socket.recvVec (s, 400))
    if Word8VectorSlice.length got = 0 then Socket.close s else
    (Socket.sendVec (s, got); worker s ())

fun welcome () = 
    val (s, _) = Socket.accept port
    spawn (worker s);
    welcome ()
val () = spawn welcome
val () = main ()