leiningen

ghaskins 2024-08-25T11:39:00.170289Z

Hi All, Looking for suggestions on how to debug the following problem: I have a Leiningen project that ultimately builds an application into an uberjar that deploys inside a docker container as a Kubernetes microservice. It has been stable for years now. Recently, the local build on OSX started exhibiting weird behavior where I can’t run the uberjar itself (e.g., via ‘java -jar path/to/uber.jar’) if I built it on my Mac. It just silently exits on both OSX and within the Linux docker env. It works fine if I use the uberjar as built from the Dockerfile. It also works fine if I ‘lein run’ it. I ran it through jdwp suspend=y, and it seems that the problem is a java.lang.NoClassDefFoundError when running the uberjar built by OSX directly, so I am guessing this is somehow related to the classpath. Both the Linux and OSX environments are using JDK22 by default, though I have also tried jenv to build the uberjar with other JDK variants (21, 20, 13, etc) on the Mac to try to isolate to no avail. It would be much appreciated if anyone could give an idea of what might cause this or what to look for.

ghaskins 2024-08-25T11:40:37.400799Z

In general, the fact that the uberjar built from the Dockerfile continues to run fine I suppose points to OSX vs Linux vs something wrong with my lein project.clj, but I am at a loss

ghaskins 2024-08-25T11:46:42.947289Z

As another data point, I can build other microservices that use the same pattern and the uberjar runs fine. So there is something about this particular app

dpsutton 2024-08-25T12:18:30.751349Z

What is the class that is not found?

ghaskins 2024-08-25T12:25:13.258279Z

I cant really tell. I’ve tried this with jdb

ghaskins 2024-08-25T12:27:38.548469Z

ghaskins 2024-08-25T12:28:35.230669Z

I think this is telling me that the problem is with

manetu.protobuf.attributes.meta__init.load 

ghaskins 2024-08-25T12:28:36.725239Z

?

ghaskins 2024-08-25T12:33:42.334249Z

and frame 8 has

main[8] locals
Method arguments:
name = "manetu.protobuf.attributes.meta$write_Literal_Lang"
Local variables:

ghaskins 2024-08-25T12:34:09.540389Z

so as best I can tell its related to trying to load that class…but also as best I can tell, that class is available in the classpath

ghaskins 2024-08-25T12:42:54.932589Z

If I run java -verbose i can see some activity in this area but no clear error

Alex Miller (Clojure team) 2024-08-25T13:11:26.241539Z

It’s not uncommon for protobuf classes to be included in multiple dependency jars and thus for you to end up with an arbitrary (perhaps old) copy from a dependency in your uberjar. Might also check if the those are compiled with the same jvm version

ghaskins 2024-08-25T13:53:28.189359Z

In this case they are .cljc files generated by my protojure tool. They don’t seem to be included in any obviously wrong way (old, multiple times, etc). It seems to purely be how the cljc gets incorporated into the uberjar

ghaskins 2024-08-25T13:53:50.438799Z

It’s very odd

ghaskins 2024-08-25T14:07:39.064399Z

The generated code looks like this

;;;----------------------------------------------------------------------------------
;;; Generated by protoc-gen-clojure.  DO NOT EDIT
;;;
;;; Message Implementation of package manetu.protobuf.attributes.meta
;;;----------------------------------------------------------------------------------
(ns manetu.protobuf.attributes.meta
  (:require [protojure.protobuf.protocol :as pb]
            [protojure.protobuf.serdes.core :as serdes.core]
            [protojure.protobuf.serdes.complex :as serdes.complex]
            [protojure.protobuf.serdes.utils :refer [tag-map]]
            [protojure.protobuf.serdes.stream :as serdes.stream]
            [com.google.protobuf :as com.google.protobuf]
            [manetu.protobuf :as manetu.protobuf]
            [clojure.set :as set]
            [clojure.spec.alpha :as s]))
;;----------------------------------------------------------------------------------
;;----------------------------------------------------------------------------------
;; Enumerations
;;----------------------------------------------------------------------------------
;;----------------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Subject-Type
;-----------------------------------------------------------------------------
(def Subject-Type-default :type-attribute)

