Может кто-нибудь, пожалуйста, помогите мне понять следующий фрагмент Ruby? - PullRequest
4 голосов
/ 21 марта 2011

Недавно я столкнулся с утечкой памяти permgen при запуске Sinatra на JRuby в Tomcat. Проблема была связана с библиотекой Tilt, которую Sinatra использует для поддержки различных шаблонных опций. Старый код (который здесь не включен) генерировал утечку памяти. Новый код (ниже) не работает, и на самом деле я вижу, что Permgen GC теперь работает.

Предполагается, что Ruby самоописывает себя, но я не мог понять этот код, прочитав его. Есть вложенные классовые уловки. Зачем? Почему метод определяется, а затем не связан?

Почему код, который компилирует кучу шаблонов и сохраняет их для повторного использования, выглядит так сложно?

Также: если кто-то из сотрудников GitHub рассматривает этот вопрос, не могли бы вы добавить в GitHub некоторые функции, позволяющие пользователям вставлять вопрос в фрагмент кода?

(Этот код был снят с 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  

Ответы [ 2 ]

5 голосов
/ 28 марта 2011

Есть вложенные классовые уловки.Почему?

Вместо того, чтобы быть элегантным кодом с самоописанием, как можно было бы разумно ожидать, этот метод выглядит так, как будто он основан на боевом, исправленном и исправленном рабочем коде (поэтому, возможно, мы можем проститьих немного).

Так почему же два зла?Прежде чем второй вложенный «реальный» код метода шаблона может быть eval'd, код, который должен быть eval'd, должен иметь префикс с правильной исходной кодировкой, которая могла быть определена как «магический комментарий» в файле шаблона.

Как только строковое кодирование установлено правильно, может быть предпринят реальный class_eval.Другой способ сказать, что это может быть «Это исходный код, который пишет исходный код, который пишет исходный код»! *

Предположительно, это исправляет проблемы совместимости, которые могут возникнуть в Ruby 1.9, где компилируемый шаблон может содержатькодировка символов (UTF-8), которая отличается от кодировки самого исходного кода библиотеки Tilt (кодировка US-ASCII), что приведет к неправильной оценке строк шаблона (поскольку кодировка строки уже будет установлена ​​в коде хоста, которыйвызывает файл шаблона).

Почему метод определяется и затем не связан?

Чтобы уточнить: в Ruby unbound не являетсятак же, как undefined .

Существуют несвязанные методы как свободные объекты методов типа UnboundMethod , которые можно вызывать, хотя они больше не связаны с конкретным объектом.У несвязанного метода больше нет получателя.

Чтобы создать несвязанный метод, он сначала должен быть привязан (определен к) к объекту.Вот почему метод скомпилированного шаблона быстро удаляется из объекта верхнего уровня, потому что это было только временное расположение, необходимое для генерации несвязанного метода.

Этот метод используется, чтобы сделать возможным использование скомпилированных шаблонов, которыеограничены различными экземплярами данного класса, без изменения корневого объекта или клиентского класса стороннего разработчика любым видимым или постоянным способом.

Отключая метод скомпилированного шаблона от конкретного объекта кода клиента, скомпилированныйМетод шаблона может быть позднее восстановлен для новых экземпляров класса этого объекта во время будущих вызовов шаблонов, использующих объекты этого типа.

Например, с учетом следующего шаблона ERB:

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

... и следующий код вызова:

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

Во время этого первого рендеринга шаблон eval'd и скомпилирован с экземпляром объекта TOPOBJECT.Метод скомпилированного шаблона будет называться как «__tilt_2151955260».Затем этот метод не привязывается для повторного использования со всеми экземплярами типа TOPOBJECT (который в зависимости от версии Ruby является просто Object или BasicObject) и, следовательно, может использоваться против любого типа объекта клиента.

В следующий разшаблон отображается, метод скомпилированного шаблона связан с экземпляром baq для TOPOBJECT:

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

Под капотом, когда вызывается template.render(baq), несвязанный метод скомпилированного шаблона привязывается к 'baq 'instance of Person:

__tilt_2151955260.bind(baq).call

Отсутствие необходимости каждый раз вызывать class_eval приводит к значительному увеличению производительности.

Почему код компилирует кучу шаблонов идержит их для повторного использования настолько сложный внешний вид?

Моя оценка состоит в том, что хотя реализация кода действительно выглядит излишне сложной на первый взгляд, эти уровни косвенности часто необходимы в коде платформыцелью которого является сделать публичный API невероятно простым и приятным для использованиямногие тысячи других разработчиков, даже если это за счет тех немногих разработчиков, которые должны поддерживать его.

Сложность кода (двойное вложение) также возросла в результате реальных проблем, возникающих из-за API, который используется во многих различных локалях и, следовательно, во многих кодировках со всего мира.

Сноска : Класс Template, упомянутый в вопросе, с тех пор был преобразован в отдельный файл github.com / rtomayko / tilt / blob / master / lib / tilt / template.rb

1 голос
/ 23 марта 2011

Вот что я понимаю в этом коде:

Object.class_eval выполнит первый блок за пределами текущей области видимости и в глобальной области видимости (eval_file и line-offset находятся там только для печати правой строкии имя файла, если возникнет ошибка), тогда в фиктивном контейнере будет создан новый метод (я полагаю, что это то, чем является TOPOBJECT), после того, как метод скомпилирован, он не связан и сохранен где-то еще.

После этогоМетод будет присоединен к новому объекту, содержащему переменные шаблона, и будет запущен там, я не помню точный синтаксис, но вот идея (где метод является несвязанным методом):

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

Что касаетсясложность кода мне уже приходилось писать такие вещи (не такие сложные, как говорилось), чтобы сделать API выше простым в использовании, то есть цена иногда ^^

...