Repository navigation
calling clojure from Java Azure Function ( ExceptionInInitializerError) #55
Description
I had a previous issue on the same topic described in this thread. I found a solution using maven-assembly-plugin however I encountered the below issue.
Sample code.
1)the following classes
i) import clojure.java.api.Clojure; , ii) import clojure.lang.IFn;2)compile your clojure into an uberjar then import it into the java
code.
I have opted for the 2nd option as it's more straight forward.
Here is the clojure code
(ns com.test.app.service
(:gen-class
:name com.test.app.service
:main false
:methods [^{:static true} [returned [int] int]]))
(defn returned
[number]
(* 2 number))
(defn -returned
[number]
(returned number))
Here is the Java code.
package com.s.profile;
import java.util.*;
import com.microsoft.azure.serverless.functions.annotation.*;
import com.microsoft.azure.serverless.functions.*;
import com.test.app.service;
/**
* Azure Functions with HTTP Trigger.
*/
public class Function {
/**
* This function listens at endpoint "/api/hello". Two ways to invoke it using "curl" command in bash:
* 1. curl -d "HTTP Body" {your host}/api/hello
* 2. curl {your host}/api/hello?name=HTTP%20Query
*/
@FunctionName("hello")
public HttpResponseMessage<String> hello(
@HttpTrigger(name = "req", methods = {"get", "post"}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request.");
// Parse query parameter
String query = request.getQueryParameters().get("name");
String name = request.getBody().orElse(query);
if (name == null) {
return request.createResponse(400, "Please pass a name on the query string or in the request body");
} else {
service.returned(4);
context.getLogger().info("process data" );
return request.createResponse(200, "Hellos, " + name );
}
}
}
And here is the full stacktrace
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
java.lang.ExceptionInInitializerError
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at clojure.lang.Namespace.<init>(Namespace.java:34)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at clojure.lang.Namespace.findOrCreate(Namespace.java:176)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at clojure.lang.Var.internPrivate(Var.java:151)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at com.test.app.service.<clinit>(Unknown Source)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at com.s.profile.Function.hello(Function.java:42)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at java.lang.reflect.Method.invoke(Method.java:483)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at com.microsoft.azure.webjobs.script.broker.JavaMethodInvokeInfo.invoke(JavaMethodInvokeInfo.java:22)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at com.microsoft.azure.webjobs.script.broker.JavaMethodExecutor.execute(JavaMethodExecutor.java:59)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at com.microsoft.azure.webjobs.script.broker.JavaFunctionBroker.invokeMethod(JavaFunctionBroker.java:40)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at com.microsoft.azure.webjobs.script.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:33)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at com.microsoft.azure.webjobs.script.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:10)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at com.microsoft.azure.webjobs.script.handler.MessageHandler.handle(MessageHandler.java:43)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at com.microsoft.azure.webjobs.script.JavaWorkerClient$StreamingMessagePeer.lambda$onNext$0(JavaWorkerClient.java:84)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at com.microsoft.azure.webjobs.script.JavaWorkerClient$StreamingMessagePeer$$Lambda$10/293453555.run(Unknown Source)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1407)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:902)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1689)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1644)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
Caused by: java.io.FileNotFoundException: Could not locate clojure/core__init.class or clojure/core.clj on classpath:
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at clojure.lang.RT.load(RT.java:443)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at clojure.lang.RT.load(RT.java:411)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at clojure.lang.RT.doInit(RT.java:447)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
at clojure.lang.RT.<clinit>(RT.java:329)
info: Worker.Java.917bfefd-b67a-49b2-b77b-6848c52c3a98[0]
The information I have on that issue is below as per the documentation.
Signals that an unexpected exception has occurred in a static initializer. An ExceptionInInitializerError is thrown to indicate that an exception occurred during evaluation of a static initializer or the initializer for a static variable.
Which static method had a problem the one in my clojure code? Seems unlikely because when I run the code from a more simple "hello world" Java example from the main function it returns fine, which leads me to believe my compiled clojure jar file is not the problem. So where is it stemming from? Are there rules behind how the azure functions work internally that limit what can get called?
Activity
JunyiYi commentedon Feb 21, 2018
The solution should be putting all related libraries into "/lib" folder as specified here. Please notice that this changeset has not been published to Azure yet.
kyale-mwendwa commentedon Feb 28, 2018
This doesn't seem to work either.
Below is my pom.xml
`
4.0.0
If i try remove "maven-assembly-plugin" then it reverts to the error "java.lang.NoClassDefFoundError". The strange part is if i add the following line of code
String t = String.valueOf(mpesa.returned(2));context.getLogger().info("process data"+ t);And run the command
mvn clean packageI see the following
Which means that it is able to see the jar file at this point.
So what is going wrong when running the service?
esuomi commentedon Mar 5, 2018
I spent some time today digging into this as I also need to run Clojure on Azure Functions and it seems the issue is caused by
clojure.lang.RTnot respecting the loading rules ofjava.net.URLClassLoader. This is documented rather poorly in some ancient tomes such as here: https://dev.clojure.org/jira/browse/CLJ-260As can be seen from this project's code, it is currently hardcoded to use URLClassLoader through its
com.microsoft.azure.webjobs.script.reflect.DefaultClassLoaderProviderclass which is directly instantiated incom.microsoft.azure.webjobs.script.JavaWorkerClient#26and finally used incom.microsoft.azure.webjobs.script.broker.JavaMethodExecutor.java#L54-L57Apparently due to this mismatch Clojure natively has
clojure.lang.DynamicClassLoaderwhich I guess is what should be used instead of just plainURLClassLoader. This guess is based on this StackOverflow answer.Going on with the assumption that this is caused by wrong kind of ClassLoader it also explains why resetting the ClassLoader (as seen here) by putting
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());into code doesn't work either. And in pure-Clojure environment this wouldn't work anyways as there would then need to be a Java class just for resetting classloader which isn't as straightforward as it should...So, assuming this is a ClassLoader issue we'd need to be able to override the ClassLoader used by Azure Functions so that the
JavaWorkerClientwould use user providedClassLoaderProviderinstead of the hardcoded one using some reasonable mechanism. Then we'd only need to writeClojureAwareClassLoaderProviderand initialiseclojure.lang.DynamicClassLoaderfrom there and that's it.Maybe.
JunyiYi commentedon Apr 4, 2018
Esko Suomi (@esuomi) , there is a chicken-and-egg problem of customized class loader: you don't have any chance to specify how to load your class before your class is loaded.
esuomi commentedon Apr 5, 2018
J. Yi (@JunyiYi) Well yes, which is why one generally should avoid customizing it and let the platform provide eg. context or system classloader and trust they do the right thing... :) ClassLoaders (and to extent bytecode generation) have always been a mess, especially after the advent of JVM as platform for alternative languages.
krukow commentedon Apr 19, 2018
J. Yi (@JunyiYi) or Esko Suomi (@esuomi) - is there any progress on this or any news about prioritizing a solution?
esuomi commentedon Apr 20, 2018
Karl Krukow (@krukow) I'd be interested as well - I'm a user, not MS employee :) If this was my project, I'd remove the whole classloader thing and instead replace it with JVM startup script which uses Java classpath wildcard feature to pick the dependencies from specific subdir which would circumvent the entire problem but this is just me - there probably is some other reason why this isn't done in that way but instead the classloader based solution has been chosen, for example maybe the ultimate goal is to use shared JVMs for multiple functions independent from each other. Of course being a relatively old Java user I remember the days of OSGi and other "ClassWorld" systems and I'm not excited about that at all, but now I'm just rambling... :)
krukow commentedon Apr 20, 2018
Do we know what AWS is doing?
esuomi commentedon Apr 21, 2018
I'd imagine they're doing pretty much what I said as that is the sensible thing to do but that's just an educated guess. I haven't really used AWS Lambda in such extent that I could claim to know for sure.
JunyiYi commentedon May 17, 2018
Esko Suomi (@esuomi) Karl Krukow (@krukow) , we do not use JVM arguments because the worker doesn't know anything about the functions (not even the jar path) when it is started, after sometime the server will tell the worker to load some specific jar file and run the functions in there.
Java worker will not create any new processes due to the suggestion from the host side. We can create new threads, but we are not supposed to create processes. So the host can easily terminate and restart everything by just killing the worker process.
pragnagopa commentedon Aug 5, 2019
Kyale Mwendwa (@kyale-mwendwa) - we have made several changes / improvements on we load dependencies for java functions. Can you please let us know if this still reproes?
shreyas-gopalakrishna commentedon Aug 10, 2022
Closing this issue due to inactivity. Java worker has been improved with several changes. Please reopen if this issue persists and we can help you 😄