erlang icon indicating copy to clipboard operation
erlang copied to clipboard

add better support for ports: `open_port`, `port_command`, & others?

Open mjsir911 opened this issue 3 months ago • 4 comments

open_port/2

port_command/2 / port_command/3

and a probably better-formed receive for ffi purposes, my understanding is currently process.receive does no type validation and you have to hope that it matches the shape you're expecting, which is fine when it's only used for inter-gleam RPC but I'm trying to represent these pieces of data erlang's open_port might send:

{Port, {data, Data}}
{Port, {exit_status, Status}}
{Port, eof}
{Port, closed}

{Port, {data, {eol | noeol, Line}}} ???

and maybe others. it's kind of hard to find docs for what open_port subprocesses send back up. going off of https://runebook.dev/en/docs/erlang/doc/reference_manual/ports "Messages Received From a Port" a bit.

and, to be clear, these are very representable in gleam. They just require a decode.run() step on an arbitrary piece of data returned by receive, which it's not doing right now and the Subject(Type) api implies an amount of safety that doesn't exist right now. I guess Subject(Dynamic) is what I want?

semi-related to #28, specifically regarding:

As far as the shape of that solution, I'm not really sure. It feels like there should be an "escape hatch" (even if you have to write some Erlang) for when you want to receive some non-Gleam messages with potentially any shape, but still take advantage of Actors and Subjects. Maybe this is possible already and I just haven't figured it out yet.

mjsir911 avatar Sep 22 '25 23:09 mjsir911

got an MVP design in #90

mjsir911 avatar Sep 22 '25 23:09 mjsir911

Hello! I agree, it would be really fab to have bindings to the ports API in the Gleam ecosystem.

This package doesn't aim to directly expose Erlang APIs as-is, instead we craft APIs more suited to Gleam that take advantage of types and some glue code, to improve the developer experience and to remove any foot-guns and sharp edges. The design in the attached PR takes the faithful-to-Erlang approach and makes it easy to fall victim to the common problems, so it's not ideal for here.

They could be good to have in package you own and publish, and that would be a good place to experiment and explore Gleam-y designs that improve on the Erlang API.

If you are interested in running OS subprocesses generally the existing packages may be sufficient for your needs:

  • https://hexdocs.pm/shellout
  • https://hexdocs.pm/gleamyshell There may be others too that I'm not aware of.

lpil avatar Sep 23 '25 10:09 lpil

Hi! Thanks for the feedback

The design in the attached PR takes the faithful-to-Erlang approach and makes it easy to fall victim to the common problems, so it's not ideal for here.

I'm very open to feedback, will take a look at some other code within the repo to try to figure out what you're talking about, but can appreciate pointers too. It looks like the biggest thing that's being done with this lib's wrapper within ffi is regarding errors, which I've updated.

some other things I could consider that would make things more gleam-like:

  • representing mutually exclusive args with sum types, a-la Packet(Int) vs Stream vs Line(Int) or use_stdio vs nouse_stdio (even though erlang's not raising an error when I use both of these together??)
  • somehow representing bounded number types within the type system? Packet(Int) only accepts 1, 2, or 4
  • split up args into types dependent on PortName. a lot of args are only valid under spawn_executable (args, arg0), or both spawn_executable and spawn (exit_status, cd, env, use_stdio, nouse_stdio,
  • maybe we just don't allow spawn entirely, deferring to spawn_driver or spawn_executable depending on what you want. makes things easier. I guess spawn is doing a few other things, like PATH lookup and argument separation.
  • maybe just ignore fd for now until a usecase comes along.
  • maybe use a builder style for construction the objects. a-la .use_stdio(true), message_format(Packet(Int) | Stream | Line(Int))

They could be good to have in package you own and publish, and that would be a good place to experiment and explore Gleam-y designs that improve on the Erlang API.

I appreciate it, I'm mostly just working on this code as a necessity for a greater project and don't really care to go down the path of publishing my own things, putting it in here / in the PR for others to use. I'm using the gleam package fork support with erlang = { git= "https://github.com/mjsir911/erlang.git", ref="main" }, and invite others to do the same, although it's going to be annoying and outdated because the preference is definitely upstreaming it into the canonical gleam <-> erlang library

doubly so when I want to depend on things like gleam_erlang_ffi:identity and test how the open_port stuff interacts with a lot of the process tooling like receive and maybe select*

If you wouldn't like to give me pointers about creating a PR that "crafts APIs more suited to Gleam", we can close both this issue and the PR and then I can come back later with a fully written out proposal?

Cheers,

mjsir911 avatar Sep 23 '25 16:09 mjsir911

One of the main things I'd like to fix is how easy it is to accidentally create orphaned processes, as it's one of the most common problems people face when using ports. Elixir's documentation has some good information here: https://hexdocs.pm/elixir/main/Port.html#module-orphan-operating-system-processes

All your ideas there sound like things worth exploring!

lpil avatar Oct 02 '25 09:10 lpil