Переопределения функций при выполнении в lisp - PullRequest
1 голос
/ 29 марта 2019

Предположим, у нас есть две функции fct1 и fct2:

  • fct1, вызовы fct2,
  • fct1 устанавливает некоторый объект O1в приложении к состоянию A,
  • fct2 устанавливает некоторый объект O2 в приложении к состоянию B.

Давайте предположим следующее ограничениечто ДОЛЖНО быть истинным всегда:

  • (01 в состоянии A И 02 в состоянии B),
  • XOR (01находится в состоянии not(A) И 02 находится в состоянии not(B).

Что произойдет, если во время разговора на fct1:

  • Переопределение: fct1 теперь устанавливает некоторый объект 01 в состояние not(A),
  • Переопределение: fct2 теперь устанавливает некоторый объект 02 в состояние not(B).

Может ли он "сломать" ограничение, установив 01 в состояние A и 02 в состояние not(B)?

Я нашел этот ответ: https://stackoverflow.com/a/20477763/9614866

Если рекурсивная функция переопределяет себя, рекурсивнаяВызовы, которые еще должны быть сделаны в одном и том же вызове может продолжать идти в том же теле.[...] В более общем смысле Common Lisp позволяет компиляторам генерировать эффективные вызовы среди функций, находящихся в одном файле.Таким образом, вы обычно должны думать о замене исполняемого кода как о уровне модуля, а не как о уровне отдельных функций.Если функции A и B находятся в одном и том же модуле, и A вызывает B, то если вы просто замените B без замены A, A может продолжить вызывать старый B (потому что B был встроен в A, или потому что Aне проходит через символ, но использует более прямой адрес для B).Вы можете объявить функции notinline, чтобы подавить это.

Мои вопросы:

  • Может ли это произойти (например, 01 установлен в состояние A и 02установить в not(B) состояние)?У него есть имя?

Если "да":

  • Это зависит от реализации?
  • Есть ли способ заставить правильное поведениеНапример, встроив функцию?
  • Какой инструмент (ы) я могу использовать для проверки правильности работы функций?Кажется, что испытывать боль: я понятия не имею, как проверить переопределения без изменения базового исходного кода.
  • Как я могу обнаружить части своего кода, которые могут представлять эту проблему?

1 Ответ

2 голосов
/ 29 марта 2019

Вот пример того, как то, что вы описываете, может происходить в многопоточной среде:

(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))

Он должен работать даже без номеров версий: если вы сначала удалите пакет, то создадите его заново с тем же именем, но потом вы не сможете легко его восстановить.

В некоторых местах также может потребоваться глобальная блокировка, которой вы должны управлять как в приложении, так и во время обновления.

...