Грешки

Вход-изход

Курс по Elixir 2023, ФМИ

Да си припомним

  • Какво е поведение?
  • Какво е OTP?
  • GenServer/Supervisor/Application?
Курс по Elixir 2023, ФМИ

Грешки

Image-Absolute

Курс по Elixir 2023, ФМИ

Част от общата концепция на Elixir и Erlang е грешките да бъдат фатални и да убиват процеса,
в който са възникнали.

Нека някой друг процес (supervisor) се оправя с проблема.

Курс по Elixir 2023, ФМИ

Image-Absolute

Курс по Elixir 2023, ФМИ

Let It Crash!

  • Грешките в Elixir не са препоръчителни за употреба.
  • Имат славата на GOTO програмиране и наистина е хубаво да помислим дали има нужда от тях в дадена ситуация.
Курс по Elixir 2023, ФМИ

Let It Crash!

  • "Let It Crash!" не означава, че оставяме бъгове и проблеми в кода, когато ги намерим.
  • Бъгове и проблеми се тестват и оправят.
  • Обикновено "Let It Crash!" е свързан с външни ресурси, достъп и неща над които нямаме контрол, ако процесът се рестартира с начален стейт има шанс тази грешка да се оправи.
  • За грешно поведение и реакция - match на {:error, reason} и изпълни конкретно действие.
Курс по Elixir 2023, ФМИ

Грешки

  • Прието е функции, при които има проблем да връщат:
      {:error, <проблем>}
    
  • Ако се изпълняват с успех ще имат резултат:
      {:ok, <резултат>}
    
  • Имената на функции, които биха могли да 'вдигат' грешка, обикновено завършват на '!'.
Курс по Elixir 2023, ФМИ

'Вдигане' и 'спасяване'

Image-Absolute

Курс по Elixir 2023, ФМИ

Просто със стринг

raise "Ужаст!"
#=> (RuntimeError) Ужаст!
Курс по Elixir 2023, ФМИ

Със структура-грешка

raise RuntimeError
#=> (RuntimeError) runtime error
Курс по Elixir 2023, ФМИ

Със структура-грешка с аргумент

raise ArgumentError, message: "Грешка, брато!"
#=> (ArgumentError) Грешка, брато!
Курс по Elixir 2023, ФМИ
try do
  1 / 0
rescue
  [RuntimeError, ArgumentError] ->
    IO.puts("Няма да стигнем до тук.")
  error in [ArithmeticError] ->
    IO.puts("На нула не се дели, #{error.message}")
  any_other_error ->
    IO.puts("Лошаво... #{any_other_error.message}")
else
  IO.puts("Няма грешка.")
after
  IO.puts("Finally!")
end
Курс по Elixir 2023, ФМИ

Създаване на нови типове грешки

Image-Absolute

Курс по Elixir 2023, ФМИ

Да се запознаем със структура-грешка

defmodule VeryBadError do
  defexception message: "Лошо!!!"
end
Курс по Elixir 2023, ФМИ
try do
  raise VeryBadError
rescue
  error in VeryBadError ->
    IO.puts(inspect(error, structs: false))
end
#=> %{
#=>    __exception__: true,
#=>    __struct__: VeryBadError,
#=>    message: "Лошо!!!"
#=> }
Курс по Elixir 2023, ФМИ

Да се запознаем със структура-грешка

defmodule EvenWorseError do
  defexception [:message]

  @impl true
  def exception(value) do
    msg = "An even worst error was raised with value #{inspect(value)}"

    %EvenWorseError{message: msg}
  end
end
Курс по Elixir 2023, ФМИ
try do
  raise EvenWorseError, :my_bad
rescue
  error in EvenWorseError ->
    IO.puts(error.message)
end
Курс по Elixir 2023, ФМИ

Throw/Catch

Image-Absolute

Курс по Elixir 2023, ФМИ
  • С throw 'подхвърляме' стойност, която може да се 'хване' по-късно:
try do
  b = 5
  throw b
  IO.puts "this won't print"
catch
  :some_atom           -> IO.puts "Caught #{:some_atom}!"
  x when is_integer(x) -> IO.puts(x)
end
#=> 5
Курс по Elixir 2023, ФМИ
  • Нещо още по-рядко:
try do
  exit("I am exiting") # така можем да излезем от процес
catch
  :exit, _ -> IO.puts "not really"
end # и процесът всъщност остава жив
Курс по Elixir 2023, ФМИ

Сега - забравете за тях и не ги ползвайте.

Image-Absolute

Курс по Elixir 2023, ФМИ
  1. В кода на mix няма прихващане на грешки.
  2. В кода на компилатора на Elixir има само няколко прихващания.
Курс по Elixir 2023, ФМИ

Грешките могат да се третират като съобщения

defmodule Quitter do
  def run do
    Process.sleep(3000)

    exit(:i_am_tired)
  end
