-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
[cancelled, not using Crystal anymore] Add abstract unix socket handling #4056
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bunch of comments everywhere, mostly if can be simplified, and documentation kept as it was, but document that the socket can be abstract, then explain how different it is from normal UNIX socket, and maybe how it works (briefly).
BTW I'm not sure there is a test that verifies that we can create a STREAM server, connect a client, then transmit data? Maybe a test with DGRAM would be good, too.
spec/std/socket_spec.cr
Outdated
addr2.path.should eq(addr1.path) | ||
addr2.abstract?.should be_true | ||
addr2.abstract?.should eq(addr1.abstract?) | ||
addr2.to_s.should eq("/abstract-service.sock") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll may want to check whether sockaddr_un.sun_path
actually got the null byte prepended, that addr.path
didn't, and that #abstract?
is true. Other expectations are redundant with the other test.
spec/std/socket_spec.cr
Outdated
File.exists?(path).should be_false | ||
end | ||
|
||
File.exists?(path).should be_false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may get rid of the outer File.exists?
expectations.
spec/std/socket_spec.cr
Outdated
@@ -256,6 +296,19 @@ describe UNIXServer do | |||
end | |||
end | |||
|
|||
it "returns an abstract client UNIXSocket for abstract server" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, good test. I didn't thought about that 👍
spec/std/socket_spec.cr
Outdated
@@ -289,6 +342,19 @@ describe UNIXServer do | |||
end | |||
end | |||
|
|||
it "returns an abstract client UNIXSocket for abstract server" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe redundant. Sure it tests with #accept?
but maybe we can skip that, since #accept
will always call #accept?
anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know I saw that too, but as there was already 2 redundant tests for non-abstract sockets:
- describe "accept" > it "returns the client UNIXSocket"
- describe "accept?" > it "returns the client UNIXSocket"
I wanted to have some coherence and keep 2 redundant tests for abstract sockets... So I will remove the accept?
test, but should I remove also the accept?
test for non-abstract socket, which is redundant too ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, remove this test only.
src/socket/unix_server.cr
Outdated
# Always deletes any existing filesystam pathname first, in order to cleanup | ||
# Note for non-abstract socket: | ||
# - The socket is bound to a filesystem pathname. | ||
# - It always deletes any existing filesystem pathname first, in order to cleanup | ||
# any leftover socket file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd keep the original phrasing (more common), then add a note about abstract socket acting on virtual files (less common), thus not creating nor deleting anything.
src/socket/unix_socket.cr
Outdated
def initialize(@path : String, type : Type = Type::STREAM) | ||
# Connects a named UNIX socket. | ||
# | ||
# A non-abstract socket is bound to a filesystem pathname. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as other places: keep the original phrasing; abstract sockets are a special case, and a note on the class if enough to understand that they won't create an actual filesystem filename.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here, do I say Connects a named UNIX socket, bound to a filesystem pathname.
(which is not always true)
or Connects a named UNIX socket, bound to a pathname.
(I removed the filesystem
mention) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep the original comment, but add a note to the UNIXSocket
class about abstract
(as for UNIXServer
).
src/socket/unix_socket.cr
Outdated
# it is called from UNIXServer#new, where @abstract is already initialized | ||
# but if I remove the abstract argument here, I get the error saying that | ||
# @abstract is not initialized in all initialize... (See issue #4055) | ||
protected def initialize(family : Family, type : Type, @abstract = false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see what's to fix. UNIXServer shouldn't set @abstract
but pass abstract
to the super
call, so it will be set, here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh I see, will do that
5ad7b05
to
c85d65a
Compare
Ok, I think I got them all, thanks for your suggestions! I only spec'ed STREAM socket type, because when you say in #3750:
Your example using require "socket"
server = UNIXServer.new("/tmp/test.sock", type: Socket::Type::DGRAM)
server.close Crash with: Handling niceties (with UNIXServer and 'high level' objects) with DGRAM socket type is not trivial, I think it should involve another PR for DGRAM support for (non-)abstract sockets (and we could reopen #3214 to continue discussion, or open a new issue) |
Ping Is there something missing? From the man:
I don't think the ci kernel is before 2.2, and I have no clue what's broken.. |
Thanks @ysbaddaden! |
This looks good now, but I didn't realize abstract UNIX sockets were Linux specific. They aren't supported by any other unices (e.g. macOS, BSD), and certainly not standardized. In this light, the |
Oh didn't realized that too
What you are suggesting is to drop the In |
I have a patch against your work. I'll push it a bit later. |
@ysbaddaden do you have an update on this ? |
Weird. I did push and verified I did. I must have pushed to the wrong origin. I'll check when I'm back to my computer. |
@RX14 would you like to re-review this? |
I rebased & squashed the commits. I think that we should discuss how we want to handle this, as it's not available in all plateform (it's only a linux thing). Also, I think the Socket stuff (especially the easy wrapper classes like UNIXServer, TCPSocket) should change a little (see my comment here #4317 (comment)). |
bfcda1d
to
e4de6e2
Compare
I still believe abstraction should still be supported transparently using an ASCII symbol prefix such as Impact on the existing API would be minimal, and support for Linux transparent. |
Usually i'd disagree, we should use named arguments instead of cryptic symbols in strings, but in fact the fact that this is an abstract unix socket is simply part of the name, placing it in another namespace. The problem is though, that you can have a file called So i don't know. Perhaps This way is better and |
Now I think that we should keep it simple and compile-time safe, so using a named arg to create an abstract Regarding availability, I think it is wrong to raise at runtime when we can know at compile-time that it is not supported. # In stdlib
{% if flag?(:linux) %}
ABSTRACT_UNIX_AVAILABLE = true
{% else %}
ABSTRACT_UNIX_AVAILABLE = false
{% end %}
# In user code
{% if ABSTRACT_UNIX_AVAILABLE %}
# Use abstract unix socket
{% else %}
# Use normal unix socket
{% end %} |
No, code that compiles on one platform should compile on another. |
That's just more complex, and requires to adapt the user facing ABI to accommodate a feature only available on one specific platform, and documented as non portable. I believe using a leading |
@ysbaddaden The problem is that it just pushes the complexity on the user: now they have to manually add the null byte if they want an abstract socket instead of using a boolean and just using a named arg. |
Isn't that acceptable? After all, the user decided to deal with the complexity of a non portable feature. My take is: let's take care to not break this if we can, but let's not bother to integrate it nicely. |
I'm on the fence. I guess it's all bikeshedding on a very rare feature in the end. So let's implement the simplest method: allow a NUL at the start of the string and document the |
dc2dc7b
to
9bcdc01
Compare
I'm ok with that, I implemented it, let me know how you feel about it. For the specs on abstract socket, as they'll ultimatly fail on anything that's not linux, do I wrap them in Or should I hide the check somewhere in addr = UNIXAddress.new "\0/tmp/some_path"
addr.abstract? # => true (on Linux)
addr.abstract? # => false (on non-Linux) If the path is usable as a non-abstract socket the same code could work seamlessly. |
9bcdc01
to
c81e437
Compare
You're right @RX14 thanks, I won't touch anything for a few days until more people gave their opinion and (hopefully) a consensus is found! Edit: I'll only update little fixes that do not change the objective ;) |
spec/std/socket_spec.cr
Outdated
end | ||
{% else %} | ||
it "raises for abstract UNIX address on non-Linux" do | ||
expect_raises(ArgumentError, /not supported/) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need a regexp here, using "not supported"
as argument will match if the string is included in the error message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, I though using a String
would make it a exact match check. Stdlib is well done, it's an inclusion check, thanks 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's at least one other place in this file where a Regex
is used instead of a String
for a similar case, but I think it's not in this PR's scope to change that, I'm just mentioning it so that the knowledge is shared (no idea if it's helpful at all ^^)
crystal/spec/std/socket_spec.cr
Line 160 in 1978c8b
expect_raises(Socket::Error, /Invalid IP address/) do |
spec/std/socket_spec.cr
Outdated
end | ||
end | ||
|
||
it "is not abstract on non-Linux" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason this example should not run on linux target?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no indeed
server.abstract?.should be_false | ||
end | ||
end | ||
{% end %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two macro branches can be combined.
spec/std/socket_spec.cr
Outdated
File.exists?(path).should be_false | ||
|
||
UNIXServer.open('@' + path) do | ||
File.exists?(path).should be_false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe also doublecheck that '@' + path
doesn't exist either? Might be paranoid...
src/socket/address.cr
Outdated
raise ArgumentError.new("Path size exceeds the maximum size of #{MAX_PATH_SIZE} bytes") | ||
end | ||
|
||
if path[0]? == '@' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
path.starts_with?('@')
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I didn't want to do that because unconsciously for me starts_with?
is to check that it starts with a String
, not a Char
....
Nevermind I'll change it.
src/socket/address.cr
Outdated
@path = String.new(sockaddr.value.sun_path.to_unsafe) | ||
|
||
path = sockaddr.value.sun_path | ||
if path[0]? == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
path.starts_with?(Char::ZERO)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
path
is not a String
here, it's a StaticArray(UInt8)
.
Based on that, you made me think that it would be even more correct to check for equality with 0_u8
(even though there's an automatic conversion happening here somewhere)
src/socket/address.cr
Outdated
io << path | ||
end | ||
|
||
def to_unsafe : LibC::Sockaddr* | ||
sockaddr = Pointer(LibC::SockaddrUn).malloc | ||
sockaddr.value.sun_family = family | ||
sockaddr.value.sun_path.to_unsafe.copy_from(@path.to_unsafe, @path.bytesize + 1) | ||
|
||
destination = sockaddr.value.sun_path.to_unsafe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sun_path
is a LibC::Char[?]
: Use a Slice with #to_slice
instead of raw #to_unsafe
Pointer.
src/socket/address.cr
Outdated
destination[0] = 0_u8 | ||
destination += 1 | ||
end | ||
destination.copy_from(@path.to_unsafe, @path.bytesize + 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is the reason you should prefer Slice: Your write to destination
is bound to the size of @path
, and not checked to the size of destination
. You get this check for free with Slice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @Papierkorb !
What about @path
should I keep the #to_unsafe
? (I think it's not harmful here, but maybe there's a better way I don't see?)
Also, I can't remember/can't find what this + 1
in @path.bytesize + 1
for the count is for, it seems to work without it, soo... If there's no objection I'll remove it with the .to_slice
fix soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I can't remember/can't find what this + 1 in @path.bytesize + 1 for the count is for, it seems to work without it
Looks like it was intended to copy the trailing NUL
. You don't "need" it right now cause the call to Pointer#malloc
above asks BoehmGC to allocate it, and BoehmGC zeros memory for you. Thus, you should either 1) Copy the trailing NUL
2) or set it manually. Not doing so makes that code rely on an implementation detail of the current GC implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The @path.to_unsafe
could be replaced with String#to_slice
. I'm not sure if the returned slice contains the trailing NUL
, please verify that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Papierkorb the returned slice doesn't contain the null - why would it? Slice is a crystal type and crystal doesn't use null terminated strings. The null is only there as a convenience to C.
IMHO: you can drop most of the PR, and merely:
That is, a best effort to support the abstract namespace for UNIX sockets on Linux with minimal changes. I don't think we have to document it, either, but if we do, a oneliner would be enough:
|
I want to weigh in on the usage of |
df87705
to
5f2f223
Compare
src/socket/unix_server.cr
Outdated
@@ -24,17 +24,21 @@ class UNIXServer < UNIXSocket | |||
# Creates a named UNIX socket, listening on a filesystem pathname. | |||
# | |||
# Always deletes any existing filesystam pathname first, in order to cleanup | |||
# any leftover socket file. | |||
# | |||
# Note: An abstract UNIX server act on virtual files, thus not creating nor deleting anything. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note
-> NOTE
I agree, maybe we can allow UNIXAddress creation with a The rules I see would be:
This would allow to do this PR with minimal changes as @ysbaddaden described, but still allow simple use of abstract unix sockets IMO. |
@ysbaddaden I tried to "drop most of the PR" to have only minimal changes here, but there are edge cases that makes me re-add most of the things that I dropped.. Like the simple fact that a I used the same specs as this PR, but tried to keep the other changes simple & small. If some wants to make comments on this other implementation tentative, tell me and I can open another PR (or push it here?). If someone wants to give it a try, grab the spec file of this PR and try to make minimal changes to make it work, maybe I missed something obvious that you'll do 😃 |
@jwoertink Since both approvals where given, the code has changed a lot. It needs a rebase first and then two new reviews. |
Hello, I'm cleaning up old PRs of mine on projects I don't follow anymore. What do we do with this PR ? Close? or someone wants to take it? |
Following my last comment, I'm cleaning up my old PRs, and this seems to be going nowhere.. If anyone wants to ever implement this, please re-open the PR as yourself. |
Will fix #3936
Here is my first PR, I hope everything is good!
There are 2 things where I need review (there are comments in the code):
unix_socket.cr
, about uninitialized variable (see Allow uninitialized instance variables in protected constructors #4055)address.cr
, about little speed improvementIf there are some specs missing, tell me and I'll add them
I'll squash the commits in the end, when we're good!
Thanks in advance!