cowboyd / handlebars.rb

Ruby Bindings for Handlebars.js
http://www.handlebarsjs.com
161 stars 67 forks source link

Simultaneous rendering of a template hangs the process #39

Open elado opened 9 years ago

elado commented 9 years ago

Looks like rending same compiled template many times simultaneously fires errors like V8::Error: Maximum call stack size exceeded or worse, gets the process stuck.

Is there a better practice for that? I don't want to lose the speed of compiled templates.

The rendering is running under Sidekiq process which gets stuck often.

require 'celluloid'

handlebars = Handlebars::Context.new
tpl = handlebars.compile(...)

# works fine
10.times { 20.times.map { |i| Celluloid::Future.new { puts "start #{i}"; sleep 0.1; puts "end #{i}" } }.each(&:value) }

# process will hang forever at some point
10.times { 20.times.map { |i| Celluloid::Future.new { puts "start #{i}"; tpl.call(data: data); puts "end #{i}" } }.each(&:value) }
elado commented 9 years ago

I could ask if Handlebars::Context.current has a value and recompile another template. Not optimal though.

jeremyhaile commented 9 years ago

+1 Just rolled this out and we're seeing Maximum call stack size exceeded. Using a shared context across multiple threads in puma/Rails.

jeremyhaile commented 9 years ago

@elado I was able to reproduce using your code, but I had to increase the number of "times" significantly to get it to reproduce the issue in a console.

For example, this code reproduces consistently (whereas yours was not):

require 'celluloid'
handlebars = Handlebars::Context.new
tpl = handlebars.compile('Test {{test}}')

# works fine
100.times { 20.times.map { |i| Celluloid::Future.new { puts "start #{i}"; sleep 0.1; puts "end #{i}" } }.each(&:value) }

# process will hang forever at some point
100.times { 20.times.map { |i| Celluloid::Future.new { puts "start #{i}"; tpl.call(test: 123); puts "end #{i}" } }.each(&:value) }
jeremyhaile commented 9 years ago

I thought that perhaps the handlebars context was not threadsafe. So I tried creating a new handlebars context for each thread. This also seems to completely lock up the process.

Here is a test case that reproduces for me. @elado can you confirm this reproduces for you too?

require 'celluloid'
100.times { 20.times.map { |i| Celluloid::Future.new { puts "start #{i}"; Handlebars::Context.new.compile("Test {{test}}").call(test: 123); puts "end #{i}" } }.each(&:value) }

Any known workarounds here? This is a critical production issue for us.

elado commented 9 years ago

@jeremyhaile https://github.com/cowboyd/handlebars.rb/blob/master/lib/handlebars/template.rb the context is being saved globally, I believe that's the issue.

I ended up migrating to ERB. With compiled ERB, performance was pretty good anyway.

jeremyhaile commented 9 years ago

@elado yeah, I switched to liquid.

omenking commented 6 years ago

I am experiencing this issue as well, I have my ruby gem called monster-queries where I used handlebars to intermix SQL queries together. It only became an evident problem when using puma a new version of Rails and Ruby. I might have to migrate away from handlebar unless I successful doing some deep digging on this issue.

I'm considering moving over to https://github.com/jamesotron/FlavourSaver or Liquid templates