Restore Kernel.binding to a previous state

Discussion in 'Ruby' started by Hans Sjunnesson, Feb 22, 2007.

  1. Hey all,

    I'm implementing a simple queue system with which I'm evaling 'jobs',
    like so:

    code = lambda {eval(job)}
    code.call

    Where 'job' is a string containing ruby code.
    The problem I'm having is that I need these jobs to evaluate in a
    clean environment, uncluttered by the previous jobs. What I'd like is
    something along the lines of this:

    temp = Kernel.binding
    code = lambda {eval(job)}
    code.call
    Kernel.binding = temp

    Though that looks sort of dangerous.
    Another idea might be to be able to create a binding which is a deep
    copy of the current binding. I'm not sure if this has been done before.
     
    Hans Sjunnesson, Feb 22, 2007
    #1
    1. Advertising

  2. Hans Sjunnesson

    Guest

    On Fri, 23 Feb 2007, Hans Sjunnesson wrote:

    > Hey all,
    >
    > I'm implementing a simple queue system with which I'm evaling 'jobs',
    > like so:
    >
    > code = lambda {eval(job)}
    > code.call
    >
    > Where 'job' is a string containing ruby code.
    > The problem I'm having is that I need these jobs to evaluate in a
    > clean environment, uncluttered by the previous jobs. What I'd like is
    > something along the lines of this:
    >
    > temp = Kernel.binding
    > code = lambda {eval(job)}
    > code.call
    > Kernel.binding = temp
    >
    > Though that looks sort of dangerous.
    > Another idea might be to be able to create a binding which is a deep
    > copy of the current binding. I'm not sure if this has been done before.
    >


    http://www.linuxjournal.com/article/7922
    http://codeforpeople.com/lib/ruby/rq/

    if you're on windows you're out of luck though. in any case, all you need is
    a closure defined early on in your program. from there you evaluate the code.
    for example

    harp:~ > cat a.rb
    require 'thread'

    jq = SizedQueue.new 1
    rq = SizedQueue.new 1

    runner = Thread.new do
    loop do
    job = jq.pop

    rq.push Thread.new{
    $SAFE = 4
    begin
    eval job
    rescue Exception => e
    e
    end
    }.value
    end
    end

    x = 42

    jq.push 'x = 42.0; x'
    p rq.pop

    jq.push 'x = :forty_two; x'
    p rq.pop


    harp:~ > ruby a.rb
    42.0
    :forty_two


    basically you set of a thread server which, itself, is a thread. the top
    level thread is a closure of the initial context, so each new thread only has
    access to that clean environment. this does not reset the environment, but it
    does run each job in a clean one without poluting the current one.

    regards.

    -a
    --
    be kind whenever possible... it is always possible.
    - the dalai lama
     
    , Feb 22, 2007
    #2
    1. Advertising

  3. On Fri, Feb 23, 2007, Hans Sjunnesson wrote:
    > Where 'job' is a string containing ruby code.
    > The problem I'm having is that I need these jobs to evaluate in a
    > clean environment, uncluttered by the previous jobs. What I'd like is
    > something along the lines of this:
    >
    > temp = Kernel.binding
    > code = lambda {eval(job)}
    > code.call
    > Kernel.binding = temp


    I have (what I believe to be) a similar problem in some code I'm working
    on. It allows plugins written by the user to be executed, but each time
    it needs a clean environment. I'm taking advantage of the fact that you
    can pass eval a binding in which to execute. I have a simple binding
    factory which just creates new instances of itself and returns their
    binding:

    class BindingFactory
    def self::get_binding
    return self.new.send( :binding )
    end
    end

    eval( action, BindingFactory.get_binding )

    For my purposes, it works like a charm:

    >> ex1 = "a = :sym"

    => "a = :sym"
    >> ex2 = "puts a"

    => "puts a"
    >> eval ex1, BindingFactory.get_binding

    => :sym
    >> eval ex2, BindingFactory.get_binding

    NameError: undefined local variable or method `a' for #<BindingFactory:0x406f60f4>
    from (irb):18:in `send'
    from (irb):18:in `get_binding'
    from (irb):24
    from :0

    Is that helpful?

    Ben
     
    Ben Bleything, Feb 22, 2007
    #3
  4. On Feb 22, 5:01 pm, Ben Bleything <> wrote:
    > On Fri, Feb 23, 2007, Hans Sjunnesson wrote:
    > > Where 'job' is a string containing ruby code.
    > > The problem I'm having is that I need these jobs to evaluate in a
    > > clean environment, uncluttered by the previous jobs. What I'd like is
    > > something along the lines of this:

    >
    > > temp = Kernel.binding
    > > code = lambda {eval(job)}
    > > code.call
    > > Kernel.binding = temp

    >
    > I have (what I believe to be) a similar problem in some code I'm working
    > on. It allows plugins written by the user to be executed, but each time
    > it needs a clean environment. I'm taking advantage of the fact that you
    > can pass eval a binding in which to execute. I have a simple binding
    > factory which just creates new instances of itself and returns their
    > binding:
    >
    > class BindingFactory
    > def self::get_binding
    > return self.new.send( :binding )
    > end
    > end
    >
    > eval( action, BindingFactory.get_binding )
    >
    > For my purposes, it works like a charm:
    >
    > >> ex1 = "a = :sym"

    > => "a = :sym"
    > >> ex2 = "puts a"

    > => "puts a"
    > >> eval ex1, BindingFactory.get_binding

    > => :sym
    > >> eval ex2, BindingFactory.get_binding

    >
    > NameError: undefined local variable or method `a' for #<BindingFactory:0x406f60f4>
    > from (irb):18:in `send'
    > from (irb):18:in `get_binding'
    > from (irb):24
    > from :0
    >
    > Is that helpful?
    >
    > Ben


    Helpful - yes, in finding out that using eval might not be what I'm
    after.
    I thought, at first, that a binding housed the entirety of a running
    ruby process' context.

    I'm wanting to run ruby snippets like the below:

    require 'rake'
    default = task :default do
    # do stuff
    end
    default.invoke

    But for my problem, I might be better off using Kernel::system, and
    invoking ruby in an external process, even though I'd really like my
    little code-snippet to be able to access, for instance, the Logger
    I've set up in my queue-system.
    I might get away with that by modifying your BindingFactory to pass
    objects into the created binding as local variables. But stuff like
    rake modify the running context in more ways than setting local
    variables.
     
    Hans Sjunnesson, Feb 22, 2007
    #4
  5. Hans Sjunnesson

    Guest

    On Fri, 23 Feb 2007, Hans Sjunnesson wrote:

    >> Ben

    >
    > Helpful - yes, in finding out that using eval might not be what I'm
    > after.
    > I thought, at first, that a binding housed the entirety of a running
    > ruby process' context.
    >
    > I'm wanting to run ruby snippets like the below:
    >
    > require 'rake'
    > default = task :default do
    > # do stuff
    > end
    > default.invoke
    >
    > But for my problem, I might be better off using Kernel::system, and
    > invoking ruby in an external process, even though I'd really like my
    > little code-snippet to be able to access, for instance, the Logger
    > I've set up in my queue-system.
    > I might get away with that by modifying your BindingFactory to pass
    > objects into the created binding as local variables. But stuff like
    > rake modify the running context in more ways than setting local
    > variables.
    >
    >


    why don't you fork?

    -a
    --
    be kind whenever possible... it is always possible.
    - the dalai lama
     
    , Feb 22, 2007
    #5
  6. On Fri, Feb 23, 2007, Hans Sjunnesson wrote:
    > Helpful - yes, in finding out that using eval might not be what I'm
    > after. I thought, at first, that a binding housed the entirety of a
    > running ruby process' context.


    Sort of. It includes the context of wherever the binding was created.
    If you call binding in the top level of your script, you'll get all of
    that context that you can execute inside of.

    > I'm wanting to run ruby snippets like the below:
    >
    > require 'rake'
    > default = task :default do
    > # do stuff
    > end
    > default.invoke


    That's exactly what I'm doing :)

    > But for my problem, I might be better off using Kernel::system, and
    > invoking ruby in an external process, even though I'd really like my
    > little code-snippet to be able to access, for instance, the Logger
    > I've set up in my queue-system.


    You can pretty easily inject variables into your binding. As I looked
    at my code, I realized that I actually am not using the BindingFactory
    idea (it was a prototype that I ended up not sticking with).

    Instead, I have a Workspace class. It's got a method called #set_value
    that takes a name and value and sets an instance variable and adds an
    attr_reader inside the workspace's eigenclass.

    Then, the tasks that I've got (I call them Command in my code) know how
    to execute themselves inside a workspace, so I can just say

    command.execute( workspace )

    and it calls the proc inside that binding.

    I'm working on getting my code released (gotta get the company to
    agree), and until then I can't show too much, but I'm happy to talk
    about it more if it sounds similar enough to what you're doing to help.

    Ben
     
    Ben Bleything, Feb 22, 2007
    #6
  7. On Feb 22, 8:56 pm, wrote:
    > On Fri, 23 Feb 2007, Hans Sjunnesson wrote:
    > >> Ben

    >
    > > Helpful - yes, in finding out that using eval might not be what I'm
    > > after.
    > > I thought, at first, that a binding housed the entirety of a running
    > > ruby process' context.

    >
    > > I'm wanting to run ruby snippets like the below:

    >
    > > require 'rake'
    > > default = task :default do
    > > # do stuff
    > > end
    > > default.invoke

    >
    > > But for my problem, I might be better off using Kernel::system, and
    > > invoking ruby in an external process, even though I'd really like my
    > > little code-snippet to be able to access, for instance, the Logger
    > > I've set up in my queue-system.
    > > I might get away with that by modifying your BindingFactory to pass
    > > objects into the created binding as local variables. But stuff like
    > > rake modify the running context in more ways than setting local
    > > variables.

    >
    > why don't you fork?
    >
    > -a
    > --
    > be kind whenever possible... it is always possible.
    > - the dalai lama


    Actually, that works perfectly. I was under the impression that
    Kernel.fork wrapped native fork() and thus wasn't portable. But it
    works well for what I need.
     
    Hans Sjunnesson, Feb 22, 2007
    #7
  8. Hans Sjunnesson

    Guest

    On Fri, 23 Feb 2007, Hans Sjunnesson wrote:

    >>
    >> why don't you fork?
    >>
    >> -a
    >> --
    >> be kind whenever possible... it is always possible.
    >> - the dalai lama

    >
    > Actually, that works perfectly. I was under the impression that
    > Kernel.fork wrapped native fork() and thus wasn't portable. But it
    > works well for what I need.
    >


    definitely not portable. then again, thread aren't so much either unless one
    is __very__ careful with io and other blocking ops.

    regards.

    -a
    --
    be kind whenever possible... it is always possible.
    - the dalai lama
     
    , Feb 22, 2007
    #8
  9. > The problem I'm having is that I need these jobs to evaluate
    > in a clean environment, uncluttered by the previous jobs.


    You could use an anonymous module to de_inspect a string:

    a = "[1, 2, 3, 4]".de_inspect

    gegroet,
    Erik V. - http://www.erikveen.dds.nl/

    ----------------------------------------------------------------

    class String
    def de_inspect
    Thread.new do
    $SAFE = 4

    eval(self, Module.new.module_eval{binding})
    end.value
    end
    end

    class String
    def de_inspect_unsafe
    eval(self, Module.new.module_eval{binding})
    end
    end

    ----------------------------------------------------------------
     
    Erik Veenstra, Feb 24, 2007
    #9
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. yogesh
    Replies:
    3
    Views:
    610
    Kenny McCormack
    Feb 12, 2006
  2. Replies:
    3
    Views:
    187
  3. Lars Gierth
    Replies:
    6
    Views:
    244
    David Masover
    Mar 20, 2010
  4. Peter Otten
    Replies:
    0
    Views:
    176
    Peter Otten
    Aug 31, 2012
  5. Chris Withers
    Replies:
    0
    Views:
    216
    Chris Withers
    Aug 31, 2012
Loading...

Share This Page