Skip to content

Genie.Renderer.Json causes world age issues and instability in code it should not affect #778

Description

@mrarat

Describe the bug
Genie causes repeated recompilation problems, increases allocation count and makes it non-deterministic in code it should not affect e.g. OrdinaryDiffEq solvers. Furthermore, recompilations caused by Genie have extremely high values e.g. 5000%.

It seems that version 5.35.7 made it worse, but the issue still persists on 5.35.6.

To reproduce
Below is MWE code that reproduces the issue:

# Minimal Working Example: Genie 5.35.7 causes ODE solver recompilation
#
# After warming up an ODE solve (QNDF + KLUFactorization), subsequent calls
# show 0% compilation time as expected.  After `import Genie.Renderer.Json: json`,
# the very next ODE solves shows recompilation and increased allocations.
#
# Root-cause hypothesis
# ---------------------
# Genie 5.35.7 added in src/JSONParser.jl (commit 7add658):
#
#   const UNDEFINED_REPLACEMENT = Ref{Any}(nothing)
#   const UNDEFINED_TYPE = Ref{DataType}(typeof(UNDEFINED_REPLACEMENT[]))
#   typify(x::String) = x == UNDEFINED_PLACEHOLDER[] ? UNDEFINED_REPLACEMENT[] : x
#
# The module-level evaluation of `typeof(UNDEFINED_REPLACEMENT[])` dereferences
# a `Ref{Any}` holding `nothing`, triggering new @generated dispatch
# specialisations that intersect with OrdinaryDiffEq / LinearSolve / SparseArrays
# dispatch paths.  This invalidates previously cached method specialisations in
# the ODE solver.

ENV["GENIE_ENV"] = "prod"  # disable Revise — issue persists regardless

using OrdinaryDiffEq
using LinearSolve
using SparseArrays

# ── Simple stiff ODE (Robertson chemical kinetics) ───────────────────────────
function rober!(du, u, p, t)
    y₁, y₂, y₃ = u
    k₁, k₂, k₃ = p
    du[1] = -k₁ * y₁ + k₃ * y₂ * y₃
    du[2] =  k₁ * y₁ - k₂ * y₂^2 - k₃ * y₂ * y₃
    du[3] =  k₂ * y₂^2
end

const JAC_SPARSITY = float(sparse(Bool[
    1 1 1
    1 1 1
    0 1 0
]))

function run_ode()
    u0    = [1.0, 0.0, 0.0]
    p     = (0.04, 3e7, 1e4)
    tspan = (0.0, 1e5)
    f     = ODEFunction(rober!; jac_prototype = JAC_SPARSITY)
    prob  = ODEProblem(f, u0, tspan, p)
    return solve(prob, QNDF(linsolve = KLUFactorization());
                 abstol = 1e-8, reltol = 1e-6)
end

# ── Phase 1: warm up ODE solver ───────────────────────────────────────────────
println("=== Run 1  (first compilation — expected slow) ===")
@time run_ode()

println("\n=== Run 2  (expected: 0% compilation) ===")
@time run_ode()

println("\n=== Run 3  (expected: 0% compilation) ===")
@time run_ode()

# ── Load Genie.Renderer.Json ──────────────────────────────────────────────────
println("\nLoading Genie.Renderer.Json (Genie 5.35.7)...")
import Genie.Renderer.Json: json
println("Loaded.\n")

# ── Phase 2: same ODE, same solver, same types ────────────────────────────────
println("=== Run 4 (bug: Genie 5.35.7 shows compilation here, expected 0% compilation) ===")
@time run_ode()

println("\n=== Run 5 (bug: Genie 5.35.7 shows compilation here, expected 0% compilation) ===")
@time run_ode()

println("\n=== Run 6 (bug: Genie 5.35.7 shows compilation here, expected 0% compilation) ===")
@time run_ode()

println("\n=== Version Info ===")
import Pkg
Pkg.status()
using InteractiveUtils   
versioninfo()

output:

=== Run 1  (first compilation — expected slow) ===
  4.144455 seconds (10.20 M allocations: 509.281 MiB, 12.61% gc time, 99.91% compilation time)

=== Run 2  (expected: 0% compilation) ===
  0.000528 seconds (4.44 k allocations: 210.344 KiB)

=== Run 3  (expected: 0% compilation) ===
  0.000577 seconds (4.44 k allocations: 210.344 KiB)

Loading Genie.Renderer.Json (Genie 5.35.7)...
Loaded.

=== Run 4 (bug: Genie 5.35.7 shows compilation here, expected 0% compilation) ===
  0.099837 seconds (684.82 k allocations: 32.385 MiB, 198.45% compilation time: 50% of which was recompilation)

=== Run 5 (bug: Genie 5.35.7 shows compilation here, expected 0% compilation) ===
  0.000664 seconds (5.47 k allocations: 261.617 KiB, 5137.18% compilation time)

=== Run 6 (bug: Genie 5.35.7 shows compilation here, expected 0% compilation) ===
  0.000644 seconds (6.02 k allocations: 287.109 KiB, 5013.85% compilation time)

=== Version Info ===
Status `~/Code/MWE/GenieWorldAgeProblem/Project.toml`
  [c43c736e] Genie v5.35.7
  [7ed4a6bd] LinearSolve v3.65.0
  [1dea7af3] OrdinaryDiffEq v6.108.0
  [2f01184e] SparseArrays v1.12.0
Julia Version 1.12.5
Commit 5fe89b8ddc1 (2026-02-09 16:05 UTC)
Build Info:
  Official https://julialang.org release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 14 × Intel(R) Core(TM) Ultra 7 165U
  WORD_SIZE: 64
  LLVM: libLLVM-18.1.7 (ORCJIT, alderlake)
  GC: Built with stock GC
Threads: 1 default, 1 interactive, 1 GC (on 14 virtual cores)

Expected behavior
No compilation or increased allocation count on function calls that happen after import Genie.Renderer.Json: json

Additional context
Version info provided in the output of the MWE script


Please answer these optional questions to help us understand, prioritise, and assign the issue

2/ Can you give us, in a few words, some details about the app you're building with Genie?

I am trying to host some ODE solvers via REST API and I cannot afford to lose their performance or stability when calling them from Genie.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions