-
-
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
Seperate Socket and FileDescriptor IO #4707
Conversation
src/socket.cr
Outdated
# see IO::FileDescriptor#unbuffered_write | ||
if (writers = @writers) && !writers.empty? | ||
add_write_event | ||
write_syscall_helper(slice, "Error sending message") do |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.
I'm not very sure this behaviour is correct, as write_syscall_helper
retries send
. This looks correct for stream-type sockets but not for datagram-type sockets, but send
/recv
are used for both. However, datagram-type sockets still implement IO
so i'm not sure what to do. Socket
in general feels like a messy part of the stdlib to me.
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.
It musn't retry. Send with either succeed to send the whole messages, or fail and raise an exception.
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 realise that it's way beyond the scope of this PR but I really think we should have seperate types for datagram and stream-type sockets. The send
syscall is basically overloaded to do 2 different things: copy data to the kernel's output buffer for stream sockets and send a message for datagram sockets. I think it's a mistake to confuse these by putting it all under one type.
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.
Fixed this by simply not touching the "message" parts of Socket
, and the diff is a lot cleaner as a result.
I hardly see a benefit in this PR for the time being (e.g. code duplication), and how it will simplify integrating WinSock (which are BSD sockets, thus use file descriptors, unlike windows files). Calls to read and write (generic fd calls) are also kept in Socket, not the abstraction... but they're impossible on windows: there are no generic fds, only sockets so it has special calls (AFAIK). |
@ysbaddaden The point is so that |
There's a Ruby library called rex-socket which deals with some of the concerns here. It's effectively a set of abstractions we use for dealing with remote networking requirements and unconventional activities (a core part of Metasploit). The approach uses Ruby modules to define common functionality and apply in class composition via mixins (sometimes via extend because we pull from Ruby's stdlib c primitives). This really helps reduce redundancy as only the differentiated parts are written in the class itself. Over time creep will occur, but beats copy pasta everywhere and provides consistent interfaces and coding convention for a critical and touchy part of any language. |
3bc1658
to
6429494
Compare
Looks to me like Travis now doesn't have enough memory to reliably compile |
What prevents to run specs separately through |
@akzhan the spec output will be almost too ugly to read and it'll be a lot less efficient in terms of compilation time.
|
may be set |
looks like |
Regarding the I think is good to remove the inheritance between Socket and FileDescriptor, but I would feel more comfortable knowing what is the implementation needed for windows in order to judge if the abstraction is appropriate. |
Memory issues on travis also need to be resolved before this can be merged and I have no idea how to proceed on that. @bcardiff maybe I should work further into the future and attempt to port windows on top of this PR + abstraction IO to Crystal::System. The danger however is that this PR gets rejected and all that work is wasted. Thoughts? |
Seems like CircleCI is happy. LGTM? |
@RX14 can you resync with master now that Ary split the CI runnables? Let's move this forward. I wouldn't be too shy about finding the perfect abstraction: on the way to getting Windows support in master we're likely to break things loudly many times :D. |
Yup, if this turns out to be wrong I'm happy to do the work to put them all under the same hierarchy again. I'll rebase this and recheck my work in a bit. |
6429494
to
b93251a
Compare
This isn't ready to merge just yet. I also hit this while refactoring, is it a bug? def add(timeout : LibC::Timeval? = nil)
if timeout
typeof(timeout) # => LibC::Timeval
LibEvent2.event_add(@event, pointerof(timeout))
else
LibEvent2.event_add(@event, nil)
end
end
# argument 'timeout' of 'LibEvent2#event_add' must be Pointer(LibC::Timeval), not Pointer(Nil)
# doesn't make sense
def add(timeout : LibC::Timeval? = nil)
if timeout_copy = timeout
LibEvent2.event_add(@event, pointerof(timeout_copy))
else
LibEvent2.event_add(@event, nil)
end
end
# even making a copy doesn't work |
@RX14 The problem is that |
@asterite that's exactly what my second example does with |
I mean: if timeout
timeout_copy = timeout
end In |
@asterite Well if you use |
@RX14 I know, but the compiler doesn't :-P |
@RX14 Actually, no: if foo = @foo
end
# but here foo can be nilable The only place where |
Aargh but it feels wrong! I never think of |
IO::Syscall is an abstraction over non-blocking IO provided by syscalls. It takes care of checking for EAGAIN and managing the lists of waiting fibers.
On some platforms - notably windows - socket descriptors are different from file descriptors so it makes no sense for them to be shared under a common hierarchy.
b93251a
to
98eba71
Compare
I've refactored this a bit to store a |
Let's approve this 👍 |
Still no second review? :( |
I'm unsure that conversions on every |
add_read_event | ||
read_syscall_helper(slice, "Error reading file") do | ||
# `to_i32` is acceptable because `Slice#size` is a Int32 | ||
LibC.read(@fd, slice, slice.size).to_i32 |
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.
Why do we need this explicit conversion?
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.
Because read_syscall_helper is explicitly annotated to return i32.
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, but read_syscall_helper
didn't exist before this refactor, so why does it need to be explicitly annotated?
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.
Because IO#read
should always return typeof(Bytes.new.size)
. That wasn't the case before.
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.
Got it :). Thanks for bearing with me!
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.
It would be fantastic if we could force return types from an abstract declaration. Returning anything other than i32 from read doesn't make sense, and a return value from write is similarly useless. But that's (literally) another issue.
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, we were discussing exactly that with @matiasgarciaisaia in relation to #4864).
@akzhan Actually, every call to libevent will have been immediately preceded by a context switch ( |
Reminder for the changelog that this is a breaking change. |
Just created a |
@mverzilli Do you think |
It's an ortogonal dimension in my head. Let's keep it like that for now, I don't see any harm. |
On some platforms - notably windows - socket descriptors are different from file
descriptors so it makes little sense for them to be shared under a common hierarchy.
This is done in 3 stages:
read
/write
like functions fromIO::FileDescriptor
intoIO::Syscall
. It takes care of checking for EAGAIN and managing the lists of waiting fibers.edge_triggerable
option fromIO::FileDescriptor
, it doesn't seem to be used anywhere and it seems to me like it would cause hangs and other weird behaviour if used.Socket
not to inheritIO::FileDescriptor
. This involved quite a lot of copy-pasting code, which seems like a step backwards, but it means we can refactorFile
andSocket
to useCrystal::System
separately. Once these refactors are done bothFile
andSocket
class hierarchies will likely end up much cleaner.