Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JRuby: Calling Java Code From A Rack App And Keeping It In Memory

I currently know Java and Ruby, but have never used JRuby. I want to use some RAM- and computation-intensive Java code inside a Rack (sinatra) web application. In particular, this Java code loads about 200MB of data into RAM, and provides methods for doing various calculations that use this in-memory data.

I know it is possible to call Java code from Ruby in JRuby, but in my case there is an additional requirement: This Java code would need to be loaded once, kept in memory, and kept available as a shared resource for the sinatra code (which is being triggered by multiple web requests) to call out to.

Questions

  1. Is a setup like this even possible?
  2. What would I need to do to accomplish it? I am not even sure if this is a JRuby question per se, or something that would need to be configured in the web server. I have experience with Passenger and Unicorn/nginx, but not with Java servers, so if this does involve configuration of a Java server such as Tomcat, any info about that would help.

I am really not sure where to even start looking, or if there is a better way to be approaching this problem, so any and all recommendations or relevant links are appreciated.

like image 468
Jonah Avatar asked May 26 '26 13:05

Jonah


1 Answers

Yes, a setup it's possibile ( see below about Deployment ) and to accomplish it I would suggest to use a Singleton

Singletons in Jruby

with reference to question: best/most elegant way to share objects between a stack of rack mounted apps/middlewares? I agree with Colin Surprenant's answer, namely singleton-as-module pattern which I prefer over using the singleton mixin

Example

I post here some test code you can use as a proof of concept:

JRuby sinatra side:

#file: sample_app.rb

require 'sinatra/base' 
require 'java' #https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby
java_import org.rondadev.samples.StatefulCalculator #import you java class here

# singleton-as-module loaded once, kept in memory
module App
  module Global extend self    
    def calc
      @calc ||= StatefulCalculator.new 
    end
  end
end
# you could call a method to load data in the statefull java object
App::Global.calc.turn_on  

class Sample < Sinatra::Base
  get '/' do
     "Welcome, calculator register:#{App::Global.calc.display}"
  end

  get '/add_one' do
    "added one to calculator register, new value:#{App::Global.calc.add(1)}"
  end
end

You can start it in tomcat with trinidad or simply with rackup config.ru but you need:

#file: config.ru
root = File.dirname(__FILE__)            # => "."
require File.join( root, 'sample_app' )  # => true
run Sample  # ..in sample_app.rb ..class Sample < Sinatra::Base

something about the Java Side:

package org.rondadev.samples;

public class StatefulCalculator {

        private StatelessCalculator calculator;

        double register = 0;

        public double add(double a) {       
            register = calculator.add(register, a); 
            return register;
        }

        public double display() {       
            return register;
        }

        public void clean() {
            register = 0;       
        }

        public void turnOff() {
            calculator = null;
            System.out.println("[StatefulCalculator] Good bye ! ");
        }

        public void turnOn() {
            calculator = new StatelessCalculator();
            System.out.println("[StatefulCalculator] Welcome !");
        }   
}

Please note that the register in here is only a double but in your real code you can have a big data structure in your real scenario

Deployment

You can deploy using Mongrel, Thin (experimental), Webrick (but who would do that?), and even Java-centric application containers like Glassfish, Tomcat, or JBoss. source: jruby deployments

with TorqueBox that is built on the JBoss Application Server. JBoss AS includes high-performance clustering, caching and messaging functionality.

trinidad is a RubyGem that allows you to run any Rack based applet wrap within an embedded Apache Tomcat container

Thread synchronization

Sinatra will use Mutex#synchronize method to place a lock on every request to avoid race conditions among threads. If your sinatra app is multithreaded and not thread safe, or any gems you use is not thread safe, you would want to do set :lock, true so that only one request is processed at a given time. .. Otherwise by default lock is false, which means the synchronize would yield to the block directly.

source: https://github.com/zhengjia/sinatra-explained/blob/master/app/tutorial_2/tutorial_2.md

like image 135
Franco Rondini Avatar answered Jun 01 '26 21:06

Franco Rondini



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!