[gambit-list] How best cancel a read/write-subu8vector or ##device-port-wait-for-input/output! from another thread gracefully? (for abort operation on active HTTP request etc.)

Marc Feeley feeley at iro.umontreal.ca
Mon Mar 18 11:32:07 EDT 2013


On 2013-03-18, at 9:50 AM, Mikael <mikael.rcv at gmail.com> wrote:

> Dear Marc and list,
> 
> A universal HTTP client is taking form, it does connect & logics & IO in a singlethread setting now properly.
> 
> Would be happy to be able to cancel blocking read/write-subu8vector or ##device-port-wait-for-input/output! calls gracefully as to be able to abort HTTP connections directly and gracefully like this:
> 
> (define r (http-request url))
> 
> ((r 'abort!)) ; From another thread than the one performing the HTTP request logics & IO.
>               ; (Even if the abort is done at the level of HTTP request, due to the invalid
>               ; protocol state the abort produces the connection needs to be taken down.)
> 
> 
> The ways I can see this could be done by would be using
> 
> 	• close-port
> 
> 	• thread-interrupt! or ##thread-int! ??
> 
> 	• A low timeout on any read and write IO primitive invoked, like, 0.05 seconds, and check for the cancel condition on each timeout and if not reached, invoke the IO primitive again.
> 
> 	• thread-terminate! (The HTTP request may be running in the user's thread, so this would require redesign)
> 
> It'd be most preferable if this cancel could be done in such a way that
> 
>  1) The abort action's effects are limited within the scope of the code for the respective HTTP request or connection so user code executed after the HTTP request code is not affected by it.
>      This includes taking care automatically of any "mutex abandoned" exceptions or alike that would otherwise come as surprises later.
> 
>  2) The thread performing the abort action does not get blocked in turn. (Something like that, the write-subu8vector would block a mutex in the IO system that would make the close-port call used to interrupt it, block until timeout.)
> 
> 
> To help 1), I can ensure that a thread-interrupt! or alike call is not done after that the HTTP request code is done and the thread is back in user code, through making a round of locking some mutex at the end of the HTTP request and guaranteeing a thread-interrupt! never is made after that mutex was locked.
> 
> It's OK if the interrupt appears to the read/write-subu8vector caller as an IO failure i.e. #f return value.
> 
> 
> While I believe I could come up with a solution, I'd be very happy to learn to know your take on it as to do this the best way?
> 
> Thanks!
> Mikael
> 

I was going to suggest

(define (abort-io thread port)
  ;; force the thread to abort IO on the port
  (input-port-timeout-set! port -inf.0)
  (thread-interrupt! thread void))

which works by waking up the thread so that it will attempt the IO operation again after setting the timeout of the port so that it will raise a timeout.  If the thread was done with the IO when abort-io is called, the thread will not observe a timeout or be terminated forcibly.

Unfortunately, thread-interrupt! has race condition issues.  In particular, if the thread has already terminated (normally or not), the thread-interrupt! function will raise an exception.  So this is better:

(define (abort-io thread port)

  ;; force the thread to abort IO on the port

  (input-port-timeout-set! port -inf.0)

  (with-exception-catcher
   (lambda (e)
     (if (inactive-thread-exception? e)
         (void)
         (raise e)))
   (lambda ()
     (thread-interrupt! thread void))))

While it is logically correct, it is not perfect because thread-interrupt! currently has a bug when a started but not-executed-yet thread is interrupted.  But this may not affect your code and I plan to fix the bug soon.

Marc



More information about the Gambit-list mailing list