(def Subject-Type-val2label {
  0 :type-attribute
  1 :type-metadata
  2 :type-tag})

(def Subject-Type-label2val (set/map-invert Subject-Type-val2label))

(defn cis->Subject-Type [is]
  (let [val (serdes.core/cis->Enum is)]
    (get Subject-Type-val2label val val)))

(defn- get-Subject-Type [value]
  {:pre [(or (int? value) (contains? Subject-Type-label2val value))]}
  (get Subject-Type-label2val value value))

(defn write-Subject-Type
  ([tag value os] (write-Subject-Type tag {:optimize false} value os))
  ([tag options value os]
   (serdes.core/write-Enum tag options (get-Subject-Type value) os)))

;-----------------------------------------------------------------------------
; Literal-DataType
;-----------------------------------------------------------------------------
(def Literal-DataType-default :datatype-integer)

(def Literal-DataType-val2label {
  3 :datatype-integer
  4 :datatype-double
  8 :datatype-datetime
  7 :datatype-time
  5 :datatype-float
  6 :datatype-date
  1 :datatype-bool
  0 :datatype-string
  2 :datatype-decimal})

(def Literal-DataType-label2val (set/map-invert Literal-DataType-val2label))

(defn cis->Literal-DataType [is]
  (let [val (serdes.core/cis->Enum is)]
    (get Literal-DataType-val2label val val)))

(defn- get-Literal-DataType [value]
  {:pre [(or (int? value) (contains? Literal-DataType-label2val value))]}
  (get Literal-DataType-label2val value value))

(defn write-Literal-DataType
  ([tag value os] (write-Literal-DataType tag {:optimize false} value os))
  ([tag options value os]
   (serdes.core/write-Enum tag options (get-Literal-DataType value) os)))

;-----------------------------------------------------------------------------
; Literal-Lang
;-----------------------------------------------------------------------------
(def Literal-Lang-default :lang-none)

(def Literal-Lang-val2label {
  0 :lang-none})

(def Literal-Lang-label2val (set/map-invert Literal-Lang-val2label))

(defn cis->Literal-Lang [is]
  (let [val (serdes.core/cis->Enum is)]
    (get Literal-Lang-val2label val val)))

(defn- get-Literal-Lang [value]
  {:pre [(or (int? value) (contains? Literal-Lang-label2val value))]}
  (get Literal-Lang-label2val value value))

(defn write-Literal-Lang
  ([tag value os] (write-Literal-Lang tag {:optimize false} value os))
  ([tag options value os]
   (serdes.core/write-Enum tag options (get-Literal-Lang value) os)))

ghaskins 2024-08-25T14:08:54.515619Z

and you can see in the -verbose output that it was making its way through some of the functions with a cis -> get -> write pattern, but then for some reason on the Literal_Lang type it seems to balk at write-Literal-Lang after successfully calling the first two (cis, get)

ghaskins 2024-08-25T14:09:41.010929Z

and like I mentioned, it works fine in uberjars built by linux, or lein run.. I dont get it

ghaskins 2024-08-25T14:14:34.814809Z

I unzipped the resulting uberjar. The files in question are

