Skip to content

Commit cd1a5ee

Browse files
committed
fix(Http4s600): use lazy val to break recursive object init trap
OBPAPI6_0_0 and APIMethods600 both reference Http4s600.Implementations6_0_0 via direct getstatic. When either is loaded during Lift Boot, the JVM triggers Implementations6_0_0.<clinit> before Http4s600.<clinit>. Impl6's <init> then reads Http4s600.MODULE$, causing Http4s600.<clinit> to run recursively on the same thread. The JVM spec allows this (returns the partially-initialised object), but the strict-val assignment `wrappedRoutesV600Services = Impl6.allRoutesWithMiddleware` executes while Impl6 is still partially initialised, reads null for allRoutesWithMiddleware, and stores null permanently. This made every request to Http4sApp NPE at v600Routes.run(req), causing all v3.x/v2.x tests to see HTTP 500 on GET /banks and abort at class-init time. Changing to `lazy val` defers the read until first access from Http4sApp (after Boot completes and all object inits are done), at which point Impl6.allRoutesWithMiddleware is fully assigned.
1 parent fc2a3d7 commit cd1a5ee

1 file changed

Lines changed: 12 additions & 1 deletion

File tree

obp-api/src/main/scala/code/api/v6_0_0/Http4s600.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8489,6 +8489,17 @@ object Http4s600 {
84898489
}
84908490
}
84918491

8492-
val wrappedRoutesV600Services: HttpRoutes[IO] =
8492+
// `lazy val`, not `val`: `OBPAPI6_0_0` and `APIMethods600` reference
8493+
// `Http4s600.Implementations6_0_0` directly via getstatic. When either is loaded
8494+
// first (during Lift's Boot), the JVM triggers `Implementations6_0_0.<clinit>`
8495+
// before `Http4s600.<clinit>`. Resource-doc registrations inside Impl6.<init>
8496+
// reference `Http4s600.MODULE$`, triggering `Http4s600.<clinit>` recursively on
8497+
// the same thread. JVM allows recursive class init; the partially-initialised
8498+
// `Impl6.MODULE$` is returned. The strict-val `wrappedRoutesV600Services =
8499+
// Impl6.allRoutesWithMiddleware` then reads the not-yet-assigned
8500+
// `allRoutesWithMiddleware` field (still null) and writes null permanently.
8501+
// A `lazy val` defers the read until first access (from Http4sApp after Boot
8502+
// completes), by which time Impl6 is fully initialised.
8503+
lazy val wrappedRoutesV600Services: HttpRoutes[IO] =
84938504
Implementations6_0_0.allRoutesWithMiddleware
84948505
}

0 commit comments

Comments
 (0)