datomic

cch1 2026-02-10T21:51:14.745309Z

Sporadically I will see errors in our Datomic Ion applications where a protocol method is not defined. Here's an example:

"No implementation of method: :v0 of protocol: #'st.interval.protocol/Interval found for class: org.threeten.extra.LocalDateRange"
The method v0 is defined for LocalDateRange and, based on the call stack, I cannot understand how the code that provokes this exception can be invoked before the v0 implementation is defined for LocalDateRange. It's just not possible. I'm left theorizing about perhaps closing over the v0 function invocation before the v0 implementation is defined for LocalDateRange (not really possible, right?) or other kinds of race conditions perhaps exacerbated by how Datomic Ions load my code in production. This error seems to happen in our lambda handlers shortly after a deploy. None of our developers can provoke the error locally. Any ideas? More details in the ๐Ÿงต .

rutledgepaulv 2026-02-11T18:45:39.608959Z

is something reloading the defprotocol? if defprotocol is reloaded it creates a new instance of the protocol that would not be extended by other things that already exist in memory / does not inherit the historical extensions

rutledgepaulv 2026-02-11T18:49:24.761139Z

to debug you could add a top-level log statement to the same namespace as the defprotocol. oftentimes i'll put defprotocol in a dedicated namespace to prevent unintentional reloads

cch1 2026-02-11T18:57:04.971209Z

I'm going to try that approach now.

cch1 2026-02-11T19:05:02.500569Z

I need to get some clarification on a couple of points, @rutledgepaulv, if you can help... How can it be possible to "reload" the defprotocol when it exists in exactly one namespace? Assuming one is not forcing the reload of a namespace, Clojure's namespace loading mechanisms should prevent the namespace from being reloaded which in turn should prevent the defprotocol from being reloaded.

๐Ÿ‘ 1
cch1 2026-02-11T19:07:35.297979Z

(I don't have any :reload directives anywhere in my codebase)

rutledgepaulv 2026-02-11T19:07:56.753399Z

agree, unless you have some middleware or something that forces reloads of a namespace from time to time (like popular ring dev middleware) although that should only do it when the file timestamp changes iirc

cch1 2026-02-11T19:08:08.489719Z

Nothing like that...

rutledgepaulv 2026-02-11T19:08:18.865819Z

just suggesting that "reloading the defprotocol" might be the cause and not necessarily "the extension never happened"

cch1 2026-02-11T19:08:26.629399Z

But, I think you are on the right track, which is why I asked the question in this channel....

cch1 2026-02-11T19:09:36.765509Z

I wonder (@jaret) if Datomic Cloud's mechanism for loading the namespaces that hold the whitelisted lambdas and allows might be working in parallel or without the protection of Clojure's nromal loading.

rutledgepaulv 2026-02-11T19:09:50.054459Z

if you want you could also wrap the defprotocol with a defonce just to rule it out

cch1 2026-02-11T19:11:12.628279Z

From what I see in the defmacro of defprotocol, there is already a defonce in place.

rutledgepaulv 2026-02-11T19:12:33.081409Z

it does defonce the var but then it mutates it on each load i think, is easy to recreate if you force reload and then try to call a protocol method on an object that was reified with the old version

cch1 2026-02-11T19:12:35.108119Z

... but a lot of other stateful stuff as well.

cch1 2026-02-11T19:12:37.940519Z

Yeah.

rutledgepaulv 2026-02-11T19:12:43.780869Z

an outer defonce skips all the stateful stuff the next time

rutledgepaulv 2026-02-11T19:14:03.581899Z

the other thing i was going to say that i've seen (years ago) is AOT not playing well with defprotocol where it generates one version at AOT time and then another version at runtime and loading orders start to matter as far as which version you get

โž• 1
cch1 2026-02-11T19:15:35.821699Z

Regarding AOT, since my code runs in Datomic Ions, that part of the deploy is not under my control anyways, so I sure hope that's not the cause. I'm going to put something in the ns to alert if it is being reloaded. I suppose I could see if defonce'd var has been interned....

cch1 2026-02-10T21:56:31.647759Z

The lambda handler's ion entry point is a fn in a namespace where there is, effectively, this ns declaration:

(ns my.ion-entry-points
  (:require [my.ns-with-defprotocol-of-interval]
            [my.ns-with-extension-of-ldr-to-interval]))

(defn lambda-entry
  [x]
  (let [ldr (f x)]
    (v0 ldr) ; provokes exception
    ...))
Where v0 is a method of the Interval protocol.