end

Process.flag(:trap_exit, true)

spawn_link(Quitter, :run, [])
receive do msg -> IO.inspect(msg); end

# {:EXIT, #PID<0.95.0>, :i_am_tired}
Курс по Elixir 2023, ФМИ

Случай 1: Нормално излизане на процес

action = fn -> :nothing end
system_process = true # Ако е false този процес ще си чака

Process.flag(:trap_exit, system_process)
pid = spawn_link(action)

receive do
  msg -> IO.inspect(msg)
end

# {:EXIT, <pid>, :normal}
Курс по Elixir 2023, ФМИ

Случай 2: Излизане с exit/1

action = fn -> exit(:stuff) end
system_process = true # Ако е false този процес ще умре с ** (EXIT from <pid>) :stuff

Process.flag(:trap_exit, system_process)
pid = spawn_link(action)

receive do
  msg -> IO.inspect(msg)
end

# {:EXIT, <pid>, :stuff}
Курс по Elixir 2023, ФМИ

Случай 3: Излизане с exit(:normal)

  • Аналогично на случай 1:
action = fn -> exit(:normal) end
system_process = true # Ако е false този процес ще си чака

Process.flag(:trap_exit, system_process)
pid = spawn_link(action)

receive do
  msg -> IO.inspect(msg)
end

# {:EXIT, <pid>, :normal}
Курс по Elixir 2023, ФМИ

Случай 4: Излизане с raise

action = fn -> raise("Stuff") end
system_process = true # Ако е false този процес умира с
# [error] Process <pid> raised an exception ** (RuntimeError) Stuff

Process.flag(:trap_exit, system_process)
pid = spawn_link(action)

receive do
  msg -> IO.inspect(msg)
end

# {:EXIT, <pid>,
#   {%RuntimeError{message: "Stuff"},
#     [{:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 668]}]}}
Курс по Elixir 2023, ФМИ

Случай 5: Излизане с throw

action = fn -> throw("Stuff") end
system_process = true # Ако е false този процес умира с
# [error] Process <pid> raised an exception ** (ErlangError) erlang error: {:nocatch, "Stuff"}

Process.flag(:trap_exit, system_process)
pid = spawn_link(action)

receive do
  msg -> IO.inspect(msg)
end

# {:EXIT, <pid>,
#  {{:nocatch, "Stuff"},
#    [{:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 668]}]}}
Курс по Elixir 2023, ФМИ

Функцията Process.exit/2

  • Може да се използва да ликвидираме процес от друг процес
  • Ако извикаме с :kill можем да убием даже процеси, които trap-ват сигнали.
  • Малко повече на тема Process.exit
Курс по Elixir 2023, ФМИ

Supervision и shutdown

  • Child спецификацията идва с опция за shutdown : когато Supervisor трябва да спре под-процес.
  • По дефаулт, когато това се случи Process.exit(child, :shutdown) се праща на процеса и се чака 5 секунди за чистене на ресурси.
  • Ако процесът не хваща сигнали само това ще го убие, но ако хваща след тези 5 минути Process.exit(child, :kill) се праща.
  • Тези пет секунди могат да се променят с число (в ms) или на :infinity.
Курс по Elixir 2023, ФМИ

Supervision и shutdown

  • Можем да сложим и стойността :brutal_kill, тогава направо се праща Process.exit(child, :kill).
  • Process.exit(child, :shutdown) ще наката под-процесът да извика terminate/2 callback функцията.
Курс по Elixir 2023, ФМИ

Вход-изход

Image-Absolute

Курс по Elixir 2023, ФМИ

Вход-изход

  • Изход с IO.puts/2 и IO.write/2
IO.puts("По подразбиране пишем на стандартния изход.")
IO.puts(:stdio, "Можем да го направим и така.")
IO.puts(:stderr, "Или да пишем в стандартния изход за грешки.")
Курс по Elixir 2023, ФМИ
IO.write(:stderr, "Това е грешка!")
Курс по Elixir 2023, ФМИ

Принтиране

  • Първият аргумент на puts и write може да е атом или pid. Нарича се device и представлява друг процес.
  • Вторият аргумент се очаква да е нещо от тип chardata.
Курс по Elixir 2023, ФМИ

Image-Absolute

Курс по Elixir 2023, ФМИ

Какво е chardata?

  • Низ, да речем "Далия".
  • Списък от codepoint-и, да речем [83, 79, 0x53] или [?S, ?O, ?S] или 'SOS'.
  • Списък от codepoint-и и низове - [83, 79, 83, "mayday!"].
  • Списък от chardata, тоест списък от нещата в горните три точки : [[83], [79, ["dir", 78]]].
Курс по Elixir 2023, ФМИ

Какво е chardata?

IO.chardata_to_string([1049, [1086, 1091], "!"])
#=> "Йоу!"
Курс по Elixir 2023, ФМИ