(default ) ✔ ~/sandbox/git/mcp-attribute-service/target/uberjar/manetu/protobuf/attributes/meta [master|⚑ 13]
10:13 $ ls
ChangeRecord-KafkaState-record$reify__23964.class	ChangeRecord-record$reify__24011.class			Literal-record$reify__24202.class
ChangeRecord-KafkaState-record$reify__23966.class	ChangeRecord-record$reify__24013.class			Literal-record.class
ChangeRecord-KafkaState-record$reify__23968.class	ChangeRecord-record$reify__24015.class			Subject-Ref-record$reify__23804.class
ChangeRecord-KafkaState-record.class			ChangeRecord-record$reify__24017.class			Subject-Ref-record$reify__23806.class
ChangeRecord-Triple-record$reify__24153.class		ChangeRecord-record$reify__24019.class			Subject-Ref-record$reify__23808.class
ChangeRecord-Triple-record$reify__24155.class		ChangeRecord-record$reify__24021.class			Subject-Ref-record.class
ChangeRecord-Triple-record$reify__24157.class		ChangeRecord-record.class				Subject-record$reify__23876.class
ChangeRecord-Triple-record$reify__24159.class		CompletionResult-record$reify__24113.class		Subject-record$reify__23878.class
ChangeRecord-Triple-record$reify__24161.class		CompletionResult-record$reify__24115.class		Subject-record$reify__23880.class
ChangeRecord-Triple-record.class			CompletionResult-record$reify__24117.class		Subject-record$reify__23882.class
ChangeRecord-TxnState-record$reify__24077.class		CompletionResult-record.class				Subject-record$reify__23884.class
ChangeRecord-TxnState-record$reify__24079.class		EncryptionEnvelope-record$reify__23925.class		Subject-record$reify__23886.class
ChangeRecord-TxnState-record.class			EncryptionEnvelope-record$reify__23927.class		Subject-record.class
ChangeRecord-record$reify__24003.class			EncryptionEnvelope-record$reify__23929.class		Value-record$reify__23843.class
ChangeRecord-record$reify__24005.class			EncryptionEnvelope-record.class				Value-record.class
ChangeRecord-record$reify__24007.class			Literal-record$reify__24198.class
ChangeRecord-record$reify__24009.class			Literal-record$reify__24200.class

ghaskins 2024-08-25T14:14:54.344569Z

i assume these are the interesting bits

ghaskins 2024-08-25T14:14:55.967259Z

$ ls Literal-record*
Literal-record$reify__24198.class	Literal-record$reify__24200.class	Literal-record$reify__24202.class	Literal-record.class

ghaskins 2024-08-25T14:15:14.800759Z

is there something I could look for to ensure the functions are present?

ghaskins 2024-08-25T14:15:39.195099Z

I tried playing around with disassembler and just plain binary grep, but it wasnt very fruitful

ghaskins 2024-08-25T14:17:06.384019Z

oh wait, its one directory up that is pertinent

ghaskins 2024-08-25T14:17:54.431349Z

$ ls -la | grep Literal
-rw-r--r--    1 ghaskins  staff   2887 Aug 25 08:48 meta$cis__GT_Literal$fn__24225.class
-rw-r--r--    1 ghaskins  staff   1362 Aug 25 08:48 meta$cis__GT_Literal.class
-rw-r--r--    1 ghaskins  staff   1221 Aug 25 08:48 meta$cis__GT_Literal_DataType.class
-rw-r--r--    1 ghaskins  staff   1213 Aug 25 08:48 meta$cis__GT_Literal_Lang.class
-rw-r--r--    1 ghaskins  staff   1742 Aug 25 08:48 meta$convert_Literal_lang.class
-rw-r--r--    1 ghaskins  staff   1742 Aug 25 08:48 meta$convert_Literal_type.class
-rw-r--r--    1 ghaskins  staff   1141 Aug 25 08:48 meta$ecis__GT_Literal.class
-rw-r--r--    1 ghaskins  staff    819 Aug 25 08:48 meta$fn__24194$__GT_Literal_record__24215.class
-rw-r--r--    1 ghaskins  staff   1442 Aug 25 08:48 meta$fn__24194$map__GT_Literal_record__24217.class
-rw-r--r--    1 ghaskins  staff   2772 Aug 25 08:48 meta$get_Literal_DataType.class
-rw-r--r--    1 ghaskins  staff   2767 Aug 25 08:48 meta$get_Literal_Lang.class
-rw-r--r--    1 ghaskins  staff   3404 Aug 25 08:48 meta$new_Literal.class
-rw-r--r--    1 ghaskins  staff   1095 Aug 25 08:48 meta$pb__GT_Literal.class
-rw-r--r--    1 ghaskins  staff   1958 Aug 25 08:48 meta$write_Literal_DataType.class
-rw-r--r--    1 ghaskins  staff   2734 Aug 25 08:48 meta$write_Literal_Lang.class
-rw-r--r--    1 ghaskins  staff   2738 Aug 25 08:48 meta$write_Literal_type.class

