Can someone please help me understand the following Ruby snippet?

I recently ran into a permgens memory leak that ran Sinatra on JRuby in Tomcat. The problem is with the Tilt library, which Sinatra uses to support various template options. Old code (which is not included here) caused a memory leak. The new code (below) does not do this, and in fact I see that hypergen GC is working now.

Ruby is supposed to describe itself, but I could not understand this code by reading it. There are nested class ratings. What for? Why is the method defined and then unrelated?

Why is there code that compiles a bunch of templates and supports them for reusing such a complex look?

Also: if there are any GitHub employees considering this issue, can you add some GitHub features that allow users to embed the question in a piece of code?

(This code was shot from https://github.com/rtomayko/tilt/blob/master/lib/tilt.rb )

def compile_template_method(locals) source, offset = precompiled(locals) offset += 5 method_name = "__tilt_#{Thread.current.object_id.abs}" Object.class_eval <<-RUBY, eval_file, line - offset #{extract_magic_comment source} TOPOBJECT.class_eval do def #{method_name}(locals) Thread.current[:tilt_vars] = [self, locals] class << self this, locals = Thread.current[:tilt_vars] this.instance_eval do #{source} end end end end RUBY unbind_compiled_method(method_name) end 
+4
source share
2 answers

There are nested class ratings. Why?

Instead of being an elegant self-describing code, as you would reasonably expect, this method looks like it is corrupted, fixed and production code fixed (so maybe we can forgive them a little).

So why two considerations? Before the second nested "real" method of the template method can be evaluated, the code that must be eval'd must have a prefix of the correct source encoding, which can be defined as a "magic comment" in the template file.

After correctly setting the string encoding, you can try to execute the real class class_eval. Another way of saying this could be "This is the source code that writes the source code that writes the source code"!

Presumably, this should fix the compatibility issues that may occur in Ruby 1.9, where the compiled template may contain character encoding (UTF-8), which is different from the encoding of the Tilt library source code itself (US-ASCII encoding), which will lead to incorrect string evaluation template (because the string encoding will already be set in the main code that invokes the template file).

Why is the method defined and then unrelated?

To clarify: in Ruby, unbound does not match undefined.

Unbound methods exist as objects of a free method like UnboundMethod that can be called, although they are no longer associated with a particular object. The unbound method no longer has a receiver.

To create an unrelated method, it is first bound (defined against) to the object. This is why the compiled template method is quickly removed from the top-level object, since only a temporary layout is required to create an unrelated method.

This method is used to use compiled templates that are limited to various instances of this class, without changing the root object or third-party class of the developer client in any visible or permanent way.

By disabling the compiled template method from a specific client code object, the compiled template method may subsequently bounce to new instances of this class of objects during future calls to templates that use objects of this type.

For example, given the following ERB pattern:

 <p>Hello <%= @name %></p> 

... and the following call code:

 scott = Person.new scott.name = "Scott" output = template.render(scott) => "<p>Hello Scott</p>" 

During this first rendering, the template is eval'd and compiled against an instance of the TOPOBJECT object. The compiled template method will be called as __tilt_2151955260 . This method will then not be used for reuse for all instances of type TOPOBJECT (which is simply Object or BasicObject depending on the version of Ruby) and therefore can be used for any type of client object.

The next time the template is rendered, the compiled template method is associated with the "baq" TOPOBJECT instance:

 baq = Person.new baq.name = "Baq" output = template.render(baq) 

Under the hood, when template.render(baq) , the method of an unrelated compiled template is bound to an instance of "baq" Person:

 __tilt_2151955260.bind(baq).call 

The absence of a call to class_eval each time leads to a significant increase in performance.

Why is code that compiles a bunch of templates and supports them for reusing such a complex look?

My assessment is that although the implementation of the code does look unnecessarily complex at first glance, these layers of indirection are often needed in the code, whose goal is to make the public API incredibly simple and sweet to be consumed by many thousands of other developers, even if it is at the expense of few developers that should support him.

The code complexity (double eval nesting) has also increased as a result of real problems arising from the API, which is consumed in many different locales and, therefore, many encodings from around the world.

Footnote : The Template class mentioned in the question has since been reorganized into a separate file github.com/rtomayko/tilt/blob/master/lib/tilt/template.rb

+5
source

Here is what I understand in this code:

Object.class_eval will execute the first block outside the current region and in the global region (eval_file and line offset are only there to print the correct line and file name when an error occurs), then the new method will be created in a dummy container (I suppose this is what TOPOBJECT), as soon as the method is compiled, it is unbound and stored somewhere else.

After that, the method will be bound to a new object containing the template variables, and it will start there, I do not remember the exact syntax, but here is the idea (where the method is an unrelated method):

 object = SomeClass.new object.param1 = "something" object.param2 = 43 method.apply(object) 

Regarding the complexity of the code, I already had to write such things (not as difficult as said) to make api higher than easy to use, that is, the price sometimes ^^

+1
source

Source: https://habr.com/ru/post/1344642/


All Articles