Topik ini memperkenalkan gen_statem, behaviour OTP untuk proses yang perilakunya ditentukan state tempatnya berada: pesan yang sama diproses berbeda di setiap state, dan perpindahan state adalah hasil eksplisit dari setiap peristiwa. Contohnya adalah pintu yang hanya bisa dibuka dari state tertutup dan menutup sendiri setelah lima detik.
Kapan statem dibutuhkan#
gen_server memproses pesan dengan pola terlepas dari riwayatnya, sementara banyak proses nyata justru bergantung pada riwayat: pintu hanya menerima tutup saat terbuka, koneksi hanya menerima kirim setelah tersambung, dan transaksi hanya menerima batalkan sebelum dikunci. Menanam kondisi itu ke dalam data gen_server berarti menumpuk tuple penanda di dalam data dan memeriksa penanda itu di setiap klausa. Gen_statem memberi state tempat yang semestinya: nama state menjadi bagian callback, sehingga tiap state punya klausulnya sendiri dan pesan yang tidak sah jatuh ke error pencocokan tanpa pemeriksaan manual.
Modul pintu dengan dua state#
Modul berikut mendefinisikan mesin dua state, tertutup dan terbuka, dengan dua peristiwa cast:
-module(pintu).
-behaviour(gen_statem).
-export([start_link/0, buka/0, tutup/0]).
-export([init/1, callback_mode/0, tertutup/3, terbuka/3]).
start_link() -> gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []).
buka() -> gen_statem:cast(?MODULE, buka).
tutup() -> gen_statem:cast(?MODULE, tutup).
init([]) -> {ok, tertutup, #{}}.
callback_mode() -> state_functions.
tertutup(cast, buka, Data) -> {next_state, terbuka, Data, 5000};
tertutup(cast, tutup, Data) -> {next_state, tertutup, Data}.
terbuka(cast, tutup, Data) -> {next_state, tertutup, Data};
terbuka(timeout, _Peristiwa, Data) -> {next_state, tertutup, Data}.Atribut -behaviour(gen_statem). menuntut callback init/1 dan callback_mode/0. Nilai state_functions memilih mode callback: satu fungsi per state yang menerima jenis peristiwa, isi peristiwa, dan data. Fungsi yang namanya sama dengan nama state menampung semua perilaku state itu. Perhatikan nilai kembalian klausul pertama: 5000 adalah waktu tunggu yang diminta gen_statem, sehingga bila tidak ada peristiwa dalam lima detik, peristiwa timeout tiba di state yang sama.
Menjalankan mesin#
Sesi berikut mengamati state sebelum, selama, dan setelah peristiwa:
Erlang/OTP 29 [erts-17.1.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit:ns]
Eshell V17.1.1 (press Ctrl+G to abort, type help(). for help)
1> c(pintu).
{ok,pintu}
2> pintu:start_link().
{ok,<0.92.0>}
3> sys:get_state(pintu).
{tertutup,#{}}
4> pintu:buka().
ok
5> sys:get_state(pintu).
{terbuka,#{}}
6> pintu:tutup().
ok
7> sys:get_state(pintu).
{tertutup,#{}}Fungsi sys:get_state/1 membaca state proses OTP dari luar tanpa mengganggunya, dan kembaliannya adalah tuple nama state beserta datanya. Sesi kedua memperlihatkan transisi berwaktu: setelah buka/0, state berubah ke terbuka, dan setelah menunggu lima setengah detik, state kembali tertutup karena peristiwa timeout menutup pintu tanpa campur tangan siapa pun:
1> pintu:start_link().
{ok,<0.89.0>}
2> pintu:buka().
ok
3> sys:get_state(pintu).
{terbuka,#{}}
4> timer:sleep(5500).
ok
5> sys:get_state(pintu).
{tertutup,#{}}Peristiwa timeout dihapus otomatis setiap kali state berpindah, sehingga timer tidak perlu dibatalkan secara manual, dan waktu tunggu baru diminta kembali oleh state berikutnya bila dikehendaki.
Dua mode callback#
Mode state_functions menempatkan setiap state pada fungsi bernama, dan mode handle_event_function menempatkan semua state pada satu fungsi dengan nama state sebagai argumen pertama. Pedoman pemilihannya sederhana: state yang sedikit tetapi datanya beraneka ragam lebih rapi di state_functions, sedangkan state yang banyak dan seragam bentuk peristiwanya lebih rapi di handle_event_function. Kedua mode memakai bentuk kembalian yang sama, {next_state, State, Data} beserta variasi waktu tunggunya, sehingga perpindahan antar mode tidak mengubah logika transisi.
Perbandingan dengan gen_server memperlihatkan letak perbedaannya dengan jelas:
| Aspek | gen_server | gen_statem |
|---|---|---|
| Pemilihan cabang | pola pesan saja | nama state ditambah pola pesan |
| Penyimpanan state | satu state pada callback | state bernama ditambah data terpisah |
| Pesan yang tidak sah | lazim ditampung klausul penangkap | jatuh ke error pencocokan pada state itu |
| Perpindahan perilaku | dikelola manual di dalam data | eksplisit lewat next_state |
Data pada gen_statem tetap disimpan terpisah dari nama state, sebagaimana #{} pada contoh pintu, sehingga penghitung atau konteks yang tidak menentukan transisi tidak perlu diangkat menjadi state. Kembalian {keep_state, Data} mempertahankan state sambil memperbarui data, dan {repeat_state, Data} mengulang state yang sama, dua bentuk yang memperkaya pola tanpa perpindahan.
Kesalahan umum#
- Menanam penanda state di dalam data gen_server padahal perilakunya bergantung riwayat. Bila pesan yang sama harus dibedakan menurut kondisi, itu adalah state. Memeriksanya manual di handle_call menyisakan risiko satu jalur yang terlewat.
- Mengabaikan klausul peristiwa yang tidak sah pada tiap state. Peristiwa yang tidak cocok di suatu state memicu error, dan itu perilaku yang benar. Menambah klausul penangkap
_tanpa pertimbangan menelan pesan salah dan menyembunyikan kegagalan desain. - Mengandalkan timer manual di luar gen_statem. Waktu tunggu pada kembalian transisi sudah dihapus otomatis saat state berpindah, sehingga timer sendiri hanya menambah risiko sisa timer lama.
Ringkasan#
- gen_statem mengangkat state ke nama callback, sehingga pesan yang tidak sah di suatu state jatuh ke error pencocokan tanpa pemeriksaan manual.
- callback_mode/0 memilih state_functions atau handle_event_function, dan keduanya memakai bentuk kembalian yang sama.
- Waktu tunggu pada kembalian transisi memicu peristiwa timeout otomatis dan dihapus saat state berpindah, sehingga penutupan berwaktu tidak butuh timer manual.
- sys:get_state/1 membaca nama state dan data proses yang berjalan untuk inspeksi langsung.
Referensi#
- gen_statem — Ericsson — Dokumentasi resmi gen_statem beserta kedua mode callback dan bentuk transisinya.
- Erlang Design Principles: gen_statem — Ericsson — Panduan desain mesin state dengan gen_statem.
- Learn You Some Erlang for Great Good! — Fred Hebert — Bab OTP yang menyiapkan pola pikir behaviour sebelum gen_statem.