Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPC transport? #3

Open
glyn opened this issue Jan 28, 2025 · 5 comments
Open

IPC transport? #3

glyn opened this issue Jan 28, 2025 · 5 comments

Comments

@glyn
Copy link

glyn commented Jan 28, 2025

(Pardon this issue in lieu of a discussion.)

Is channels-rs limited to TCP as a transport?

Context: https://github.com/servo/ipc-channel implements channels based on operating system IPC constructs rather than TCP.

Possible advantages of an IPC transport:

  • more convenient name generation (rather than using a TCP port which could collide with servers),
  • performance (particularly for servo's usecases).

Note: I'm more interested in positioning channels-rs relative to servo/ipc-channel rather than necessarily proposing a new feature for channels-rs.

@threadexio
Copy link
Owner

Don't worry about the issue vs discussion thing. It's probably time I set up discussions...

To answer your question:

Is channels-rs limited to TCP as a transport?

channels is not limited to just TCP as the underlying transport. It doesn't place any restrictions on the underlying transport. As long as you give it a "reader" that receives data from somewhere and a "writer" that sends data somewhere, channels will work (assuming the underlying implementation of the transport is not broken). The examples only mention TCP because it is the most cross-platform (so we don't have test cases/examples per platform) out-of-process transport. channels can work with UDP for example, but that requires connection tracking and more. I myself have used channels to communicate over Unix sockets for a local daemon.

Regarding servo/ipc-channel, I was not aware of this crate before and it does seem interesting. From the limited reading I've done, it seems like it provides a cross-platform way to get messages across processes. It handles everything from the setting up of the underlying transport to the serialization and deserialization of messages. This is a fundamentally different approach from channels. channels lets you pick and set up a transport medium yourself with any settings you might want.

more convenient name generation (rather than using a TCP port which could collide with servers),

That's true. TCP, for local services at least, is hardly the best option. With TCP you get all of the overhead of the network stack and the TCP protocol itself. However, I'm not quite sure I understand what you mean by "name generation", would you care to explain a bit further?

performance (particularly for servo's usecases).

Performance is an interesting subject. A quick skim through the source code, particularly of IpcSender::send, tells me that the crate uses the native shared memory APIs of the different platforms (I assume System V shm* and/or the classic POSIX mmap). I speculate that servo/ipc-channel is faster than channels at raw throughput of messages simply because shared memory requires less on-going involvement from the kernel. I believe that most modern kernels, on x86_64 at least, implement shared memory by remapping the same physical page(s) of memory into the address space of 2 or more processes. Thus, a change from one process is directly visible in another process without any syscalls or other kernel nonsense, it's just a normal write to memory. channels on the other hand, assuming it is layered on top of a stream-based transport such as pipes or Unix domain sockets, requires 1 syscall to send and 1 to receive a message (at the very least). A system call is orders of magnitude slower than a simple memory read or write (not to mention that channels might have to copy around data in memory). So in that sense, it seems only logical to me for ipc-channel to be more performant than channels. However, I have not done any benchmarking, so take this with a grain of salt.

PS: It would probably be possible to layer channels on shared memory, to be closer to the model ipc-channel uses, but it would probably still be slower.

@glyn
Copy link
Author

glyn commented Jan 28, 2025

Thanks for those details - very useful and interesting.

However, I'm not quite sure I understand what you mean by "name generation", would you care to explain a bit further?

ipc-channel provides a one-shot server to help establish a channel between two processes. When a one-shot server is created, a name of the server is generated. Under the covers this name could be a file system path (for Unix variants, which use Unix sockets as the IPC mechanism, with the file system path bound to the socket) or other kinds of generated names on macOS (which uses Mach ports) and Windows (which uses named pipes).

The server process then calls accept() on the server to accept connect requests from clients and return a pair (receiver end of a new ipc-channel from client to server, first message received from the ipc-channel). The client process calls connect() passing the name and this establishes an ipc-channel from the client to the server and returns the sender end of the channel. Note that there is a restriction in ipc-channel: connect() may only be called at most once per one-shot server.

@glyn
Copy link
Author

glyn commented Jan 28, 2025

PS. I tried linking to this repo in servo/ipc-channel#384.

@threadexio
Copy link
Owner

I think I understand now. So you create this one-shot server and it gives you a handle to the server object and the server name. And then you connect() to the server by passing in the server's name. But if the server name it gives you is essentially opaque data, where is a client supposed to connect if even the server doesn't know its "address" before creating the IPC server? Does it require some other out-of-band channel to transmit that information?

In any case, it is possible to achieve the same behavior with channels. Essentially, you can create an abstract Unix socket and have it listen on some address and clients can connect to that. It's just that with channels, the responsibility of setting up the socket is on you.

@glyn
Copy link
Author

glyn commented Jan 29, 2025

I think I understand now. So you create this one-shot server and it gives you a handle to the server object and the server name. And then you connect() to the server by passing in the server's name. But if the server name it gives you is essentially opaque data, where is a client supposed to connect if even the server doesn't know its "address" before creating the IPC server? Does it require some other out-of-band channel to transmit that information?

I'm not sure I understand the question, but the out of band information may be the server name, which needs to be passed to the client process somehow (e.g. using an environment variable or a command line argument). Maybe these sequence diagrams will give a clue:

Image

Image

In any case, it is possible to achieve the same behavior with channels. Essentially, you can create an abstract Unix socket and have it listen on some address and clients can connect to that. It's just that with channels, the responsibility of setting up the socket is on you.

I see, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants