chicken-users
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Chicken-users] Reading from TCP sockets and time outs


From: Kim
Subject: Re: [Chicken-users] Reading from TCP sockets and time outs
Date: Tue, 13 Apr 2004 10:08:01 -0700
User-agent: Mutt/1.5.6i

Hmmm... actually, after some brain storming with Category 5, here is the
current idea for an event handler API.

Provide a way to create an 'event monitor' (this would be similiar to 
the PLT concept of a 'waitable', but I would prefer using some other
term to avoid confusion.)  An event monitor would be something similiar
to a mutex in terms of having state and exclusivity.  Then also have
a "(event-case ...) construct to wait on events.  The rough idea 
would be to have something that looked like this:

  (define readable? (make-event-monitor an-input-port ))
  (define writable? (make-event-monitor an-output-port ))
  (define messages? (make-event-monitor a-mailbox ))
  (define got-mutex? (make-event-monitor a-mutex ))

  (event-case
    ((readable?)  (read-line (event-monitor-source readable?)))
    ((writable?)  (display "Output" (event-monitor-source writable?)))
    ((messages?)  (get-message-from a-mailbox) )
    ((got-mutex?) (do-protected-stuff ... ) )
    (num-or-time-spec (do-stuff-on-timeout ... ) )
  )

What (event-case ... ) would do is block until one of the events happened,
then execute the code for that case *after* locking the resource 
involved, and then unlocking the resource when exiting the (event-case).

So the 'event-monitor' API would look something like:

  (make-event-monitor [ port | mutex | thread | signal | ? ] )
    Creates a monitor that indicates a state change on the object/situation
    being monitored.  (Note that calling: 
      (define a (make-event-monitor (current-input)))
      (define b (make-event-monitor (current-input)))
    may result in 'a' and 'b' have the exact same value.  Having multiple
    monitors on a single event source may still boil down, 
    implementation-wise, to a single internal one.)

  (event-monitor-source a-monitor)
    Returns the instance of the object being monitored.  If the current
    thread does not have a lock/exclusive access to the event-monitor,
    this function might need to block -- not sure about that yet.

  (event-monitor-lock! a-monitor [timeout [thread]])
    Locks the event-monitor such that no other thread has access to
    the event-monitor.  No other thread will receive any events or 
    status updates about the event-monitor or the object being 
    monitored while the event-monitor is locked.
    
  (event-monitor-unlock! a-monitor) 
    Unlocks the event-monitor, allowing any other threads to receive
    event notifications.  (*Probably* do not need the mutex-unlock!
    syntax of having a condition variable.  That is fairly implicit
    in the idea of dealing with events.)

  (event-monitor-status a-monitor)
    Returns status information about the monitor -- locked, unlocked,
    events ready to be deal with, or an error state.  The error state
    could be a result of the event source, the object being monitored
    becoming invalid.  (i.e. port closed, thread termianted, etc.)

  (event-monitor? a-monitor)
    Returns #t if a-monitor is an event-monitor

  (event-monitor-ready? a-monitor)
    Returns #t iff there are events pending.

  (event-monitor-number-pending a-monitor)
    Returns the number of unhandled events.  For some objects being
    monitored, this may never be greater than 1.  Not entirely sure
    if this will be useful.

So, say:

  (define a-monitor (make-event-monitor an-input-port))

  ;; Block forever until a-monitor has an event.
  (event-monitor-lock! a-monitor) 
  (read (event-monitor-source a-monitor))
  (event-monitor-unlock! a-monitor) 

  ;; Check to see if there's anything to be read, but don't block
  (when (event-monitor-lock! a-monitor 0) 
    (read (event-monitor-source a-monitor))
    (event-monitor-unlock! a-monitor) )

  ;; Only wait so long for input
  (if (event-monitor-lock! a-monitor 0) 
    (begin
      (read (event-monitor-source a-monitor))
      (event-monitor-unlock! a-monitor) )
    (print "Timed out waiting for input!"))


So, on the face of it, not much more than some syntactic wrapping of 
mutexes around current objects, with the mutexes becoming available
or ready to be locked only when there is some pending event.

