zed.0xff.me
You MUST free memory you got from ALLOC_N & friends
It’s not documented anywhere, but you must call “xfree()
” (note ‘x’ there) on memory blocks you got from ALLOC_N & friends.
Ruby will not free that memory automatically during it’s usual GC process.
Guys from ruby-talk and rubyforge also noticed that.
sample code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
VALUE method_parse(VALUE self, VALUE text) { VALUE s; char *p = RSTRING(text)->ptr; in_buf_len = RSTRING(text)->len; // [skipped some code here] buf = ALLOC_N(char, bufsize); // protect buf from GC (theoretically) rb_iv_set(self,"@obj",Data_Wrap_Struct(rb_cData,NULL,NULL,buf)); // [skipped some hard work here] // make ruby string from our char[] data s = rb_str_new(buf,bufptr-buf); // cleanup rb_iv_set(self,"@obj",Qnil); xfree(buf); buf = NULL; bufsize = 0; return s; } |
Особенности кэширования классов в Rails
Задача
Вот есть например у нас некий самописный класс для взаимодействия со сторонним API:
1 2 3 4 5 6 7 |
class SomeAPIClient def initialize params = {} @host = params[:host] || 'some.api.host' @port = params[:port] || 12345 end ... end |
И кладем мы его в #{RAILS_ROOT}/lib/, чтобы его можно было легко юзать из классов рельсового приложения.
Дальше мы задумываемся о том, что из нашего Rails-приложения, мы будем всегда обращаться к одним и тем же хосту и порту апи-сервера. И надо где-то их вконфигурить. А приложение-то у нас opensource, и хостится на github-e, поэтому хост и порт (и возможно какие-то другие параметры типа api-key) у нас для разных юзеров приложения могут быть разными.
Отсюда вопрос: Как бы это сконфигурить так, чтобы и либу не особо корежить, и чтоб наиболее Rails-way было?..
Мне вот лично пришла в голову следующая мысль:
1 2 3 4 5 6 7 8 9 |
class SomeAPIClient cattr_accessor :default_port, :default_host def initialize params = {} @host = params[:host] || default_host || 'some.api.host' @port = params[:port] || default_port || 12345 end ... end |
Проблема
Всё вроде бы красиво, свои значения для default_host & default_port мы назначаем в #{RAILS_ROOT}/config/initializers/my_api_client.rb, который исключаем из git-репозитария путём помещения в .gitignore.
Но! Но не зря в топике написано про кэширование классов.. в development окружении первый запрос к API выполняется отлично, а вот уже на втором и всех следующих – вступает в действие config.cache_classes = false, рельсы перечитывают декларацию нашего апи, при этом удаляя старый класс из памяти через remove_const. Ну а initializers естественно не перечитывают.
В production всё ОК, потому что там обычно config.cache_classes = true.
Решение
Копаясь во внутренностях Rails обнаружил вот это:
( actionpack-2.3.2/lib/action_controller/dispatcher.rb )
1 2 3 4 5 6 7 8 9 10 11 |
# Add a preparation callback. Preparation callbacks are run before every # request in development mode, and before the first request in production # mode. # # An optional identifier may be supplied for the callback. If provided, # to_prepare may be called again with the same identifier to replace the # existing callback. Passing an identifier is a suggested practice if the # code adding a preparation block may be reloaded. def to_prepare(identifier = nil, &block) ... |
Таким образом, чтобы решение стало работоспособным надо обернуть нашу инициализацию в do_prepare:
( #{RAILS_ROOT}/config/initializers/my_api_client.rb )
1 2 3 4 |
ActionController::Dispatcher.to_prepare(:my_api) do SomeAPIClient.default_host = 'my.api.host' SomeAPIClient.default_port = '23456' end |
И в development mode этот код будет срабатывать перед обработкой каждого запроса, а в production – только перед обработкой самого первого запроса.
Done.
rspec views & params
Пытаюсь тестить спеку вьюшки(view). Причем внутри нее такая логика:
1 |
<%= verbose_search_results(user) if params[:verbose] %>
|
И я не нашел более вменяемого способа передать в нее params, чем следующий:
1 2 3 4 |
it "should do what I said" @controller.stub!(:params).and_return({:verbose => true}) render 'admins/users/list' end |
И тут есть белая сторона медали, а есть черная.
Белая – при этом стандартные параметры 'action'
и 'controller'
также нормально передаются, т.е. мой and_return
действует недеструктивно.
Черная – params
, переданный внутри такой спеки, становится невосприимчив к ключам-символам!
т.е. if params[:verbose]
работать НЕ будет (хотя на живых рельсах работает прекрасно).
А вот if params['verbose']
будет работать и там и там.
PS: может быть, конечно, есть более вменяемый способ передать params, но пока я его не обнаружил.
UPD: как оказалось rspec инициализирует параметры 'action'
и 'controller'
уже на готовом хэше params
, т.е. чтобы всё работало также как и на живых рельсах, надо делать так:
1 2 3 4 |
it "should do what I said" @controller.stub!(:params).and_return(HashWithIndifferentAccess.new({:verbose => true})) render 'admins/users/list' end |
т.е. явно инициализировать params
как HashWithIndifferentAccess
, а не просто хэш.
IMHO как-то это всё-таки кривовато, и должен быть более прямой и очевидный способ..