六 月
14
木曜日

core.async/multでIllegalArgumentException "No implementation of method:〜"

core.asyncmultIllegalArgumentException "No implementation of method:〜"が発生するケースを調べた際のメモ。

再現手順。

(require '[clojure.core.async :as a])
(a/mult (a/chan)) ;; 一度目は成功
(require 'clojure.core.async :reload-all) ;; 適当な名前空間でreload-all
(a/mult (a/chan)) ;; リロード以降は失敗する
;;=> IllegalArgumentException No implementation of method: :exec of protocol: #'clojure.core.async.impl.protocols/Executor found for class: clojure.core.async.impl.exec.threadpool$thread_pool_executor$reify__489  clojure.core/-cache-protocol-fn (core_deftype.clj:583)

例外のスタックトレース。

#error {
 :cause "No implementation of method: :exec of protocol: #'clojure.core.async.impl.protocols/Executor found for class: clojure.core.async.impl.exec.threadpool$thread_pool_executor$reify__23498"
 :via
 [{:type java.lang.IllegalArgumentException
   :message "No implementation of method: :exec of protocol: #'clojure.core.async.impl.protocols/Executor found for class: clojure.core.async.impl.exec.threadpool$thread_pool_executor$reify__23498"
   :at [clojure.core$_cache_protocol_fn invokeStatic "core_deftype.clj" 583]}]
 :trace
 [[clojure.core$_cache_protocol_fn invokeStatic "core_deftype.clj" 583]
  [clojure.core$_cache_protocol_fn invoke "core_deftype.clj" 575]
  [clojure.core.async.impl.protocols$eval31750$fn__31751$G__31741__31758 invoke "protocols.clj" 43]
  [clojure.core.async.impl.dispatch$run invokeStatic "dispatch.clj" 21]
  [clojure.core.async.impl.dispatch$run invoke "dispatch.clj" 18]
  [clojure.core.async$mult invokeStatic "async.clj" 695]
  [clojure.core.async$mult invoke "async.clj" 669]

発生箇所のコード を確認する。

(defn run
  "Runs Runnable r in a thread pool thread"
  [^Runnable r]
  (impl/exec @executor r))

impl/execNo implementation of method: :exec of protocol と怒られているので 額面通りに受け取ると @exector が指すオブジェクトが clojure.core.async.impl.protocols.Executorプロトコルを実装していないという事になる。

もし @exector が nil ならばこの例外発生は至極当然だが、found for class: clojure.core.async.impl.exec.threadpool$thread_pool_executor$reify__23498とあり nil になってはいない。


ここからは推測になってしまうのだが、原因は

  • (require 'xxx :reload-all) で 名前空間 clojure.core.async.impl.protocols がリロードされる
  • プロトコル Executor も再定義。Javaのインタフェースも再作成され同名で別の型が作られる
  • 再定義されプロトコル Executorexec 関数は新しい型用となる
  • defonceで束縛されたオブジェクトが実装しているのは同名の古い型
  • 新しいexec関数から見るとdefonceで束縛されたオブジェクトは Executorプロトコルを実装していない

という塩梅で、core.asyncとは直接関係の無い require :reload-allににおけるdefonceとプロトコル固有の問題 と理解した。

Clojure開発で快適なリロード生活を営むため tools.namespacens-trackerも含め各種リロード方式の仕組みやその注意点についてちゃんと勉強しておいた方が良さそうだ。