[racket] instantiating multiple sandboxes with gui's

From: Greg Hendershott (greghendershott at gmail.com)
Date: Sat Apr 5 09:25:16 EDT 2014

Thank you Robby. I've spent some time thinking about your response,
trying to make use of it, and even looking at some
mred/private/common/wx code.

First, I don't grok the need for an extra thread. This is for an Emacs
mode REPL. It's quite similar to Racket's xrepl, which doesn't use an
extra thread. Although I could create one if necessary, I think it
would complicate things when I have a more basic issue regarding
`main-eventspace`, custodians, and how/when racket/gui/base gets
loaded:

As an Emacs mode (unlike DrRacket, which is a GUI that needs
racket/gui/base itself), I don't want to statically `require`
racket/gui/base, because I don't want it loaded until/unless it's
actually needed by a user program. (Otherwise, e.g. on OS X, running a
non-GUI program, you would get a weird windowless menu bar, and it
steals the focus from Emacs.)  So the way that racket/gui/base _first_
gets required, is via the dynamic-require of the first user program
that happens to require it. As a result the `main-eventspace` state in
mred/private/wx/common/queue.rkt (IIUC) is associated with
`user-cust`, not `orig-cust`. As a result, custodian-shutdown-all
blows that away. And I don't know how to recreate it correctly.
Symptom: Subsequent runs of a user program like `(require plot) (plot
__)`, result in windows that sit there un-drawn and/or unresponsive to
mouse input.


Seems like it could be solved on either "end":

1. Somehow try to manage the load process so that the first-ever load
of racket/gui/base gets its stuff on orig-cust but the user's stuff on
user-cust.

2. Somehow re-initialize the main eventspace sufficiently.

Although 2 seems more plausible and less kludgy, I can't work out how
to do it either way.


Some code snippets that might be more understandable than prose:

