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

ServerSocket still relevant #4748

Open
ioquatix opened this issue Aug 23, 2017 · 17 comments
Open

ServerSocket still relevant #4748

ioquatix opened this issue Aug 23, 2017 · 17 comments

Comments

@ioquatix
Copy link
Contributor

I ran into this issue

https://travis-ci.org/socketry/async-http/jobs/267440658#L617-L618

looked at https://github.com/jruby/jruby/wiki/ServerSocket

I thought JRuby uses native UNIX functions where possible?

Just wondering what is considered best practice here.

@headius
Copy link
Member

headius commented Aug 23, 2017

The implementation of Socket in JRuby is a bit troublesome because the JDK separates socket types by class. So if you want to create a server, you need to construct a server socket object. In Ruby, however, you can create a socket object and later decide whether it's a server or a client.

JRuby sacrifices that ability of Ruby by only supporting client connections in the bare Socket class and mentioning the ServerSocket class as an alternative. Unfortunately since this class doesn't exist in regular Ruby, nobody knows about it and nobody uses it.

The async-io library could be modified to use a specific server socket type (like TCPServer), perhaps?

I did have an experiment on a branch where I delayed the creation of the Java socket, but it's really pretty ugly; I have to simulate a bunch of intermediate steps like "bind" and all the errors that go with them, since I can't actually initiate a socket until later.

There is of course the possibility that we could improve our Socket impl to use native functions, but native socket functions are notoriously troublesome to bind through FFI, since they have various struct shapes across platforms and libraries.

@ioquatix
Copy link
Contributor Author

ioquatix commented Feb 2, 2018

I played around with this a bit more. It's troublesome.

The async-io library could be modified to use a specific server socket type (like TCPServer), perhaps?

This isn't really a good option. I'd really have to implement the ServerSocket / ClientSocket split into the core of the io abstraction in order to make it work.

It's probably the only solution that's going to work well, but the problem is core Ruby doesn't make this split, while JRuby does. It's a bit of a mess to be honest.

The only solution I can really see working, is to actually re-implement the socket API from the ground up.

I did have an experiment on a branch where I delayed the creation of the Java socket, but it's really pretty ugly; I have to simulate a bunch of intermediate steps like "bind" and all the errors that go with them, since I can't actually initiate a socket until later.

I tried this too. It was hard work, but it did work for the most part.

There is of course the possibility that we could improve our Socket impl to use native functions, but native socket functions are notoriously troublesome to bind through FFI, since they have various struct shapes across platforms and libraries.

Honestly, I think this is the best route if you want to make things just work without additional work for the user. Or, the above, where you basically cache all the method calls until the actual socket is allocated.

The problem is, it's not clear whether you have a server socket or client socket. That's because a user might call bind (server) or bind & connect (bind to local address for connection). So, it's hard to detect the exact sequence.

The real issue is Java has made a decision about how sockets should work without actually providing the real basic API level functions.

Are there any external libraries you could depend on to provide a proper socket implementation?

@ioquatix
Copy link
Contributor Author

ioquatix commented Feb 2, 2018

Here is the hack which for the most part worked, but failed with UDP sockets.

https://github.com/socketry/async-io/blob/8214304d84111a48560f002c07155129850768d9/lib/async/io/socket.rb#L24-L107

@ioquatix
Copy link
Contributor Author

ioquatix commented Feb 2, 2018

I think that the cross-interpreter costs w.r.t. how the APIs work is stupidly complex for typical developers to manage which is unfortunate.

I could modify async-io to only expose a very limited set of APIs, but my goal was to provide drop-in replacements for core Ruby IO classes.

@headius
Copy link
Member

headius commented Feb 13, 2018

The long term answer is that we need to start moving toward all-native Socket. There's a solid start on this in the rubysl-socket library, but it has a number of dependencies on Rubinius or on having an initial build step per-platform, which we can't provide. The TruffleRuby folks may have a modified version that we could drop in.

I have attempted at various times to make Socket defer the decision whether to use a Java TCPSocket or TCPServer, but it's very hacky and is incompatible with code that attempts to make use of the file descriptor between the time the socket is created and bound.

