Ability to set custom name for the server
There should be a method, when spawning an IPC server, that lets you pass in your own name. There should be another method that checks if a given name is already used, which I believe your one-shot function uses internally to generate random names until it comes up with one that's free. We have run into a use case where it would be much more preferable to have the server process passed the name of the pipe to use on its command line, rather than have to somehow communicate back to the client what its name should be before actual IPC is established.
It seems that the name generation is a little more involved than the OP describes and depends on the OS and whether the "force-inprocess" feature is enabled:
- macOS constructs and registers a global "bootstrap" name, both using Mach ports
- Windows constructs a pipe ID from a UUID and then creates a named pipe using the pipe ID
- unix creates a UNIX socket and uses the socket path as a name
- "in process" constructs a name from a UUID and stores that in a static hashmap.
So it would seem to be straightforward to set a custom name for Windows and "in process", but possibly more involved for macOS and unix.
Maintainers: what's your policy for implementing features that are not specifically required by Servo? Is this generally acceptable or do you only allow this when there isn't significant impact to the design? I'm trying to gauge whether this feature is likely to be acceptable if someone implemented it. /cc @antrik @pcwalton @jdm @nox @mrobinson
I'm ok with features that Servo doesn't use. There are some if those in the library already.
Thanks @jdm! I'm mulling over some implementation options:
- Support custom server names in the platform layer using a new static hashmap for each OS and leveraging the existing static hashmap for "inprocess" (i.e. four independent implementations), with a thin wrapper in ipc.rs.
- A variation of option 1 using common code (including the new static hashmap) in the platform layer for the three OS's (but still with an independent implementation for "inprocess").
- Support custom server names in ipc.rs using a new static hashmap and building on existing one shot server support in the platform layer.
Option 3 is the most maintainable, but results in two static hashmaps in the "inprocess" case, which could impact performance a little. Option 2 is a bit less maintainable than option 3, but reuses the existing static hashmap in the "in process" case, so would perform a little better in that case than option 3. Option 1 is possibly the least maintainable as it would duplicate code for each OS. The performance of options 1 and 2 is similar, but option 2 introduces common code into the platform layer, which I think deviates from the current design of having independent implementations for each OS and "inprocess".
Do you have a feel for which option(s) is likely to be most acceptable? I'm not sure whether we are aiming for ultimate performance or maintainable code. Also, I'm not sure how wedded the project is to having four independent implementations in the platform layer or whether it might be ok to evolve towards sharing some common code. Or is it preferable to push common code up into ipc.rs?
FWIW I would recommend trying option 3 first and switching to option 2 or 1 if the performance turned out to be unacceptable (which I suspect is unlikely).
(Option 1 might also permit more sophisticated implementations for certain OS's. For example, it may be possible to use named OS primitives for one shot servers with internally generated names and to use other OS primitives for one shot servers with custom names. I am currently ruling out more sophisticated implementations because of the extra complexity.)
@bobi6666 I've been mulling over the ramifications of implementing this feature and I'd appreciate your feedback.
- Currently, there is a theoretical race where
connect()is called concurrently withnew()of the one-shot server, but the race doesn't happen in practice becauseconnect()needs the name returned fromnew(), so effectively these functions must be called in sequence. But suppose there was an additional functionnew_named()which takes a custom name. Then there would be an actual race betweennew_named()andconnect(). Ifconnect()was called too early, it would fail because the name would not be found, but if it was called sufficiently late,connect()would succeed. Essentially, the user would need to synchronise the calls to be sure thatconnect()was called afternew_named(). - One way to alleviate point 1 would be to make
connect()retry internally until the name was available. - Since the internal name has to be a file system path in the case of Unix variants, I think the only feasible way to implement
new_named()would be to create some kind of IPC shared memory (or memory mapped file) containing a mapping from custom name to internal name. There would probably also need to be another variant ofconnect()-- let's call itconnect_named()-- which consults the mapping (and perhaps retries until the name is found as in point 2) and then callsconnect(). - If the implementation really does need to be as in point 3, then effectively there would be a completely independent layer of function consisting of
new_named()(which would callnew()),connect_named()(which would callconnect()), and the IPC shared memory mapping. But this layer could be supplied independently of ipc-channel, either by the user or in a separate repository. - Following on from point 4, it could be argued that such an implementation doesn't belong in ipc-channel, e.g. because different users might want different behaviours. For example, some users might prefer a racing
connect_named()and others might prefer a retryingconnected_named(). For another example, some users might want a retryingconnect_named() to retry indefinitely and others might want it to fail after a timeout value specified as a parameter). Keeping the implementation separate from ipc-channel would give users the freedom to choose whatever behaviours they want. - Finally, note that
new_named()would need to cope with the case where it is called with a name that is already in use. It would probably return an error in this case.
it means that we need a helper crate for this?