Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have a standard and composable way for things which need deterministic cleanup #212

Open
slaymaker1907 opened this issue Feb 26, 2022 · 2 comments
Labels
libraries This is for issues related to Rhombus libraries

Comments

@slaymaker1907
Copy link

Instead of littering with-* style functions all over the place leading to lots of nesting, Rhombus should really have a composable way to handle deterministic cleanup (such as files, locks, etc.). There are two ways I think this could be done in a way that is keeping with Racket/Rhombus' ethos.:

  1. Follow the example of C#/Java and have using blocks where there is some closeable protocol attached to values.
  2. Have a "resource" block which works like parameterize and allows for other functions to add arbitrary code to the exit of the resource block (kind of like Golang's defer, but it would require explicit boundaries via the "resource" block and as a consequence would allow for other functions to add to this block).

Personally, I think the second approach would be somewhat unique but would provide the most amount of flexibility and ease of use. I particularly like that it doesn't require writing any classes to use. Here is a straw man example of what I think that would look like (with s-expr syntax); in this example, defer is a syntax construct which adds the statement as a lambda to the cleanup stack:

(define (make-lock)
  (make-semaphore 1))

(define (lock! lock-val)
  (semaphore-wait lock-val))

(define (unlock! lock-val)
  (semaphore-post lock-val))

(define (auto-lock! lock-val)
  (lock! lock-val)
  (defer (unlock! lock-val)))

(define (printlnf template . args)
  (let ([to-print (apply format template args)])
    (println to-print)))

(define (auto-open-db! db-name)
  (printlnf "Opened ~a" db-name)
  (defer (printlnf "Closed ~a" db-name))
  db-name)

;; Maybe first lock is logical while the latter
;; protects some internal data structures for the DB.
(define user-table-lock (make-lock))
(define db-integrity-lock (make-lock))

(define (add-user! username)
  (resource-boundary
   (auto-lock! user-table-lock)
   (auto-lock! db-integrity-lock)
   (define db (auto-open-db! "users"))
   ;; logic manipulating db to save user excluding locks.
   (printlnf "Added ~a to users" username)))
@slaymaker1907
Copy link
Author

In terms of behavior with continuations, I propose that we allow them, but that exceptions should always install a continuation barrier and that the cleanup functions should be run before going to the exception handler. A dynamic-wind solution is possible, but that seems suboptimal to me since that would rerun any initialization code before entering the resource boundary again if I am not mistaken.

@jackfirth jackfirth added the libraries This is for issues related to Rhombus libraries label Apr 1, 2022
@mflatt
Copy link
Member

mflatt commented Nov 21, 2024

We're currently trying an RAII-style Closeable interface and Closeable.let form. Ports are closeable, so we expect that Closeable.let will often be used with Port.Input.open_file like this:

block:
  Closeable.let in = Port.Input.open_file("x")
  do_work(in)
  // in is closed on return or escape from the block

As another example, instead of just returning a path, filesystem.make_temporary returns a Closeable to delete the temporary file or directory that it makes.

block:
  Closeable.let tmp = filesystem.make_temporary()
  do_work(tmp.path)
  // tmp.path is deleted on return or escape from the block

New classes can implement Closeable with a close method. The Closeable.let form is implemented with defn.sequence_macro, which is how it's able to move subsequent forms in the same definition context into a nested block.

The Closeable.let form does install a continuation barrier. Although a dynamic-wind expansion (i.e., try with ~initially and ~finally at the Rhombus level) seemed natural to me at first, the problem is that a name like in would not be rebound while jumping back into a continuation, even if the file is reopened.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libraries This is for issues related to Rhombus libraries
Projects
None yet
Development

No branches or pull requests

3 participants