domingo, 5 de junio de 2011

Shikashi v0.5.0 released

Shikashi is a sandbox for ruby that handles all ruby method calls executed in the interpreter to allow or deny these calls depending on the receiver object, the method name, the source file from where the call was originated

For more info about the project, visit the project page

You can install the gem by doing

gem install shikashi

New Enhancements

The primary focus of this latest release was improve the performance of execution of ruby code in the sandbox but maintaining the purity of ruby (i.e. without implementing anything with C or similar)

Code packets

In the previous version of shikashi (0.4.0), to execute code in the sandbox, you do:
Shikashi::Sandbox.run("1+1", Shikashi::Privileges.allow_method(:+))
This, internally, implies:
  • Parse the code
  • Process the syntax tree (evalhook)
  • Emulate the transformed tree (partialruby)
  • Execute the emulation code using eval...
This was a great performance problem in cases where you need to execute the same piece of ruby code many times in the sandbox (e.g. sandboxed web code executed for each incoming request)
To solve this, the concept of "code packet" was implemented; a "code packet" is the ultimate product of the above mentioned processing chain ready to be executed as many times as necessary without reprocess the code and tree. Example:
packet = Shikashi::Sandbox.packet(code)
# after that, you can run the "packet" as many times as you want without doing all
# parsing, tree processing and emulation stuff internally
packet.run(privileges)
packet.run(privileges)
packet.run(privileges)
packet.run(privileges)

The performance difference is of the order of 20X, you can check by doing the following benchmark:

require "rubygems"
require "shikashi"
require "benchmark"

code = "class X
def foo(n)
end
end
X.new.foo(1000)
"

s = Shikashi::Sandbox.new

Benchmark.bm(7) do |x|

x.report("normal") {
1000.times do
s.run(code, Shikashi::Privileges.allow_method(:new))
end
}

x.report("packet") {
packet = s.packet(code, Shikashi::Privileges.allow_method(:new))
1000.times do
packet.run
end
}

end

In my laptop, the result of this benchmark is:

            user     system      total        real
normal 5.870000 0.540000 6.410000 ( 6.436331)
packet 0.290000 0.020000 0.310000 ( 0.315351)

The repeated execution of code using packets is 20 times faster than normal

Refactor of evalhook internals

Evalhook is the support gem that provides the ability to hook events on the execution of ruby code. Evalhook implemenations initially was not desgined for performance, then it's possible to refactor the implementation to increase the performance by supressing unnecessary calls and other optimizations.

require "rubygems"
require "shikashi"
require "benchmark"

s = Shikashi::Sandbox.new

class NilClass
def foo
end
end

Benchmark.bm(7) do |x|

x.report {

code = "
500000.times {
nil.foo
}
"
s.run code, Shikashi::Privileges.allow_method(:times).allow_method(:foo)
}

end



The result without the optimizations:
            user     system      total        real
36.140000 1.420000 37.560000 ( 37.672708)


The result with the optimizations:
            user     system      total        real
27.460000 1.130000 28.590000 ( 28.658031)


The difference is not so significant because most of the time is used by privileges checking in both cases. Similar bechmarks executed against evalhook directly throws greater differences

Links