ghaskins 2024-08-25T14:18:11.399529Z

$ javap meta\$write_Literal_Lang.class
Compiled from "meta.cljc"
public final class manetu.protobuf.attributes.meta$write_Literal_lang extends clojure.lang.AFunction {
  public static final clojure.lang.Var const__0;
  public static final clojure.lang.Var const__2;
  public static final clojure.lang.Var const__3;
  public static final clojure.lang.Keyword const__4;
  public static final clojure.lang.Var const__5;
  public static final java.lang.Object const__6;
  public static final clojure.lang.AFn const__8;
  public static final clojure.lang.Keyword const__9;
  public static final clojure.lang.Var const__10;
  public static final java.lang.Object const__11;
  public static final clojure.lang.AFn const__12;
  public manetu.protobuf.attributes.meta$write_Literal_lang();
  public static java.lang.Object invokeStatic(java.lang.Object, java.lang.Object);
  public java.lang.Object invoke(java.lang.Object, java.lang.Object);
  public static {};
}

ghaskins 2024-08-25T14:18:35.080619Z

it seems like everything is there, so I dont know where the exception is coming from

souenzzo 2024-08-26T19:33:37.581959Z

a debug tip: run java -cp path/to/uber.jar clojure.main it will open a clojure REPL then you do (require 'your-app.main) and try to call (your-app.main/-main "with" "argv") it will move your problem from "silent exit" to a clear exception

👍 1
ghaskins 2024-08-26T20:05:12.084879Z

That was something I didn’t know, so thank you for that! Unfortunately its only telling me info I already know

$ java -cp target/uberjar/app.jar clojure.main
Clojure 1.11.4
user=> (require 'manetu.attribute-service.main)
Warning: environ value /Users/ghaskins/.jenv/versions/22 for key :java-home has been overwritten with /usr/local/Cellar/openjdk/22.0.2/libexec/openjdk.jdk/Contents/Home
WARNING: import already refers to: #'clojure.core/import in namespace: manetu.utils.time.google, being replaced by: #'manetu.utils.time.google/import
WARNING: compile already refers to: #'clojure.core/compile in namespace: manetu.attribute-service.compiler-cache, being replaced by: #'manetu.attribute-service.compiler-cache/compile
Execution error (NoClassDefFoundError) at java.lang.ClassLoader/defineClass1 (ClassLoader.java:-2).
manetu/protobuf/attributes/meta$write_Literal_Lang (wrong name: manetu/protobuf/attributes/meta$write_Literal_lang)

ghaskins 2024-08-26T20:06:25.748589Z

Any further tips appreciated

Alex Miller (Clojure team) 2024-08-26T20:28:05.382949Z

there's a typo difference there at the end - ...Lang vs ...lang

ghaskins 2024-08-26T21:17:19.632539Z

Good eye, but I don’t think I’m doing that. Seems to be a problem with the toolchain on my Mac. Is that possible?

ghaskins 2024-08-26T21:18:32.152999Z

Ie this code works if I build an uberjar on Linux or if I run it via lein run. So it seems to be how the uberjar is emitted on my Mac

souenzzo 2024-08-26T21:20:10.611759Z

@ghaskins compare your jar content, between the jar that works and the jar that does not work

linux ~$ jar tf path/to/app.jar
macos ~$ jar tf path/to/app.jar
if there is something different run lein classpath on both system and check: • if both classpath are equal • try to find the origin of the difference between the systems.

Alex Miller (Clojure team) 2024-08-26T21:20:12.736139Z

it is possible - the Mac filesystem is case-insensitive but case preserving and you can absolutely be picking up a case difference in class names

Alex Miller (Clojure team) 2024-08-26T21:20:55.220979Z

Java is case-sensitive in class names so definitely matters to java