Link versus monitor

Dua model propagasi kegagalan antar process.

Daftar isi

Process yang bekerja sama perlu saling memberi tahu ketika salah satunya berhenti secara tidak wajar, dan BEAM menyediakan dua mekanisme untuk itu, yaitu link dan monitor. Topik ini membahas cara membuat keduanya, bagaimana sinyal exit (exit signal) dan pesan :DOWN dihasilkan, peran Process.flag(:trap_exit, true), serta pertimbangan memilih antara keduanya. Hasil akhirnya adalah kemampuan menangkap kegagalan process lain secara deterministik, lengkap dengan alasan berhentinya.

spawn_link/1 membuat process baru sekaligus menghubungkannya dengan process pembuat melalui link, yaitu hubungan dua arah antara dua process. Ketika salah satu process yang terhubung berhenti, BEAM memancarkan exit signal kepada process lainnya. Isi sinyal ini berupa alasan (reason) berhentinya, misalnya atom :normal untuk berhenti yang wajar atau tuple berisi exception untuk berhenti akibat error.

Aturan penularannya bergantung pada alasan tersebut. Exit signal dengan alasan :normal diabaikan oleh process penerima, sehingga berhentinya satu pihak tidak menular. Alasan selain :normal menular sepenuhnya: process penerima ikut berhenti dengan alasan yang sama, dan penularan itu berlanjut ke seluruh process yang terhubung dengannya. Fungsi Process.exit/2 mengirim exit signal dengan alasan pilihan pemanggil ke sebuah process, dan alasan tersebut dapat berupa atom apa pun, misalnya :dibunuh.

Perilaku penularan ini berarti satu process yang melempar error dapat menghentikan sebagian besar program tanpa kesempatan mencatat apa pun. Mekanisme trap_exit pada bagian berikutnya mengubah perilaku itu menjadi pesan yang dapat dibaca.

trap_exit: exit menjadi pesan#

Process.flag(:trap_exit, true) mengubah cara sebuah process menerima exit signal. Alih-alih berhenti, process yang memasang bendera ini mengubah setiap exit signal menjadi pesan biasa berbentuk {:EXIT, pid, alasan}, yang masuk ke mailbox seperti pesan lain. Process tersebut tetap hidup dan bebas memutuskan tindak lanjutnya. Skrip berikut memasang bendera itu di process utama, lalu menguji tiga process terhubung yang berhenti dengan alasan berbeda:

elixir link.exs
# link.exs — exit signal dari process terhubung ditangkap sebagai pesan {:EXIT, pid, alasan}
Process.flag(:trap_exit, true)

# 1: process terhubung yang selesai dengan normal
spawn_link(fn -> :ok end)

receive do
  {:EXIT, _pid, alasan} -> IO.puts("process pertama berhenti, alasan: #{inspect(alasan)}")
after
  1000 -> IO.puts("waktu habis")
end

# 2: process terhubung yang dibunuh dengan alasan :dibunuh
sasar =
  spawn_link(fn ->
    receive do
      :menunggu_selamanya -> :ok
    after
      5000 -> :waktu_habis
    end
  end)

Process.exit(sasar, :dibunuh)

receive do
  {:EXIT, _pid, alasan} -> IO.puts("process kedua berhenti, alasan: #{inspect(alasan)}")
after
  1000 -> IO.puts("waktu habis")
end

# 3: process terhubung yang melempar error
spawn_link(fn -> raise "runtime error" end)

receive do
  {:EXIT, _pid, {%RuntimeError{message: pesan}, _jejak}} ->
    IO.puts("process ketiga berhenti, alasan: error runtime: #{pesan}")
after
  1000 -> IO.puts("waktu habis")
end
text Keluaran
process pertama berhenti, alasan: :normal
process kedua berhenti, alasan: :dibunuh
process ketiga berhenti, alasan: error runtime: runtime error

Tiga bentuk alasan terlihat sekaligus pada contoh di atas. Process pertama selesai tanpa error sehingga alasannya berupa atom :normal. Process kedua dibunuh melalui Process.exit/2 sehingga alasannya berupa atom :dibunuh. Process ketiga berhenti akibat exception, sehingga alasannya berupa tuple dua elemen yang berisi struct exception dan jejak tumpukan (stacktrace), yaitu daftar posisi kode yang sedang berjalan. Jejak tumpukan memuat alamat berkas dan nomor baris yang panjang, sehingga contoh mencocokkannya dengan pola {%RuntimeError{message: pesan}, _jejak} dan hanya mencetak pesan exceptionnya.

Monitor: pengamatan satu arah#