So, now to:

  (event-case [#t | #f ] ;; automatically lock
    ((a-monitor ... ) (expressions ...))
     ...
    (int-or-timespec (expressions...))
  )

  What event-case would do is wait until one of the event-monitors
  listed had events pending, or until the timeout (if present)
  expired.

  If the first argument to event-case is a boolean, this determines
  whether or not the event-case will automatically use an implicit
  (event-monitor-lock!) and (event-monitor-unlock!) around the
  executed expressions.  That is, the following two would be *almost*
  equivalent:

    (event-case #t
      ((a-monitor) 
         (do-stuff-with a-monitor)))

    (event-case #f
      ((a-monitor) 
         (event-monitor-lock! a-monitor) 
         (do-stuff-with a-monitor)
         (event-monitor-unlock! a-monitor)))

  The difference is in the latter there might (depending on the
  implementation) be a window of time where if the thread quantum
  expired just so, the (event-monitor-lock!) statement might
  block again if another thread locked the event-monitor between
  the event-case firing and the event-monitor-lock! locking
  the event monitor.  (Note that *is* a problem, race condition-wise
  because the thread would no longer be blocking on a timeout or
  any other event, but solely on acquiring a-monitor, except it 
  may acquire it only after another thread has processed the event,
  leaving the code with no event to process, and potentially missing
  any other events that have been happening.)

  The non-automatically locking version is needed if the expression
  called when an event happens is not going to return, or otherwise
  is going to manage the locking itself.  The automatically-locking
  version basically assures an atomicity between the event occuring
  and the event-case statement starting an expression, and the 
  expression having exclusive access to the monitored resource.


  In the case of:
   (define a-mutex (make-mutex))
   (define a-monitor (make-event-monitor a-mutex))
   (event-case ((a-monitor) (do-stuff)))
 
  is pretty much the same as:
   (define a-mutex (make-mutex))
   (mutex-lock! a-mutex) (do-stuff) (mutex-unlock! a-mutex)

  and from an implementation point of view, (event-monitor-lock! a-monitor)
  should, if a-monitor is created around a mutex, lock the underlying
  mutex as well as an atomic operation.  Though combining 'direct'
  operations with the monitored objects simultaneously with the
  event-monitor wrapped ones is probably not a good idea.  The whole
  point of developing an API/framework like this is to manage the 
  processing of events and thread interactions nicely.
   
A version of (event-case) that is more amiable to a dynamic list of
events to wait on would probably also be needed.  Something like
  (event-case-list [#t | #f] 
    (list (list (list a-monitor) (lambda () (do-stuff)))))

That might actually be the underlying implementation, of course.

This could collapse down into compatibility with PLT with something
like:

  (letrec ([a-read-monitor (make-event-monitor an-input-port)]
           [a-write-monitor (make-event-monitor an-output-port)])
    (event-case
      ((a-read-monitor)  (read (event-monitor-source a-read-monitor)))
      ((a-write-monitor)  (event-monitor-source a-write-monitor))
      (timeout-value #f)))

would be a reasonable implementation/equivalent of the PLT:
  (object-wait-multiple timeout-value an-input-port an-output-port)

Except that the (event-case) style lets the program decide exactly
how to process the data from the input-ports instead of having the
implementation read an arbitrary block of it and passing that back.

Most of this could probably be cobbled together ontop of SRFI-18 
at least in prototype form.  The only implementation tricky bit is
linking things like i/o readiness the mutex-style locking/unlocking.

So, comments anyone?

Other examples/thoughts:

 (event-case
   ((monitor-1 monitor-2) 
     (if (did-thread-lock? monitor-1)
       (do-stuff monitor-1) 
       (do-stuff monitor-2) )))


More robust:

  (event-case
    ((a-monitor)
      (case (event-monitor-status a-monitor)
        (('locked) (do-stuff-with a-monitor))
        (('error) (deal-with-error-on a-monitor)))))

-- 
Kim      address@hidden                 http://solluna.org/
"Nothing in the world is more dangerous than a sincere ignorance 
and conscientious stupidity" - Martin Luther King, Jr.




reply via email to

[Prev in Thread] Current Thread [Next in Thread]