warbler icon indicating copy to clipboard operation
warbler copied to clipboard

Warbler doesn't appear to pull in all necessary dependencies from Jarfile

Open Taywee opened this issue 7 years ago • 8 comments

In this case, I'm using the h2 database. When I use jbundler directly, it appears to work, but after a run through bundler, it finds the h2 engine, but not the dependent h2-mvstore.

Full example:

$ pwd            
/tmp/warbletest

Gemfile

gem "warbler", "~> 2.0"
gem 'jbundler', '~> 0.9'

Jarfile

jar 'com.h2database:h2', '1.4.197'

bin/main.rb

#!/usr/bin/env ruby

begin
  require 'jbundler'
rescue LoadError
  # This will fail when wrapped in a jar
end

Java::org.h2.Driver

connection = Java::java.sql.DriverManager.get_connection('jdbc:h2:/tmp/h2-warbler-test.db')
connection.auto_commit = false

statement = connection.create_statement
statement.execute_update 'CREATE TABLE IF NOT EXISTS foo (a INTEGER, b CLOB, c BOOLEAN)'
statement.execute_update 'INSERT INTO foo (a, b, c) VALUES (5, \'test\', TRUE)'
connection.commit

Execution

$ ./bin/main.rb 

$ warble 
No executable matching config.jar_name found, using bin/main.rb
rm -f warbletest.jar
Creating warbletest.jar

$ java -jar warbletest.jar 
Exception in thread "Thread-4" java.lang.NoClassDefFoundError: org/h2/mvstore/MVMap$2
	at org.h2.mvstore.MVMap.entrySet(MVMap.java:827)
	at org.h2.store.LobStorageMap.removeAllForTable(LobStorageMap.java:301)
	at org.h2.engine.Database.removeOrphanedLobs(Database.java:1381)
	at org.h2.engine.Database.close(Database.java:1300)
	at org.h2.engine.DatabaseCloser.run(DatabaseCloser.java:63)
Caused by: java.lang.ClassNotFoundException: org.h2.mvstore.MVMap$2
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 5 more

Note that it still happens the same even when I explicitly put the h2-mvstore dependency in the Jarfile. I haven't been able to find a workaround so far.

Taywee avatar Jul 13 '18 23:07 Taywee

might be an edgy case, you could try "forcing" JRuby's class-loader to be the context-loader during boot. require 'jruby'; JRuby.set_context_class_loader if works Warbler could/should? maybe set it for you

kares avatar Jul 16 '18 14:07 kares

Where should that live? I put that in my main.rb at the top, and it still fails with the same error.

I've noticed that when I explicitly put h2-mvstore in the jarfile, then it does actually end up in ./META-INF/lib/h2-mvstore-1.4.197.jar in the jar, but otherwise it is absent. Even when it's in there, the class isn't found, so the jar doesn't appear to end up properly referenced.

Taywee avatar Jul 16 '18 18:07 Taywee

I found some interesting things here. If I explicitly load in the classes in my ruby, it works:

...
require 'java'

Java::org.h2.Driver
Java::org.h2.mvstore.const_get('MVMap$2')
Java::org.h2.mvstore.const_get('MVMap$2$1')
Java::org.h2.mvstore.WriteBuffer

connection = Java::java.sql.DriverManager.get_connection('jdbc:h2:/tmp/h2-warbler-test.db')
...

This works correctly. If I don't reference those classes in the main.rb file, I get the NoClassDefFoundError. It works the same whether h2-mvstore is referenced in the Jarfile or not. I don't actually know a whole lot about Java. Might this be an issue with how h2 itself is packaged? Any idea why jbundler works around this, but warbler doesn't, and how to possibly make warbler handle this case?

Taywee avatar Jul 16 '18 20:07 Taywee

It does look like warbler's JarMain always uses URLClassLoader, where the JRuby Main class uses it's own class loader. I assume that might have something to do with it.

Taywee avatar Jul 16 '18 21:07 Taywee

so you are talking about an executable jar which does have a different classloading semantic then running an application in war-file.

this file worked for me:

JRuby.set_context_class_loader

connection =
Java::java.sql.DriverManager.get_connection('jdbc:h2:/tmp/h2-warbler-test.db')
connection.auto_commit = false

statement = connection.create_statement
statement.execute_update 'CREATE TABLE IF NOT EXISTS foo (a INTEGER, b
CLOB, c BOOLEAN)'
statement.execute_update 'INSERT INTO foo (a, b, c) VALUES (5, \'test\',
TRUE)'
connection.commit

note that I do not load ny Driver class before and important is the set_context_class_loader to be the very first statement.

mkristian avatar Jul 17 '18 06:07 mkristian

Doesn't work for me, even if I do the set_context_class_loader as the very first statement. This is my main.rb file here:

JRuby.set_context_class_loader

connection = Java::java.sql.DriverManager.get_connection('jdbc:h2:/tmp/h2-warbler-test.db')
connection.auto_commit = false

statement = connection.create_statement
statement.execute_update 'CREATE TABLE IF NOT EXISTS foo (a INTEGER, b CLOB, c BOOLEAN)'
statement.execute_update 'INSERT INTO foo (a, b, c) VALUES (5, \'test\', TRUE)'
connection.commit

And when I run it:

$ warble
No executable matching config.jar_name found, using bin/main.rb
rm -f warbletest.jar
Creating warbletest.jar

$ java -jar warbletest.jar
Exception in thread "Thread-3" java.lang.NoClassDefFoundError: org/h2/mvstore/MVMap$2
	at org.h2.mvstore.MVMap.entrySet(MVMap.java:827)
	at org.h2.store.LobStorageMap.removeAllForTable(LobStorageMap.java:301)
	at org.h2.engine.Database.removeOrphanedLobs(Database.java:1381)
	at org.h2.engine.Database.close(Database.java:1300)
	at org.h2.engine.DatabaseCloser.run(DatabaseCloser.java:63)
Caused by: java.lang.ClassNotFoundException: org.h2.mvstore.MVMap$2
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 5 more

Taywee avatar Jul 17 '18 15:07 Taywee

that is strange as I did see your problem and I looked at the classloader-hierarchy and this setup just worked for me.

then I see only your way to hard load these three classes

mkristian avatar Jul 17 '18 16:07 mkristian

Maybe it's a JVM difference, I'm using:

$ java -version
openjdk version "1.8.0_171"
OpenJDK Runtime Environment (IcedTea 3.8.0) (Gentoo icedtea-3.8.0)
OpenJDK 64-Bit Server VM (build 25.171-b11, mixed mode)

Honestly, having to explicitly specify classes I need is an acceptable workaround for me here. It would be good to get to the root of what's causing the issue (whether in Warbler or H2), but despite needing some ugly-looking hacks to get it working, it's not holding me back.

Taywee avatar Jul 17 '18 16:07 Taywee