Monitor menyediakan hubungan satu arah yang tidak menularkan kegagalan. spawn_monitor/1 membuat process baru dan memasang monitor atasnya dalam satu langkah, lalu mengembalikan pasangan {pid, ref}. Ketika process yang dimonitor berhenti, dengan alasan apa pun termasuk :normal, process pemonitor menerima pesan {:DOWN, ref, :process, pid, alasan} tanpa harus memasang trap_exit dan tanpa risiko ikut berhenti. Nilai ref adalah referensi (reference) yang unik, sehingga pesan :DOWN dari beberapa monitor dapat dibedakan dengan mencocokkan ^ref.

Process yang sudah ada juga dapat dimonitor melalui Process.monitor/1. Contoh berikut memonitor process pekerja yang selesai secara normal setelah menerima satu pesan:

elixir Contoh: Process.monitor atas pid yang sudah ada
pekerja = spawn(fn -> receive do :selesai -> :ok after 5000 -> :waktu_habis end end)
ref = Process.monitor(pekerja)
send(pekerja, :selesai)

receive do
  {:DOWN, ^ref, :process, _pid, alasan} -> IO.puts("alasan berhenti: #{inspect(alasan)}")
after
  1000 -> IO.puts("waktu habis")
end
text Keluaran
alasan berhenti: :normal

Skrip utuh berikut merangkum kedua skenario monitor dalam satu alur, yaitu process yang berhenti normal dan process yang melempar exception. Karena spawn_monitor/1 mengembalikan pid dan referensi sekaligus, pencocokan ^ref memastikan pesan :DOWN yang dibaca benar-benar berasal dari monitor yang baru dibuat:

elixir monitor.exs
# monitor.exs — pesan {:DOWN, ref, :process, pid, alasan} tanpa menghubungkan dua process

# 1: monitor process yang berhenti dengan normal
{_pid1, ref1} = spawn_monitor(fn -> :ok end)

receive do
  {:DOWN, ^ref1, :process, _pid, alasan} ->
    IO.puts("process pertama berhenti, alasan: #{inspect(alasan)}")
after
  1000 -> IO.puts("waktu habis")
end

# 2: monitor process yang melempar error
{_pid2, ref2} = spawn_monitor(fn -> raise "runtime error" end)

receive do
  {:DOWN, ^ref2, :process, _pid, {%RuntimeError{message: pesan}, _jejak}} ->
    IO.puts("process kedua berhenti, alasan: error runtime: #{pesan}")
after
  1000 -> IO.puts("waktu habis")
end
text Keluaran
process pertama berhenti, alasan: :normal
process kedua berhenti, alasan: error runtime: runtime error

Kedua mekanisme menyampaikan informasi yang sama, yaitu process lain telah berhenti beserta alasannya, tetapi keduanya dirancang untuk kebutuhan yang berbeda. Tabel berikut merangkum perbedaannya:

AspekLinkMonitor
Arah hubunganDua arah antara dua processSatu arah, dari pemonitor ke yang dimonitor
Dampak kegagalanMenular ke kedua pihak, kecuali trap_exit dipasangTidak menular sama sekali
Pesan yang diterima{:EXIT, pid, alasan} saat trap_exit dipasang{:DOWN, ref, :process, pid, alasan}
Fungsi pembuatspawn_link/1 atau Process.link/1spawn_monitor/1 atau Process.monitor/1
Penggunaan lazimProcess yang harus hidup atau mati bersamaMengamati pekerjaan process dari luar

Link adalah pilihan yang tepat ketika dua process membentuk satu kesatuan yang kegagalan salah satunya membuat yang lain tidak berguna, misalnya server dan penyimpanannya. Pohon supervision OTP dibangun di atas kombinasi link dan trap_exit, karena supervisor harus mengetahui setiap kali anaknya berhenti. Monitor adalah pilihan yang tepat ketika pengamat tidak terlibat dalam pekerjaan process yang diamati, misalnya menunggu tugas selesai sambil tetap bebas berhenti sendiri. Task, yang dibahas pada modul Inti OTP, mengandalkan monitor untuk keperluan semacam ini, sehingga pemahaman pesan :DOWN menjadi bekal yang langsung terpakai.

Ringkasan#

  • Link menghubungkan dua process secara dua arah melalui spawn_link/1, dan exit signal dengan alasan selain :normal menularkan kegagalan ke process yang terhubung.
  • Process.flag(:trap_exit, true) mengubah exit signal menjadi pesan {:EXIT, pid, alasan}, dengan alasan berupa atom seperti :normal dan :dibunuh atau tuple exception dan jejak tumpukan.
  • Monitor mengamati satu process secara satu arah melalui spawn_monitor/1 atau Process.monitor/1 dan selalu menghasilkan pesan {:DOWN, ref, :process, pid, alasan} tanpa menularkan kegagalan.
  • Link digunakan untuk process yang hidup dan mati bersama, sedangkan monitor digunakan untuk mengamati pekerjaan dari luar.

Referensi#