-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
First pass at support for TCP servers and clients. #884
Conversation
7b2378b
to
90b78e9
Compare
Cool, finally something that does IO and can be benchmarked. Looking forward to testing this out. From my benchmarking of Node.js it really doesn't matter if you're looking at HTTP or WebSocket performance, both are capped at fairly low req/sec or messages/sec by what seems to be the TCP wrappers. The project "websockets/ws" perform about identical amount of messages per second as Node.js does HTTP req/sec. If you want higher perf. with Deno this thing will be critical to solve at much higher efficiency than for Node.js as it basically caps everything else (database transfers, messaging, http, etc). Well, Node.js does a good job for big sends (because it is capping out the kernel) but for small sends it really falls behind with 1/10th the performance of what you could get on the same system. Let's make sure to follow this up with at least a simple ping/pong benchmark to measure the theoretical maximum Deno apps can expect to get when doing network IO. |
@alexhultman Regarding benchmarks - probably a good one would be to start an echo TCP server and pipe 100M of data thru it using netcat. Here's an example that works with this branch: import * as deno from "deno";
let listener = deno.listen("tcp", "127.0.0.1:4500");
listener.accept().then(async (conn) => {
await deno.copy(conn, conn);
conn.close();
listener.close();
}); Then in another terminal you run:
There is likely much low hanging fruit here. I've been waiting to have such a benchmark before attempting to optimize. For example, just by increasing the buffer inside copy() to 64k, it improves quite a bit:
Also these numbers are for the debug build, not release. |
Piping 100 MB of data is kind of a pointless test. All servers will perform exactly the same in that test, because you are not testing anything user space, everything is going to be bottlenecked by the kernel space. It doesn't matter if you pick a web server written in Ruby on Rails or C in that case. You are bottlenecking on syscalls because all the process ends up doing is just passing huge blocks of memory to the send syscall. What I had in mind was small sends used in REST services (think sub 16kb) and WebSocket messaging (think sub 1kb) and similar. That's where you will see a difference between the servers. Small sends have a much higher ratio of user space involvement than huge static sends. |
Hopefully! That's the point of the benchmark. The code path for that echo server is not trivial - it includes
All of these are potential bottlenecks. |
Add both then. I get the exact same perf. in Python, Node.js and C when doing large sends. They all consume almost 0% user space CPU time and 100% kernel space CPU time. Small sends swap those numbers. It is completely impossible to outperform Node.js in large send throughput on Linux, unless you replace the entire kernel with some bypass user space TCP stack. |
I have a graph of the thing here: https://raw.githubusercontent.com/uNetworking/uSockets/master/misc/http.png Everything over 50mb is completely kernel bottlenecked. You can clearly see that Node.js has serious issues with small sends. |
@alexhultman I encourage you to outline your desired benchmark in the issues, I think it makes a lot of sense. |
Adds deno.listen(), deno.dial(), deno.Listener and deno.Conn.
readonly localAddr: string | ||
) {} | ||
|
||
write(p: ArrayBufferView): Promise<number> { |
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 missing something like shutdown here. How is that done?
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's not implemented yet.
It will be added to the AsyncWrite implementation in resources.rs. There's a sub here
Lines 104 to 106 in e5e7f0f
fn shutdown(&mut self) -> futures::Poll<(), std::io::Error> { | |
unimplemented!() | |
} |
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.
https://golang.org/pkg/net/#TCPConn.CloseRead
Following golang I will surface it as closeRead()
and closeWrite()
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've updated the source code to include a stubbed method.
* | ||
* See dial() for a description of the network and address parameters. | ||
*/ | ||
export function listen(network: Network, address: string): Listener { |
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 find "network" very confusing. Why not "protocol" like everybody else and their cat?
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 comes from Golang https://golang.org/pkg/net/#Listen
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 see. Still a bad name.
"unix packet" has nothing to do with network.
Soon well have "websocket" which is also not a network.
* dial("udp", "[fe80::1%lo0]:53") | ||
* dial("tcp", ":80") | ||
*/ | ||
export async function dial(network: Network, address: string): Promise<Conn> { |
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.
Not sold on the dial
name, but let's keep it for now.
However I would suggest to treat connect
as a reserved method name, in case we change our mind.
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.
Added.
let address = msg.address().unwrap(); | ||
|
||
Box::new(futures::future::result((move || { | ||
// TODO properly parse addr |
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.
What's not proper about it?
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 doesn't handle port only: ":8080"
needs tests to ensure conformance with golang.
pub struct Resource { | ||
pub rid: ResourceId, | ||
} | ||
|
||
impl Resource { | ||
// TODO Should it return a Resource instead of net::TcpStream? |
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.
IDK, I can't see through it at this point. Let's leave it for now and refactor (if necessary) when we add other types of servers.
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.
Mostly LGTM, but I would like to hear how shutdown() fits in, or alternatively why it is not needed.
I really like that this is mostly simple additive changes and requires little refactoring. That suggests that we are getting the internal abstractions right.
- Improve fetch headers (denoland#853) - Add deno.truncate (denoland#805) - Add copyFile/copyFileSync (denoland#863) - Limit depth of output in console.log for nested objects, and add console.dir (denoland#826) - Guess extensions on extension not provided (denoland#859) - Renames: deno.platform -> deno.platform.os deno.arch -> deno.platform.arch - Upgrade TS to 3.0.3 - Add readDirSync(), readDir() - Add support for TCP servers and clients. (denoland#884) Adds deno.listen(), deno.dial(), deno.Listener and deno.Conn.
- Improve fetch headers (#853) - Add deno.truncate (#805) - Add copyFile/copyFileSync (#863) - Limit depth of output in console.log for nested objects, and add console.dir (#826) - Guess extensions on extension not provided (#859) - Renames: deno.platform -> deno.platform.os deno.arch -> deno.platform.arch - Upgrade TS to 3.0.3 - Add readDirSync(), readDir() - Add support for TCP servers and clients. (#884) Adds deno.listen(), deno.dial(), deno.Listener and deno.Conn.
Adds deno.listen(), deno.dial(), deno.Listener and deno.Conn.
Fixes #725