Como a troca de código quente do Erlang funciona no meio da atividade?

Atualmente, estou trabalhando em um servidor de mídia ao vivo, o que permitirá que os consumidores em geral nos enviem vídeo ao vivo. Em nosso ambiente atual, vimos transmissões enviadas a nós com a duração de dias; portanto, a ideia de poder corrigir um bug (ou adicionar um recurso) sem desconectar os usuários é extremamente atraente.

No entanto, enquanto escrevia o código, percebi que a troca de código quente não faz sentido, a menos que eu escreva todos os processos para que todo o estado seja sempre realizado dentro de um gen_server, e todos os módulos externos que o gen_server chama devem ser o mais simples possível.

Vamos dar o seguinte exemplo:

-module(server_template).
-behaviour(gen_server).

-export([start/1, stop/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init([]) -> {ok, {module1:new(), module2:new()}}.

handle_call(Message, From, State) -> {reply, ok, State}.

handle_cast(any_message, {state1, state2}) -> 
    new_state1 = module1:do_something(state1),
    new_state2 = module2:do_something(state2),
    {noreply, {new_state1, new_state2}}.

handle_info(_Message, _Server) -> {noreply, _Server}.

terminate(_Reason, _Server) -> ok.

code_change(_OldVersion, {state1, state2}, _Extra) -> 
    new_state1 = module1:code_change(state1),
    new_state2 = module2:code_change(state2)
    {ok, {new_state1, new_state2}}

De acordo com o que pude encontrar, quando uma nova versão do código é carregada no tempo de execução atualmente em execução sem o uso de um sistema OTP, você pode atualizar para a versão atual do código chamando seu módulo como uma chamada de função externa, portantomy_module:loop(state).

O que também vejo é que, quando uma troca a quente é realizada, ocode_change/3 A função é chamada e atualiza o estado, para que eu possa usá-lo para garantir que cada um dos meus módulos dependentes migre o último estado que eles me deram para a versão atual do código. Isso é feito porque o supervisor conhece o processo em execução, o que permite que o processo seja suspenso para que ele possa chamar a função de alteração de código. Tudo bom.

No entanto, se a chamada de um módulo externo sempre chamar a versão atual desse módulo, isso parecerá interrompido se uma troca a quente for realizada no meio da função. Por exemplo, mesmo meu gen_server está atualmente no processo de manipulação doany_message elenco, digamos, entre corrermodule1:do_something() emodule2:do_something().

Se estou entendendo as coisas corretamente,module2:do_something() chamaria agora a nova versão atual dodo_something , o que pode significar que estou passando dados não migrados para a nova versão domodule2:do_something(). Isso facilmente causaria problemas se um registro fosse alterado, uma matriz com um número inesperado de elementos ou mesmo se um mapa estivesse com um valor que o código espera.

Estou entendendo mal como esta situação funciona? Se isso estiver correto, isso parece indicar que devo rastrear algum tipo de detalhe da versão para qualquer estrutura de dados que possa fazer a transição dos limites do módulo, e todas as funções públicas devem verificar esse número da versão e executar uma migração sob demanda, se necessário.

Essa parece ser uma tarefa extremamente alta e parece loucamente suscetível a erros, por isso estou me perguntando se estou perdendo alguma coisa.

questionAnswers(2)

yourAnswerToTheQuestion