{"id":18151186,"url":"https://github.com/msantos/inert","last_synced_at":"2025-08-21T05:32:46.560Z","repository":{"id":10380647,"uuid":"12527367","full_name":"msantos/inert","owner":"msantos","description":"An Erlang library for notification of events on file descriptors","archived":false,"fork":false,"pushed_at":"2024-06-14T18:35:35.000Z","size":131,"stargazers_count":21,"open_issues_count":0,"forks_count":7,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-10T18:51:48.771Z","etag":null,"topics":["fd"],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/msantos.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-09-01T21:51:34.000Z","updated_at":"2024-08-22T22:11:03.000Z","dependencies_parsed_at":"2024-06-14T19:48:20.391Z","dependency_job_id":"23521d8b-954d-4952-b0e1-4d484c2e7d49","html_url":"https://github.com/msantos/inert","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/msantos/inert","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msantos%2Finert","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msantos%2Finert/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msantos%2Finert/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msantos%2Finert/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/msantos","download_url":"https://codeload.github.com/msantos/inert/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msantos%2Finert/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271430820,"owners_count":24758380,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fd"],"created_at":"2024-11-02T01:06:52.959Z","updated_at":"2025-08-21T05:32:46.241Z","avatar_url":"https://github.com/msantos.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Package Version](https://img.shields.io/hexpm/v/inert)](https://hex.pm/packages/inert)\n[![Hex Docs](https://img.shields.io/badge/hex-docs)](https://hexdocs.pm/inert/)\n\ninert is a library for asynchronous notification of events on file\ndescriptors. To be scheduler friendly, inert uses the native Erlang\nsocket polling mechanism.\n\n# QUICK USAGE\n\n```erlang\nSTDOUT = 1,\nok = inert:start(),\n{ok,read} = inert:poll(STDOUT).\n```\n\n# BUILD\n\n```\n$ rebar3 compile\n```\n\n# OVERVIEW\n\ninert sends your process a message whenever an event occurs on a\nnon-blocking file descriptor. You'll need another library to open the\nfd's (see *ADDITIONAL LIBRARIES*). For example, using inet:\n\n```erlang\n{ok, Socket} = gen_tcp:listen(1234, [binary, {active,false}]),\n{ok, FD} = inet:getfd(Socket).\n```\n\nBoth inet and inert use erts for polling, so stealing fd's may generate\nerror reports.\n\n# ADDITIONAL LIBRARIES\n\n* network sockets\n\n  https://github.com/msantos/procket\n\n* serial devices\n\n  https://github.com/msantos/srly\n\n* TUN/TAP devices\n\n  https://github.com/msantos/tunctl\n\n# EXPORTS\n\n## inert\n\n```\nstart() -\u003e ok\n\n    Start the inert service.\n\nstop() -\u003e ok\n\n    Stop the inert service.\n\npoll(FD) -\u003e {ok,read} | {error, Error}\npoll(FD, Mode) -\u003e {ok, Mode} | {error, Error}\npoll(FD, Mode, Timeout) -\u003e {ok, Mode} | {error, Error}\n\n    Types   FD = integer()\n            Mode = read | write | read_write\n            Timeout = timeout()\n            Error = closed | timeout | ebadf | einval\n\n    poll/2,3 blocks until a file descriptor is ready for reading\n    or writing (default mode: read).\n\n    The timeout option will interrupt poll/3 after the specified\n    timeout (in milliseconds), returning the tuple {error, timeout}.\n\npollid() -\u003e PollId\n\n    Types   PollId = port()\n\n    Retrieves the port identifier for the inert driver.\n\nfdset(FD) -\u003e ok | {error, Error}\nfdset(FD, Mode) -\u003e ok | {error, Error}\n\n    Types   FD = integer()\n            Mode = read | write | read_write\n            Error = closed | ebadf | einval\n\n    Monitor a file descriptor for events (default mode: read).\n\n    fdset/1,2 will send one message when a file descriptor is ready\n    for reading or writing:\n\n        {inert_read, PollId, FD}   % fd is ready for reading\n        {inert_write, PollId, FD}  % fd is ready for writing\n\n    Use pollid/0 to retrieve the descriptor for the inert driver.\n\n    When requesting a monitoring mode of read_write, the calling\n    process may receive two messages (one for read, one for write).\n\n    Further events are not monitored after the message is sent. To\n    re-enable monitoring, fdset/2,3 must be called again.\n\n    Successive calls to fdset/2,3 reset the mode:\n\n        fdset(FD, [{mode, read_write}]),\n        fdset(FD, [{mode, write}]).\n        % monitoring the fd for write events only\n\nfdclr(FD) -\u003e ok | {error, Error}\nfdclr(FD, Mode) -\u003e ok | {error, Error}\n\n    Types   FD = integer()\n            Mode = read | write | read_write\n            Error = closed | ebadf | einval\n\n    Clear an event set for a file descriptor (default: read_write).\n```\n\n## prim_inert\n\n```\ncontrolling_process(Port, PID) -\u003e ok | {error, Error}\n\n    Types   Port = port()\n            PID = pid()\n            Error = not_owner | einval\n\n    Transfer ownership of the inert port driver from the current\n    process to another process.\n\n    Since any process can use the port, controlling_process/2 just\n    sets the port owner and links the process to the port. The\n    original owner will continue to receive messages for any file\n    descriptors it has added to the pollset.\n```\n\n## inert_drv\n\ninert_drv is a wrapper around the `driver_select()` function in the\nerl_driver API. See:\n\nhttp://www.erlang.org/doc/man/erl_driver.html#driver_select\n\n# EXAMPLES\n\nRun:\n\n```\nerlc -I deps -o ebin examples/*.erl\n```\n\n## echo server\n\nSee `examples/echo.erl`. To run it:\n\n```erlang\nerl -pa ebin\n1\u003e echo:listen(1234).\n```\n\n## Connecting to a port\n\nThis (slightly terrifying) example uses the BSD socket interface in\nprocket to connect to SSH on localhost and read the version header. It\nis the equivalent of:\n\n```erlang\n{ok, Socket} = gen_tcp:connect(\"localhost\", 22, [binary, {active,false}]),\ngen_tcp:recv(Socket, 0).\n```\n\n```erlang\n-module(conn).\n-include_lib(\"procket/include/procket.hrl\").\n\n-export([ssh/0]).\n\n-ifndef(SO_ERROR).\n-define(SO_ERROR, 4).\n-endif.\n\nssh() -\u003e\n    ok = inert:start(),\n    {ok, Socket} = procket:socket(inet, stream, 0),\n    Sockaddr = \u003c\u003c(procket:sockaddr_common(?PF_INET, 16))/binary,\n            22:16,          % Port\n            127,0,0,1,      % IPv4 loopback\n            0:64\n        \u003e\u003e,\n    ok = case procket:connect(Socket, Sockaddr) of\n        ok -\u003e\n            ok;\n        {error, einprogress} -\u003e\n            poll(Socket)\n    end,\n    {ok,read} = inert:poll(Socket, read),\n    procket:read(Socket, 16#ffff).\n\npoll(Socket) -\u003e\n    {ok,write} = inert:poll(Socket, write),\n    case procket:getsockopt(Socket, ?SOL_SOCKET, ?SO_ERROR, \u003c\u003c\u003e\u003e) of\n        {ok, _Buf} -\u003e\n            ok;\n        {error, _} = Error -\u003e\n            Error\n    end.\n```\n\n# ALTERNATIVES\n\nSo why would you use `inert` instead of `inet`?\n\n* You have to monitor socket events without using inet. For example,\n  to use socket interfaces like sendmsg(2) and recvmsg(2).\n\n* You want to experiment with alternatives to inet. It'd be interesting\n  to experiment with moving some of the network code from C to Erlang.\n\nOtherwise, there are a few builtin methods for polling file descriptors\nin Erlang. All of these methods will read/write from the socket on\nyour behalf. It is your responsibility to close the socket.\n\n## gen_udp:open/2\n\nWorks with inet and inet6 sockets and supports the flow control mechanisms\nin inet (`{active, true}`, `{active, once}`, `{active, false}`).\n\n```erlang\nFD = 7,\n{ok, Socket} = gen_udp:open(0, [binary, {fd, FD}, inet]).\n```\n\n`inet` is a big, complicated driver. It expects to be receiving TCP or\nUDP data. If you pass in other types of packets, you may run into some\nweird behaviour. For example, see:\n\nhttps://github.com/erlang/otp/commit/169080db01101a4db6b1c265d04d972f3c39488a#diff-a2cead50e09b9f8f4a7f0d8d5ce986f7\n\n## erlang:open_port/2\n\nWorks with any type of non-blocking file descriptor:\n\n```erlang\nFD = 7,\nPort = erlang:open_port({fd, FD, FD}, [stream,binary]).\n```\n\nPorts do not have a built in way to do flow control (inet is a port but\nthe flow control is done within the driver). Flow control can be done\nby closing the port and re-opening it after the data in the mailbox has\nbeen processed:\n\n```erlang\n% synchronous close of port, flushes buffers\nerlang:port_close(Port),\n% process data\nPort1 = erlang:open_port({fd, FD, FD}, [stream,binary]).\n\n% async close\nPort1 ! {self(), close}\n% process data\nPort2 = erlang:open_port({fd, FD, FD}, [stream,binary]).\n```\n\n## Busy waiting\n\nAnd of course, the simple, dumb way is to spin on the file descriptor:\n\n```erlang\nspin(FD) -\u003e\n    case procket:read(FD, 16#ffff) of\n        {ok, \u003c\u003c\u003e\u003e} -\u003e\n            ok = procket:close(FD),\n            ok;\n        {ok, Buf} -\u003e\n            {ok, Buf};\n        {error, eagain} -\u003e\n            timer:sleep(10),\n            spin(FD);\n        {error, Error} -\u003e\n            {error, Error}\n    end.\n```\n\n# Behaviour of driver_select()\n\nThese are some notes describing the empirical behaviour of\n`driver_select()`.\n\nThe function signature for `driver_select()` is:\n\n```C\nint driver_select(ErlDrvPort port, ErlDrvEvent event, int mode, int on)\n```\n\n* the `return value` is either 0 or -1. However, it will only return\n  error in the case the driver has not defined the `ready_input` callback\n  (for the `ERL_DRV_READ` mode) or the `ready_output` callback (for the\n  `ERL_DRV_WRITE` mode)\n\n* on Unix platforms, `event` is simply a file descriptor which is\n  represented as an int but the size of the `ErlDrvEvent` type varies:\n\n  * int (32/64-bit): 4 bytes\n  * ErlDrvEvent (32-bit VM): 4 bytes\n  * ErlDrvEvent (64-bit VM): 8 bytes\n\n  The ErlDrvEvent type needs to be cast or included in a union:\n\n  ```C\n    typedef union {\n        ErlDrvEvent ev;\n        int32_t fd;\n    } inert_fd_t;\n  ```\n\n* `mode` sets which events the file descriptor will be monitored:\n\n  * `ERL_DRV_READ`: call the `ready_input` callback when the file\n    descriptor is ready for reading\n\n  * `ERL_DRV_WRITE`: call the `ready_output` callback when the file\n    descriptor is ready for writing\n\n  * `ERL_DRV_USE`: call the `stop_select` callback when it is safe for\n    the file descriptor to be closed\n\n  The modes in successive calls to `driver_select()` are OR'ed with the\n  previous value.\n\n  ```C\n    driver_select(port, event, ERL_DRV_READ, 1)\n    // mode = read\n    driver_select(port, event, ERL_DRV_WRITE, 1)\n    // mode = read, write\n  ```\n\n* the `on` argument is used to either set (1) or clear (0) modes from\n  an event\n\nThe only modes defined for old drivers was monitoring for read and write\nevents. An additional mode (`ERL_DRV_USE`) was introduced to allow the\ndriver to indicate to the VM when it is safe to close events, necessary on\nSMP VMs where one thread may close an event that another thread is using.\n\nTo support old drivers, the `ERL_DRV_USE` mode is not required. Any use\nof the `ERL_DRV_USE` mode (either setting or clearing) results in the\nVM issuing a warning if the driver does not support the `stop_select`\ncallback.\n\nThe interaction between `mode` and `on`:\n\n* mode:`ERL_DRV_READ`, `ERL_DRV_WRITE` `ERL_DRV_USE`, on:1\n\n  The mode is OR'ed with the existing mode for the event. If `ERL_DRV_USE`\n  was not previously set, the VM will now check for the existence of a\n  `stop_select` callback and issue a warning if it does not exist.\n\n* mode:`ERL_DRV_USE`, on:1\n\n  Setting the mode for the event to only `use` indicates to the VM that\n  the event will be re-used. Presumably the VM will not de-allocate\n  resources for the event.\n\n* mode:`ERL_DRV_READ`, `ERL_DRV_WRITE`, on:0\n\n  The mode is NOT'ed from the existing mode for the event.\n\n* mode:`ERL_DRV_USE`, on:0\n\n  Indicate to the VM that resources associated with the event can be\n  scheduled for de-allocation. When all uses of the event have completed,\n  the VM will call the driver's `stop_schedule` callback.\n\n# TODO\n\n* pass in sets of file descriptors\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsantos%2Finert","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmsantos%2Finert","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsantos%2Finert/lists"}