|
| 1 | +# Освобождение ресурсов |
| 2 | + |
| 3 | +Процесс освобождения ресурсов является распространённой задачей: закрыть файловый дескриптор, |
| 4 | +закрыть соединение и т.д.. |
| 5 | + |
| 6 | +При ручном освобождении ресурсов (она же функция `close`) могут |
| 7 | +возникнуть множество проблем — это можно забыть сделать или закрыть слишком рано. |
| 8 | + |
| 9 | + |
| 10 | +:::details Пример ручного закрытия файла |
| 11 | +```ocaml |
| 12 | +let read_first_line_from_file filename = |
| 13 | + let file = open_in filename in |
| 14 | + let line = input_line file in |
| 15 | + close_in file; |
| 16 | + line |
| 17 | +``` |
| 18 | +::: |
| 19 | + |
| 20 | +> [!INFO] Как это в других языках? |
| 21 | +> |
| 22 | +> В языке вроде C++ или Rust для это существует нативная поддержка механизма [RAII], |
| 23 | +> но он не сильно подходит средам с автоматическим управлением памятью (т.е [GC]). |
| 24 | +
|
| 25 | +Далее приведены идиомы и возможные варианты как это делать правильно на OCaml. |
| 26 | + |
| 27 | +## Функция открытия — также функция закрытия |
| 28 | + |
| 29 | +> [!NOTE] Смотрите также |
| 30 | +> - [Channels: Безопасная работа](./channels.md#безопасная-работа) |
| 31 | +> - [Switches](#switches) — далее |
| 32 | +
|
| 33 | +> [!NOTE] Пример |
| 34 | +> Абстрактный пример того, как реализуется идиоматичная функции автоматического освобождения |
| 35 | +> некоторого ресурса. |
| 36 | +> ```ocaml |
| 37 | +> let initialize _ = (* ... *) |
| 38 | +> let dispose _ = (* ... *) |
| 39 | +> |
| 40 | +> let with_initialize _ f = |
| 41 | +> let resource = initialize _ in |
| 42 | +> Fun.protect |
| 43 | +> (fun () -> f resource) |
| 44 | +> ~finally:(fun () -> dispose resource) |
| 45 | +> ``` |
| 46 | +
|
| 47 | +Данный шаблон вы можете встретить как в стандартной библиотеке, так и в множестве других, |
| 48 | +ибо он позволяет правильно завершить код даже в случае исключения внутри callback-функции. |
| 49 | +
|
| 50 | +## Освобождение при уничтожении объекта |
| 51 | +
|
| 52 | +Используя интерфейс сборщика мусора (модуль [Gc](https://ocaml.org/manual/api/Gc.html)) мы можем установить функцию, |
| 53 | +что должна быть выполнена во время освобождения объекта сборщика мусора. |
| 54 | +
|
| 55 | +Работает исключительно на heap-allocated объектах! |
| 56 | +
|
| 57 | +> [!NOTE] Материалы |
| 58 | +> Для этого смотрите функции [`finalise`](https://ocaml.org/manual/5.2/api/Gc.html#VALfinalise) и |
| 59 | +> [`alarm`](https://ocaml.org/manual/5.2/api/Gc.html#TYPEalarm). |
| 60 | +
|
| 61 | +> [!IMPORTANT] Стоит понимать |
| 62 | +> Освобождения ресурса произойдёт только тогда, когда OCaml решит очистить объект, |
| 63 | +> а это может не произойти вообще. Смотрите [Memory management](./performance.md#memory-management). |
| 64 | +
|
| 65 | +> [!TIP] Библиотека Lwt |
| 66 | +> В пакете `lwt.unix` (часть [Lwt](../libraries/concurrency/lwt.md)) есть модуль `Lwt_gc` и функция `finalise_or_exit`, которая гарантирует, что |
| 67 | +> при завершении программы ресурс будет освобождён (будет вызвана функция, в которой произойдёт освобождение). |
| 68 | +> |
| 69 | +> :::details Пример |
| 70 | +> ```ocaml |
| 71 | +> let main = |
| 72 | +> let res = String.make 10 'x' in |
| 73 | +> Lwt_gc.finalise_or_exit (Lwt_io.printlf "free '%s'") res; |
| 74 | +> Lwt.return_unit |
| 75 | +> ``` |
| 76 | +> ::: |
| 77 | +
|
| 78 | +## Switches |
| 79 | + |
| 80 | +**Switch** (свитч) — область видимости, к которой привязываются ресурсы, и по завершению которой ресурсы должны будут быть освобождены. |
| 81 | +
|
| 82 | +Эдакая прокаченная версия [with-функций](#функция-открытия--также-функция-закрытия) и [finalise из Gc](#освобождение-при-уничтожении-объекта). |
| 83 | +
|
| 84 | +Этот паттерн можно особенно ярко встретить в библиотеке [Eio](../libraries/concurrency/eio.md). |
| 85 | +Он там повсеместен и без него нельзя ничего сделать, так как ресурс должен быть к чему-то привязан. |
| 86 | +
|
| 87 | +> [!NOTE] Пример использования Eio.Switch |
| 88 | +> ```ocaml |
| 89 | +> let run_client ~net ~addr = |
| 90 | +> Switch.run ~name:"client" @@ fun sw -> |
| 91 | +> traceln "Client: connecting to server"; |
| 92 | +> let flow = Eio.Net.connect ~sw net addr in |
| 93 | +> (* Read all data until end-of-stream (shutdown): *) |
| 94 | +> traceln "Client: received %S" (Eio.Flow.read_all flow) |
| 95 | +> ``` |
| 96 | +
|
| 97 | +> [!NOTE] Пример использования Lwt_switch |
| 98 | +> В [Lwt] тоже можно найти свитчи как доп. абстракции — модуль [Lwt_switch]. |
| 99 | +> ```ocaml |
| 100 | +> let main = |
| 101 | +> Lwt_switch.with_switch @@ fun sw -> |
| 102 | +> let* file = Lwt_io.open_file "some-file" ~mode:Output in |
| 103 | +> Lwt_switch.add_hook (Some sw) (fun () -> Lwt_io.close file); |
| 104 | +> Lwt.return_unit |
| 105 | +> ``` |
| 106 | +
|
| 107 | +
|
| 108 | +
|
| 109 | +[RAII]: https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D1%80%D0%B5%D1%81%D1%83%D1%80%D1%81%D0%B0_%D0%B5%D1%81%D1%82%D1%8C_%D0%B8%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F |
| 110 | +[GC]: https://ru.wikipedia.org/wiki/%D0%A1%D0%B1%D0%BE%D1%80%D0%BA%D0%B0_%D0%BC%D1%83%D1%81%D0%BE%D1%80%D0%B0 |
| 111 | +
|
| 112 | +[Lwt]: ../libraries/concurrency/lwt.md |
| 113 | +[Lwt_switch]: https://ocsigen.org/lwt/latest/api/Lwt_switch |
0 commit comments