Un enlace es un tipo específico de relación que se puede crear entre dos procesos. Cuando se establece esa relación y uno de los procesos muere debido a un error o salida inesperados, el otro proceso vinculado también muere.
Este es un concepto útil desde la perspectiva de no detener los errores lo antes posible: si el proceso que tiene un error se bloquea pero los que dependen de él no, entonces todos estos procesos dependientes tienen que lidiar con la desaparición de una dependencia. Dejar que mueran y luego reiniciar todo el grupo suele ser una alternativa aceptable. Los enlaces nos permiten hacer exactamente esto.
Para establecer un enlace entre dos procesos, Erlang tiene la función primitiva link/1, que toma un Pid como argumento. Cuando se llama, la función creará un enlace entre el proceso actual y el identificado por Pid. Para deshacerse de un enlace, usamos unlink/1. Cuando uno de los procesos vinculados se bloquea, se envía un tipo especial de mensaje, con información relativa a lo que sucedió. No se envía dicho mensaje si el proceso muere por causas naturales (léase: termina de ejecutar sus funciones). Primero veamos esta nueva función como parte de linkmon.erl:
myproc() ->
timer:sleep(5000),
exit(reason).
Si ejecutamos las siguiente función (y espera 5 segundos entre cada comando de generación), debería ver que el shell se bloquea por "razón" solo cuando se haya establecido un vínculo entre los dos procesos.
1> c(linkmon).
{ok,linkmon}
2> spawn(fun linkmon:myproc/0).
<0.52.0>
3> link(spawn(fun linkmon:myproc/0)).
true
** exception error: reason
No se puede capturar con un try... catch el mensaje de error como de costumbre. Se deben utilizar otros mecanismos para hacer esto.
Es importante señalar que los enlaces se utilizan para establecer grupos más grandes de procesos que deberían morir todos juntos:
chain(0) ->
receive
_ -> ok
after 2000 ->
exit("chain dies here")
end;
chain(N) ->
Pid = spawn(fun() -> chain(N-1) end),
link(Pid),
receive
_ -> ok
end.
Esta función tomará un entero N, iniciará N procesos vinculados entre sí. Para poder pasar el argumento N-1 al siguiente proceso de "cadena" (que llama a spawn/1), envuelvo la llamada dentro de una función anónima para que ya no necesite argumentos. Llamar a spawn(?MODULE, chain, [N-1]) habría hecho un trabajo similar.
Aquí, tendremos muchos procesos vinculados entre sí, que morirán cuando cada uno de sus sucesores salga:
4> c(linkmon).
{ok,linkmon}
5> link(spawn(linkmon, chain, [3])).
true
** exception error: "chain dies here"
Y como puedes ver, el shell recibe la señal de muerte de algún otro proceso. Aquí hay una representación dibujada de los procesos generados y los enlaces que se caen:
[shell] == [3] == [2] == [1] == [0]
[shell] == [3] == [2] == [1] == *dead*
[shell] == [3] == [2] == *dead*
[shell] == [3] == *dead*
[shell] == *dead*
*dead, error message shown*
[shell] <-- restarted
Después de que el proceso que ejecuta linkmon:chain(0) muere, el error se propaga a lo largo de la cadena de enlaces hasta que el proceso de shell muere por ello. El fallo podría haber ocurrido en cualquiera de los procesos enlazados; como los enlaces son bidireccionales, solo es necesario que uno de ellos muera para que los demás sigan su ejemplo.
Si deseamos matar otro proceso desde el shell, podemos utilizar la función exit/2, que se llama de esta manera: exit(Pid, Reason).
Los enlaces no se pueden apilar. Si llama a link/1 15 veces para los mismos dos procesos, solo seguirá existiendo un enlace entre ellos y una sola llamada a unlink/1 será suficiente para borrarlo.
Es importante tener en cuenta que link(spawn(Function)) o link(spawn(M,F,A)) ocurren en más de un paso. En algunos casos, es posible que un proceso muera antes de que se haya establecido el enlace y luego provoque un comportamiento inesperado. Por este motivo, se ha añadido al lenguaje la función spawn_link/1-3. Esta función toma los mismos argumentos que spawn/1-3, crea un proceso y lo vincula como si link/1 hubiera estado allí, excepto que todo se realiza como una operación atómica (las operaciones se combinan como una sola, que puede fallar o tener éxito, pero nada más). Esto generalmente se considera más seguro y también ahorra un conjunto de paréntesis.