Skip to content

Instantly share code, notes, and snippets.

@rubikill
Created May 22, 2018 11:07
Show Gist options
  • Save rubikill/1013d2d40926d916a257da32e38eb98d to your computer and use it in GitHub Desktop.
Save rubikill/1013d2d40926d916a257da32e38eb98d to your computer and use it in GitHub Desktop.

OTP Applications là gì?

1. OTP là gì?

Chắc bạn đang nghĩ tới One time Password. Nhưng không phải, OTP là cụm từ viết tắt của Open Telecom Platform. Đây có thể xem như là 1 framework dùng để thiết kế ứng dụng mà cấu thành bới những phần nhỏ của ứng dụng có thể chạy trên 1 process riêng. Nó bao gồm:

  • an Erlang interpreter (which is called BEAM)
  • an Erlang compiler
  • a protocol for communication between servers (nodes)
  • a CORBA Object Request Broker
  • a static analysis tool called Dialyzer
  • a distributed database server (Mnesia)
  • and many other libraries ...

�Đoạn này mình trích lại ở wiki mà không dịch để mọi người có thể search những từ khoá nếu muốn tìm hiểu thêm

Ref: https://en.wikipedia.org/wiki/Open_Telecom_Platform

2. Các khái niệm cơ bản để viết 1 ứng dụng OTP

GenServer

Đây có thể xem như là 1 interface giúp ta hiện thực 1 server. Nó bao gồm các hàm

  • init(start_arguments): Hàm này sẽ được gọi khi start server.
  • handle_call(request, from, state): Hàm này giúp ta handle những request từ client bên ngoài gọi đến server và reply về 1 giá trị gì đó cho client
  • handle_cast(request, state): Tương tự handle_call nhứng không reply gì về cho client. Có thể xem như one way call từ client tới server.
  • handle_info(info, state): Hàm này sẽ handle những message khác không gửi từ client. Ví dụ handle timeout
  • terminate(reason, state): Hàm này được gọi khi server tắt
  • code_change(from_version, state, extra): khi có 1 module được hot code swapping thì sẽ gọi hàm này. Bởi vì OPT cho phép chúng ta replace 1 server đang chạy mà không buộc dừng hệ thống lại.
  • format_status(reason, [pdict, state]): Giúp ta format lại cách hiển thị state hiện tại

Chúng ta chỉ cần quan tâm đến các hàm init, handle_call, handle_cast ở bài viết này

Có 3 kiểu return về cho client từ server là:

  • { :stop, reason, new_state }: Tín hiệu cho biết server bị stop
  • { :reply, response, new_state [ , :hibernate | timeout ] }: Cách trả về phản hồi sau lời gọi từ client
  • { :noreply, new_state [ , :hibernate | timeout ] }: Không trả về phản hồi nào

3. Cài đặt stack server bằng OPT

Chúng ta sẽ cài đặt 1 stack với 2 phương thức là pushpop bằng GenServer File name: my_stack/server.ex

defmodule MyStack.Server do
  use GenServer

  def init(args) do
    {:ok, args}
  end

  # Đây là hàm chạy process và link với process hiện tại
  def start_link(stack) do
    GenServer.start_link(__MODULE__, stack, name: __MODULE__)
  end

  ## External API
  @doc """
  Put 1 giá trị vào trong stack
  value: giá trị cần push
  """
  def push(value) do
    # Sử dụng cast nếu như không quan tâm kết quả trả về
    GenServer.cast(__MODULE__, {:push, value})
  end

  @doc """
  Lấy giá trị ở trên cùng của stack ra
  """
  def pop() do
    # Sử dụng call nếu như quan tâm kết quả trả về
    GenServer.call(__MODULE__, :pop)
  end

  ## Handle ở server
  @doc """
  :push là tên action
  value: tham số được truyền từ bên ngoài vào
  stack: là biến stack mà application đang giữ, được init ở hàm start_link
  """
  def handle_cast({:push, value}, stack) do
    {:noreply, [value | stack]}
  end

  @doc """
  :pop là tên action
  _form: là pid của process gọi đến
  stack: là biến stack mà application đang giữ, được init ở hàm start_link
  """
  def handle_call(:pop, _from, [h | t] = stack) do
    {:reply, h, t}
  end
end

Chạy thử chương trình

~/workspace/my_stack » iex -S mix
iex(1)> MyStack.Server.start_link
{:ok, #PID<0.120.0>}
iex(2)> MyStack.Server.push 1
:ok
iex(3)> MyStack.Server.push 2
:ok
iex(4)> MyStack.Server.pop
2
iex(5)> MyStack.Server.pop
1

Nếu như tiếp tục pop từ server ra thì chương trình sẽ bị dừng

iex(6)> MyStack.Server.pop
** (EXIT from #PID<0.118.0>) shell process exited with reason: an exception was raised:
    ** (FunctionClauseError) no function clause matching in MyStack.Server.handle_call/3
        (my_stack) lib/my_stack/server.ex:45: MyStack.Server.handle_call(:pop, {#PID<0.118.0>, #Reference<0.4269747632.3638820865.190101>}, [])
        (stdlib) gen_server.erl:636: :gen_server.try_handle_call/4
        (stdlib) gen_server.erl:665: :gen_server.handle_msg/6
        (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
iex(1)> MyStack.Server.push 1
** (exit) exited in: GenServer.call(MyStack.Server, :pop, 5000)
    ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
    (elixir) lib/gen_server.ex:824: GenServer.call/3

4. Sử dụng Supervisor

Supervisor là 1 process có nhiệm vụ quản lý 1 hoặc nhiều worker con. File name: my_stack/application.ex

defmodule MyStack.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do
    # List all child processes to be supervised
    children = [
      # Starts a worker by calling: MyStack.Worker.start_link(arg)
      {MyStack.Server, []}
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: MyStack.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Chạy lại chương trình

~/workspace/my_stack » iex -S mix
iex(1)> MyStack.Server.push 1
:ok
iex(2)> MyStack.Server.push 2
:ok
iex(3)> MyStack.Server.pop
2
iex(4)> MyStack.Server.pop
1
iex(5)> MyStack.Server.pop
** (exit) exited in: GenServer.call(MyStack.Server, :pop, 5000)
    ** (EXIT) an exception was raised:
        ** (FunctionClauseError) no function clause matching in MyStack.Server.handle_call/3
            (my_stack) lib/my_stack/server.ex:46: MyStack.Server.handle_call(:pop, {#PID<0.122.0>, #Reference<0.337936538.3931111425.61921>}, [])
            (stdlib) gen_server.erl:636: :gen_server.try_handle_call/4
            (stdlib) gen_server.erl:665: :gen_server.handle_msg/6
            (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
    (elixir) lib/gen_server.ex:834: GenServer.call/3
 iex(6)> MyStack.Server.push 1
:ok

Ta thấy ở lần gọi hàm push cuối cùng, chương trình tuy bị crash nhưng đã được restart lại như ban đầu

  • OPT application
  • Tasks và Agents

ToanHa 04-06-2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment