Временные процессы gen_server и обновление pids

В настоящее время я изучаю Erlang в разумных пределах, но у меня есть вопрос о gen_server с руководителями. Если процесс gen_server дает сбой и впоследствии перезапускается супервизором, он получает новый pid. А что, если я хочу, чтобы другие процессы ссылались на этот процесс по Pid? Какие есть хорошие идиоматические способы «обновить» Pid в этих процессах?

В качестве упражнения с некоторым практическим применением я пишу сервер блокировки, где клиент может запросить блокировку с помощью произвольного ключа. В идеале я хотел бы, чтобы отдельные процессы обрабатывали блокировку и снятие конкретной блокировки, идея в том, что я могу использовать аргумент тайм-аута в gen_server для завершения процесса, если никто не запросил его через N количество времени, так что только в настоящее время соответствующие блокировки останутся в памяти. Теперь у меня есть процесс каталога, который сопоставляет имя блокировки с процессом блокировки. Когда процесс блокировки завершается, он удаляет блокировку из каталога.

Меня беспокоит, как поступить в случае, когда клиент запрашивает блокировку, когда процесс блокировки находится в процессе завершения. Он еще не выключился, поэтому нюхать, что pid жив, не получится. Процесс блокировки еще не достиг предложения, которое удаляет его из каталога.

Есть ли лучший способ справиться с этим?

ИЗМЕНИТЬ

В настоящее время существует два gen_servers: «каталог», который поддерживает таблицу ETS из LockName -> Lock Process, и «lock server», которые динамически добавляются в дерево наблюдения с помощью start_child. В идеале я хотел бы, чтобы каждый сервер блокировки обрабатывал общение с клиентами напрямую, но меня беспокоит сценарий, когда запрос на получение/освобождение выдается с помощью вызова или приведения, когда процесс находится в середине сбоя (и, следовательно, не будет отвечать к сообщению).

Начать с {local} или {global} не получится, так как их может быть N.


person Refefer    schedule 14.11.2010    source источник


Ответы (3)


Хитрость заключается в том, чтобы назвать процесс и не ссылаться на него по его pid. Обычно у вас есть 3 жизнеспособных варианта,

  • Используйте зарегистрированные имена. Это то, что предлагает andreypopp. Вы обращаетесь к серверу по его зарегистрированному имени. локально зарегистрированные имена должны быть атомами, что может несколько ограничивать вас. глобально зарегистрированные имена не имеют этого ограничения, вы можете зарегистрировать любой термин.

  • Супервизор знает Pid. Спроси это. Вам нужно будет передать PID супервизора процессу.

  • В качестве альтернативы используйте приложение gproc (существует на http://github.com). Это позволяет вам создать общий реестр процессов — вы могли бы сделать это с помощью ETS, но крадите хороший код, а не внедряйте его самостоятельно.

PID можно использовать, если все процессы являются частью одного и того же дерева наблюдения. Так что смерть одного из них означает смерть остальных. Таким образом, утилизация Pids не имеет значения.

person I GIVE CRAP ANSWERS    schedule 14.11.2010
comment
глобально зарегистрированные имена не имеют этого ограничения, вы можете зарегистрировать любой термин. Не знал, может пригодится. - person Alexey Romanov; 15.11.2010
comment
Как насчет случаев, когда мы динамически добавляем дочерние элементы в дерево супервизора с помощью start_child в супервизоре, как в этом случае? Регистрация в локальной или глобальной системе ограничивает все функции одним процессом, не так ли? - person Refefer; 15.11.2010
comment
Я лично использую gproc, поэтому я могу зарегистрировать любой термин в таблице gproc. Тогда легко идентифицировать процесс с помощью этого термина. - person I GIVE CRAP ANSWERS; 15.11.2010
comment
Привет, это поздний комментарий, и я надеюсь, что вы будете уведомлены. Если я буду следовать вашей методике с gproc и скажу, что в функции я делаю 10 вызовов gen_server по его имени (например, {bus, BusId}), это означает 10 вызовов gproc, 10 запросов ETS. Это нормально ? Вам не кажется, что это слишком много накладных расходов? Мне кажется, это идеальное решение, но как насчет производительности? - person lud; 04.08.2016
comment
забыл отметить вас @IGIVECRAPANSWERS, смотрите мой предыдущий комментарий :) - person lud; 07.08.2016
comment
Вы часто можете кэшировать Pid для 10 вызовов. Вам понадобится некоторый код, обрабатывающий случай, когда Pid все равно отсутствует, поскольку нет гарантии, что процесс существует, когда вы фактически отправляете сообщение в его папку «Входящие». - person I GIVE CRAP ANSWERS; 14.08.2016

