jackfirth / racket-disposable

An experimental Racket library providing an abstraction for values associated with external resources that allows automatic resource pooling, per-thread virtual construction, and monadic composition
Apache License 2.0
7 stars 0 forks source link

Ready-event allocation #114

Open jackfirth opened 6 years ago

jackfirth commented 6 years ago

Attempting to make a disposable with tcp-accept and port closing is undesirable, because waiting for a connection could take an arbitrarily long time and disposable allocation is called with breaks disabled. Instead, such a disposable should work by creating an event that is ready for synchronization when a connection is established, and guarantees no connection was created if it's not chosen for synchronization. A helper for constructing these sorts of disposables would help people avoid doing things the wrong way. Something like this:

(define my-disposable
  (disposable-evt <evt-alloc>
                  <evt-result-dealloc>))

Which is equivalent to this...

(define (wrapper e)
  (<evt-result-dealloc> (sync/timeout #f e)))
(define my-disposable
  (disposable-apply sync (disposable <evt-alloc> wrapper)))

Thanks for the idea @BourgondAries!

jackfirth commented 6 years ago

Uh oh: disposable-apply calls the function during allocation of the wrapper disposable, so it's also called with breaks enabled. Maybe the underlying disposable protocol needs a way for a function to say which parts of allocation require that breaking is enabled? Or maybe all allocation and deallocation should be expressed in terms of events internally.

jackfirth commented 6 years ago

Update: breaks can be re-enabled inside a region where breaks are disabled (just like parameterizations) so this ought to be possible using helpers like this:

(define (unbreakable-evt evt)
  (guard-evt (λ () (parameterize-break #f (sync evt)))))

(define (breakable-evt evt)
  (guard-evt (λ () (parameterize-break #t (sync evt)))))

Then, with disposable allocation and deallocation defined in terms of events, a disposable that needs to wait arbitrarily could use these helpers to specify when breaking is or isn't allowed.

But this might be a bad idea. I'll have to find out more about how events and breaks interact.

jackfirth commented 6 years ago

Turns out the way disposable-apply spawns child threads causes breaks to not be propagated regardless of how the joined disposables handle breaks. Had a discussion in the Racket slack channel which resulted in this:

(define (pair-evt a b)
  (define left (handle-evt a (cons 'left _)))
  (define right (handle-evt b (cons 'right _)))
  (replace-evt (choice-evt left right)
    (match-lambda
      [(cons 'left v) (handle-evt b (cons v _))]
      [(cons 'right v) (handle-evt a (cons _ v))])))

Something built on top of this ought to be capable of replacing the current delay/thread backed implementation of disposable-apply. This would require no child threads be spawned, making break forwarding and exception handling simpler.