;; (or/c #f path?) -> any
(define (run path-str)
  (define-values (mod load-dir) (path-string->mod-path&load-dir path-str))

  ;; Use custodian to release resources.
  ;;
  ;; The problem here is that this seems to be "too thorough" -- it
  ;; boinks `main-eventspace` in mred/private/queue.rkt. Why?  How
  ;; racket/gui/base first got loaded was at end of this function,
  ;; in the dynamic-require of whatever user module first happened to
  ;; use racket/gui/base. (We don't want to load racket/gui/base
  ;; in the Emacs mode until/unless it's actually needed). Therefore
  ;; `main-eventspace` is created on `user-cust`, and gets blown away
  ;; here. Ideally, instead `main-eventspace` would be managed by
  ;; `orig-cust`, and everything else about the user program would be
  ;; on `user-cust`. But how to do that??
  (custodian-shutdown-all user-cust)
  ;; New custodian
  (set! user-cust (make-custodian orig-cust))
  (current-custodian user-cust)

  ;; Save the current namespace. Save whether it uses racket/gui/base.
  (define orig-ns (current-namespace))
  (define had-gui? (module-declared? 'racket/gui/base))
  ;; Fresh, clear racket/base namespace.
  (current-namespace (make-base-namespace))

  (when had-gui?
    (displayln "had-gui? #t")
    ;; If racket/gui/base module was instantiated in orig-ns, attach
    ;; and require it into the new namespace, and _before_ requiring
    ;; the new module. Avoids "cannot instantiate `racket/gui/base' a
    ;; second time in the same process" problem.
    (namespace-attach-module orig-ns 'racket/gui/base)
    (namespace-require 'racket/gui/base)

    ;; Also need to create an eventspace.  FIXME: Not working with
    ;; plot.rkt example. custodian-shutdown-all seems to have borked
    ;; the original main eventspace state, and our newly-created one
    ;; ain't good enough.
    (define (gdr sym)
      (dynamic-require 'racket/gui/base sym))
    (define current-eventspace (gdr 'current-eventspace))
    (define make-eventspace    (gdr 'make-eventspace))
    (current-eventspace (make-eventspace)))

  ;; Load the user module, if any
  (when mod
    (parameterize ([current-load-relative-directory load-dir])
      (dynamic-require mod 0)
      (current-namespace (module->namespace mod))))
  (current-prompt-read (make-prompt-read mod)))

Example GUI user program:

#lang racket
(require plot math)
(plot-new-window? #t)
(plot (function sin (- pi) pi #:label "y = sin(x)"))



On Thu, Apr 3, 2014 at 5:49 PM, Robby Findler
<robby at eecs.northwestern.edu> wrote:
> I think the main missing thing is that the thread you're requiriing the
> user's program on should be under the control of the custodian. In other
> words, you'll want to create a new eventspace (under the new custodian) and
> queue a callback over to it to require the user's program.
>
> Robby
>
>
> On Thu, Apr 3, 2014 at 4:26 PM, Greg Hendershott <greghendershott at gmail.com>
> wrote:
>>
>> So the background for Spencer's question was a problem with my Emacs
>> racket-mode when using racket/gui/base.
>>
>> There's a long-ish bug report comment thread. If you want to read it,
>> at all, you might want to skip to the last few comments, here:
>>
>>
>> https://github.com/greghendershott/racket-mode/issues/26#issuecomment-39494285
>>
>> Or the TL;DR: I probably don't need racket/sandbox at all. I probably
>> just need a new namespace and a custodian -- in order to do a
>> DrRacket-style "reset the REPL to the source file".
>>
>> Matthew and Robby I know you're both incredibly busy, but if either of
>> you had a chance to look at this code, I'd be grateful. Although part
>> of me hopes you'll say "perfect!", the older/wiser part of me hopes
>> you'll point out X Y and Z problems I don't yet realize I have. It's
>> just these 30 lines of code here:
>>
>>
>> https://github.com/greghendershott/racket-mode/blob/experimental/sandbox.rkt#L30-L66
>>
>> On Sun, Mar 23, 2014 at 8:35 PM, Robby Findler
>> <robby at eecs.northwestern.edu> wrote:
>> > It is safe, however, to share the racket/gui/base that you get with the
>> > one
>> > in the sandbox.
>> >
>> > Robby
>> >
>> >
>> > On Sun, Mar 23, 2014 at 7:25 PM, Matthew Flatt <mflatt at cs.utah.edu>
>> > wrote:
>> >>
>> >> Yes, this is a limitation of `racket/gui/base`. On initialization, the
>> >> library must register in various non-composable ways with an underlying
>> >> GUI toolkit (callbacks, Objective-C classes, Win32 classes, etc.), and
>> >> so `racket/gui/base` cannot be instantiated multiple times.
>> >>
>> >> At Sun, 23 Mar 2014 20:12:43 -0400, Spencer Florence wrote:
>> >> > I'm attempting to launch multiple evaluators which require
>> >> > `racket/gui`,
>> >> > however I get the error:
>> >> >
>> >> > cannot instantiate `racket/gui/base` a second time in the same
>> >> > process
>> >> >
>> >> > Is `racket/gui/base` maintaining some kind of state thats escaping
>> >> > the
>> >> > sandbox?
>> >> >
>> >> >
>> >> >
>> >> >
>> >> >
>> >> > Example of the problem:
>> >> >
>> >> > #lang racket/base
>> >> > (require racket/sandbox)
>> >> >
>> >> > (call-with-trusted-sandbox-configuration
>> >> >  (lambda ()
>> >> >    (define (make)
>> >> >      (make-evaluator 'racket/base
>> >> >                      #:requires '(racket/gui/base)))
>> >> >
>> >> >    (make)
>> >> >    (make)))
>> >> > ____________________
>> >> >   Racket Users list:
>> >> >   http://lists.racket-lang.org/users
>> >> ____________________
>> >>   Racket Users list:
>> >>   http://lists.racket-lang.org/users
>> >
>> >
>> >
>> > ____________________
>> >   Racket Users list:
>> >   http://lists.racket-lang.org/users
>> >
>
>

Posted on the users mailing list.