Не ссылайтесь на процесс gen_server по pid.

Вы должны предоставить API для своего gen_server через функции gen_server:call/2 или gen_server:call/3. Они принимают ServerRef в качестве первого аргумента, который может быть Name | {Name,Node} | {global,GlobalName} | pid(). Итак, ваш API будет выглядеть так:

lock(Key) ->
  gen_server:call(?MODULE, {lock, Key}).
release(Key) ->
  gen_server:call(?MODULE, {release, Key}).

Обратите внимание, что этот API определен в том же модуле, что и ваш gen_server, и я предполагаю, что вы запускаете свой сервер с чем-то вроде:

gen_server:start_link({local, ?MODULE}, ?MODULE, [], [])

Таким образом, ваши методы API могут искать сервер не по pid, а по имени сервера, равному ?MODULE.

Для получения дополнительной информации см. gen_server документацию.

person andreypopp    schedule 14.11.2010
comment
Не ограничит ли это меня только одним процессом? Как насчет случая, когда я хочу иметь несколько дочерних процессов? - person Refefer; 15.11.2010
comment
Да, это было бы. Хм... Я думал, вы используете процесс gen_server для процесса каталога, который управляет обработчиками блокировок (сопоставляет свое имя с процессом, который обрабатывает блокировку). В этом случае вам также не нужно ссылаться на какой-либо процесс по pid, процесс каталога (который является нашим gen_server) упоминается по его имени, а доступ к обработчику блокировки должен обрабатываться только через API процесса каталога (lock/1, release/1). - person andreypopp; 15.11.2010
comment
Вы правы, процесс каталога фактически обрабатывается таким образом, регистрируясь на локальном компьютере. Мой API для каталога обрабатывается так, как вы предлагаете, так как мне все равно нужен только один процесс. Однако мои процессы блокировки также являются gen_servers (поэтому у нас их два), которые добавляются динамически через супервизора с помощью start_child. Это те, о которых я беспокоюсь, поскольку их может быть сколько угодно, по одному на замок. - person Refefer; 15.11.2010
comment
Я могу придумать два способа: 1) Ваши обработчики блокировок должны быть максимально простыми: lock_handler(Key) -> receive release -> exit after ?LOCK_TIMEOUT -> release(Key) end.. 2) Вы должны перерегистрировать обработчик блокировки в каталоге при повторной инициализации после сбоя, используя какой-либо внутренний API сервера каталогов. - person andreypopp; 15.11.2010
comment
Но, на самом деле, мне не нравится идея иметь поведение gen_server для обработчиков блокировок, потому что gen_servers — это зарегистрированные процессы, и у вас их будет произвольное количество — по одному на каждую ключевую блокировку. Это может привести к утечке памяти в вашем приложении, потому что атомы не собираются мусором (и вы будете регистрировать обработчики блокировок с атомами в качестве имен). - person andreypopp; 15.11.2010
comment
Имена блокировок в этом случае являются двоичными, поскольку в конечном итоге они будут запрашиваться через сокеты. - person Refefer; 15.11.2010

Вы можете полностью избежать использования вашего процесса «lock_server», используя API «erlang:monitor/demonitor».

Когда клиент запрашивает блокировку, вы выдаете блокировку... и выполняете erlang:monitor на клиенте.. Это вернет вам ссылку на монитор.. Затем вы можете сохранить эту ссылку вместе со своей блокировкой.. Красота этого что ваш сервер каталогов БУДЕТ уведомлен, когда клиент умирает ... вы можете реализовать TIMEOUT в клиенте.

Вот фрагмент кода, который я недавно написал. https://github.com/xslogic/phoebus/blob/master/src/table_manager.erl

По сути, table_manager — это процесс, который блокирует определенный ресурс таблицы для клиента. Если клиент умирает, таблица возвращается в пул.

person arun_suresh    schedule 15.11.2010