Вот пример того, как то, что вы описываете, может происходить в многопоточной среде:
(progn
(defun f2 (o2)
(setf (car o2) :b))
(defun f1 (o1 o2)
(setf (car o1) :a)
;; the sleep here is to increase the risk of data-race
(sleep 3)
(f2 o2))
;; call the functions in a separate thread
(sb-thread:make-thread
(lambda ()
(let ((o1 (list 0))
(o2 (list 0)))
(f1 o1 o2)
(print (list o1 o2)))))
;; in parallel, redefine f2, then f1
(defun f2 (o2)
(setf (car o2) :not-b))
(defun f1 (o1 o2)
(setf (car o1) :not-a)
(f2 o2)))
Через 3 секунды REPL напечатает
((:A) (:NOT-B))
Если вы добавите (declaim (inline f2))
до определения f2
и повторите тестирование, то код из old f2
все еще выполняется из old f1
внутри поток, который печатает следующее через 3 секунды:
((:A) (:B))
Дальнейшие вызовы обновленной функции f1
дают:
((:NOT-A) (:NOT-B))
Какой инструмент (ы) я могу использовать для проверки правильности работы функций? Кажется, что испытывать боль: я понятия не имею, как проверить переопределения без изменения базового исходного кода.
Возможно, вы обновляете работающий сервер новым кодом и не хотите, чтобы сервер использовал частичное переопределение функций при загрузке определений.
Как и во всех других аспектах вашей инфраструктуры, важно заранее планировать, как надежно делать резервные копии и обновления (базы данных, конфигурация и т. Д.).
Один из возможных способов - обновить вещи по пакетам. Вы можете суффикс вашего пакета с номером версии:
(in-package :web-0 ...)
(defun f2 () ...)
(defun f1 () ...)
;; new version
(in-package :web-1 ...)
(defun f2 () ...)
(defun f1 () ...)
Когда пришло время для обновления, вы можете скомпилировать и загрузить код для :web-1
, не вмешиваясь в работающий код. Тогда вы сможете изменить вызывающих абонентов для использования новой реализации:
;; somewhere in the main server package
(handle-request (request)
(web-0:handle request))
;; update it
(handle-request (request)
(web-1:handle request))
Он должен работать даже без номеров версий: если вы сначала удалите пакет, то создадите его заново с тем же именем, но потом вы не сможете легко его восстановить.
В некоторых местах также может потребоваться глобальная блокировка, которой вы должны управлять как в приложении, так и во время обновления.