IO.inspect/2

  • Връща каквото му е подадено. Може да се chain-ва.
  • Приема pretty print опции.
  • Приема етикети.
  • Чудесно за debugging.
Курс по Elixir 2023, ФМИ

IO.inspect/2

defmodule TaskEnum do
  def map(enumerable, fun) do
    enumerable
    |> IO.inspect(label: "Input", structs: false)
    |> Enum.map(& Task.async(fn -> fun.(&1) end))
    |> IO.inspect(label: "Tasks", width: 120, limit: :infinity)
    |> Enum.map(& Task.await(&1))
  end
end
Курс по Elixir 2023, ФМИ

Четене от стандартния вход

  • Вход с IO.read/2, IO.gets/2, IO.getn/2 и IO.getn/3
IO.read(:line)
Хей, Хей<enter>
#=> "Хей, Хей\n"
IO.gets("Кажи нещо!\n")
Кажи нещо!
Нещо!<enter>
#=> "Нещо!\n"
Курс по Elixir 2023, ФМИ
  • IO Server Time

Image-Absolute

Курс по Elixir 2023, ФМИ

Още функции

  • Функции като write и read имат версии наречени binwrite и binread.
  • Разликата е, че приемат iodata, вместо chardata.
  • По бързи са. Добри за четене на binary/не-unicode файлове.
Курс по Elixir 2023, ФМИ

Какво е iodata?

  • Подобно на chardata, iodata може да се дефинира като списък.
  • За разлика от chardata, iodata списъкът е от цели числа които представляват байтове (0 - 255),
  • binary с елементи със size, кратен на 8 (могат да превъртат) и такива списъци.
  • Писане без конкатениране? Повече тук
Курс по Elixir 2023, ФМИ

Какво е iodata?

IO.iodata_length([1, 2 | <<3, 4>>])
#=> 4
IO.iodata_to_binary([1, << 2 >>, [[3], 4]])
#=> <<1, 2, 3, 4>>
Курс по Elixir 2023, ФМИ

Файлове

Image-Absolute

Курс по Elixir 2023, ФМИ

Файлове

{:ok, file} = File.open("test.txt", [:write])
#=> {:ok, #PID<0.855.0>}

IO.binwrite(file, "some text!")
#=> :ok

File.close(file)
#=> :ok
Курс по Elixir 2023, ФМИ

Процеси и файлове

  • Така наречения device всъщност е PID на процес или атом, който е ключ, сочещ към PID на процес.
  • Когато отваряме файл се създава нов процес, който знае file descriptor-а на файла и управлява писането и четенето към и от него.
  • Това означава, че всяка операция с файла минава през комуникация между процеси
  • Именно за това има функции, които направо работят с файлове, като File.read/1, File.read!/1, File.write/3, File.write!/3.
Курс по Elixir 2023, ФМИ

Потоци и файлове

{:ok, file} = File.open("program.txt", [:read])
#=> {:ok, #PID<0.82.0>}

IO.stream(file, :line)
|> Stream.map(fn line -> line <> "!" end)
|> Stream.each(fn line -> IO.puts line end)
|> Stream.run()
Курс по Elixir 2023, ФМИ

Потоци и файлове

  • За повече четене тук
File.stream!(input_name, read_ahead: <buffer_size>)
|> Stream.<transform-or-filter>
|> Stream.into(File.stream!(output_name, [:delayed_write]))
|> Stream.run
Курс по Elixir 2023, ФМИ

Модула IO.ANSI

IO.puts [IO.ANSI.blue(), "text", IO.ANSI.reset()]
Курс по Elixir 2023, ФМИ

Модула StringIO и файлове в паметта

{:ok, pid} = StringIO.open("data") #PID<0.136.0>}
StringIO.contents(pid) # {"data", ""}

IO.write(pid, "doom!") #:ok
StringIO.contents(pid) # {"data", "doom!"}
IO.read(pid, :line) # "data"
StringIO.contents(pid) # {"", "doom!"}

StringIO.close(pid) # {:ok, {"", "doom!"}}
Курс по Elixir 2023, ФМИ

Модула StringIO и файлове в паметта

{:ok, file} = File.open("data", [:ram])
#=> {:ok, {:file_descriptor, :ram_file, #Port<0.1578>}}
IO.binread(file, :all)
#=> "data"
IO.binread(file, :all)
#=> ""
Курс по Elixir 2023, ФМИ

Модула StringIO и файлове в паметта

:file.position(file, :bof)
IO.binread(file, :all)
#=> "data"
Курс по Elixir 2023, ФМИ

Модула Path

Path.join("some", "path")
#=> "some/path"
Path.expand("~/development")
#=> "/home/meddle/development"
Курс по Elixir 2023, ФМИ

Портове

Image-Absolute

Курс по Elixir 2023, ФМИ

The End

Image-Absolute

Курс по Elixir 2023, ФМИ