Fork me on GitHub
#clojure
<
2022-11-06
>
Ben Lieberman17:11:52

I am receiving More than one matching method found: submit when trying to use an ExecutorService. It looks like there are multiple versions of submit, two that accept Runnable and one for Callable, so I tried (I think superfluously) to type hint the passed fn, but since IFn already implements both of those, I didn't think it would work, and indeed it did not. What probably obvious thing am I missing?

p-himik17:11:10

What code do you have exactly?

user=> (def pool (java.util.concurrent.Executors/newFixedThreadPool 1))
#'user/pool
user=> (.submit pool (fn []))
#object[java.util.concurrent.FutureTask 0x13047d7d "java.util.concurrent.FutureTask@13047d7d[Completed normally]"]
user=> 

Ben Lieberman17:11:46

This is dummy code I wrote in an effort to break the problem down but same issue

(let [ex (Executors/newFixedThreadPool 3)]
    (dotimes [n 3] (doto
                    ex
                     (.submit #(println (str "on thread no. " n))) .get)))

dpsutton18:11:09

(let [e (Executors/newFixedThreadPool 1)]
    @(.submit e ^Callable (fn [] 3)))
3

dpsutton18:11:55

check=> (let [e (Executors/newFixedThreadPool)]
          (.submit e (fn [] (println "HI!"))))
Syntax error (IllegalArgumentException) compiling . at (REPL:60:9).
No matching method newFixedThreadPool found taking 0 args for class java.util.concurrent.Executors
both runnable and callable are fine, depending on your needs
check=> (let [e (Executors/newFixedThreadPool 1)]
          @(.submit e ^Callable (fn [] 3)))
3
check=> (let [e (Executors/newFixedThreadPool 1)]
          (.submit e ^Runnable (fn [] (println "HI!"))))
#object[java.util.concurrent.FutureTask 0x14d5b143 "java.util.concurrent.FutureTask@14d5b143[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@3112ad10[Wrapped task = check$eval9019$fn__9020@49452b11]]"]

p-himik18:11:36

Oh, that's curious - it works as is with def but not with let. Hmm.

Ben Lieberman18:11:49

Aha, I think I see where I went astray...I had the ^Callable hint in the body of my function like (fn ^Callable [] ...) which in hindsight makes little sense

Fredrik18:11:35

@U2FRKM4TW if you (def ^:const pool ...) then it doesn't work again EDIT: but for an unrelated reason actually. Instead try

(def ^java.util.concurrent.ThreadPoolExecutor ex (java.util.concurrent.Executors/newFixedThreadPool 1))
(.submit ex (fn []))
to see that when the compiler knows the target of the host expression, it will reflect at compile-time and throw error if it finds several matching methods.

dpsutton18:11:03

@U2FRKM4TW what's your jvm version?

Fredrik18:11:31

I think it has something to do with that the compiler knows the type of pool and let-locals, so when it tries to resolve the instance method it sees that there are two competing signatures

Fredrik18:11:52

But when it sees (.submit pool (fn [])) without knowing the class of pool (ie. when deffing without the ^:const)it can't reflect and see the signatures of submit

p-himik18:11:13

@U11BV7MTK OpenJDK 11.0.16 on Ubuntu.

dpsutton18:11:22

i thought it might be a new method but was mistaken

hiredman18:11:23

The issue is you cannot type hint a function literal for reasons I forget

hiredman18:11:22

So to completely get rid of reflection you need to hint both the executor and then let bind the fn and type hint the name, or do something like ^Callable (identity (fn [] ...))

Fredrik18:11:31

> you cannot type hint a function literal But there's no reflection warnings on this, with type hinting on the function literal?

(.submit (java.util.concurrent.Executors/newFixedThreadPool 1) ^Callable (fn [] 1))

hiredman18:11:38

I must be misremembering something else

hiredman19:11:25

(can't figure out what I was thinking of, may just be completely mistaken)

dpsutton19:11:07

@U0NCTKEV8 could that be in a core.async context rather than just Clojure at large?

hiredman19:11:29

I guess? Seems unlikely because with core.async you don't directly use executors. I just could have sworn I remembered that from fighting with type hinting executors in the past. There have been other issues in the past where reflection would non-deterministicly choose which overload it used

Fredrik19:11:36

It seems that if when you don't provide enough hints at compile time, the runtime will resort to clojure.lang.Reflector/invokeInstanceMethod, which tries to choose the most specific method but ultimately is non-deterministic