@ioquatix You might have a look at https://github.com/rubysl/rubysl-socket or at the modified version in TruffleRuby at https://github.com/oracle/truffleruby/tree/master/lib/truffle/socket and see how much work would be needed to get just the basic Socket class working natively.

@eregon
Copy link
Member

eregon commented Jan 4, 2020

FWIW, the version in TruffleRuby is more complete and has less bugs than rubysl-socket (BTW that link seems dead).

@headius
Copy link
Member

headius commented Jan 6, 2020

@ioquatix We have never made a move to using native calls for all socket support because none of the available options work without a build step. JRuby runs out of the box on any system with a JDK because we rely on so many built-in JDK classes (e.g. the socket subsystem) and requiring a build step for native sockets is not in our plan right now.

The plan long term would be to support fully-native socket support, pregenerated for as many platforms as possible, with a fallback to our non-native sockets. Unfortunately we don't have many resources to work on that these days.

@eregon
Copy link
Member

eregon commented Jan 6, 2020

rubysl-socket doesn't require any build step, isn't it? It's just FFI.
But I have not tried whether it works well on Windows and exotic platforms though.

@headius
Copy link
Member

headius commented Jan 6, 2020

@eregon Last time I looked it required a bunch of properties to be set for the shape and sizing of the struct, which are generated at build time on Rubinius (and presumably on TruffleRuby). In any case the structs differ across platforms, so we'd need to have predefined values for any platforms we want to support. I did not see that in rubysl-socket nor in TruffleRuby socket last time I looked.

@headius
Copy link
Member

headius commented Jan 6, 2020

@eregon Example: https://github.com/oracle/truffleruby/blob/master/lib/truffle/socket/socket.rb#L142-L150

We do not have these values set and they would be per-platform, so I assumed TruffleRuby generates them when built for each platform. JRuby has no per-platform build step (outside of jffi and) so we can't provide these values.

@eregon
Copy link
Member

eregon commented Jan 6, 2020

I think those values could be stored in jffi then.
Here is the script generating them: https://github.com/oracle/truffleruby/blob/master/tool/generate-native-config.rb

@headius
Copy link
Member

headius commented Jan 7, 2020

@eregon Ah there's a bit of magic I did not know about. It would be reasonable for us to generate these configs, and when a platform's configs are not present we fall back on the JDK-based sockets. Just need to do something about all those pesky Truffle/Rubinius primitives.

@eregon
Copy link
Member

eregon commented Jan 7, 2020

There are no primitives used in lib/truffle/socket, only FFI.

@headius
Copy link
Member

headius commented Jan 7, 2020

I was referring to things that I believe were primitives in rbx. Perhaps they're not "primitives" in the same sense in TruffleRuby, but they don't exist as standard APIs nor as bound FFI endpoints:

https://github.com/oracle/truffleruby/blob/master/lib/truffle/socket/basic_socket.rb#L76
https://github.com/oracle/truffleruby/blob/master/lib/truffle/socket/basic_socket.rb#L178
https://github.com/oracle/truffleruby/blob/master/lib/truffle/socket/tcp_socket.rb#L48

Granted, most of this code was written just atop FFI originally, and it seems like that's still the case in your improved version. Hopefully we'll have some cycles free to attempt using it in JRuby soon.

@eregon
Copy link
Member

eregon commented Jan 8, 2020

Of these 3 only the first one is not defined directly there, but it's still pure Ruby:
https://github.com/oracle/truffleruby/blob/9b8b8579e86673b5d3e62e08836ee71ebca21af5/src/main/ruby/truffleruby/core/type.rb#L596-L600

The other two are just defined in lib/truffle/socket files. The Truffle prefix is just what replaced the Rubinius or RubySL prefix, it's just a private namespace for helper methods and classes. It would probably make sense to find another naming convention for internal namespaces.

@eregon
Copy link
Member

eregon commented Jan 8, 2020

Longer-term I think it could be cool to make socket a default gem and include this FFI-based implementation in it.

@eregon
Copy link
Member

eregon commented Jan 8, 2020

For clarification, TruffleRuby has primitives, they look like TrufflePrimitive.foo(...). None are used in that code.

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

3 participants