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

max_keep_alive and non blocking reading of request #23

Merged
merged 9 commits into from Dec 11, 2021

Conversation

craff
Copy link
Contributor

@craff craff commented Dec 8, 2021

I experienced bad behavior of my server due to client not closing sockets (and a proxy makes it worth). Just increasing
max_connection is not enough, closing the navigator still does no close the socket. I needed two fixes to handle that:

  • I added a max_keep_alive to automatically close socket if there is no new request for some time (defaut is -1.0)
    not to break existing code.

  • the reading of request is now using Unix.read and a non blocking socket to make sure we are not blocking on Stdlib.input
    which was a behavior I observed very frequently.

@craff craff mentioned this pull request Dec 9, 2021
Copy link
Owner

@c-cube c-cube left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks pretty good for a first draft, I think we can cleanup a bit (particularly the debug code?) but this should be merged I hope.

src/Tiny_httpd.ml Outdated Show resolved Hide resolved
src/Tiny_httpd.mli Show resolved Hide resolved
src/Tiny_httpd.ml Show resolved Hide resolved
@@ -83,6 +83,27 @@ module Byte_stream = struct
let of_chan = of_chan_ ~close:close_in
let of_chan_close_noerr = of_chan_ ~close:close_in_noerr

exception Timeout

let of_descr_ ?(timeout=(-1.0)) ~close ic : t =
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that looks good. We might be able to remove of_chan entirely, I think, if we rely on Unix-level IOs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still one place when of_chan is used ... I didnot want to change to many things.

let of_descr_ ?(timeout=(-1.0)) ~close ic : t =
let i = ref 0 in
let len = ref 0 in
let buf = Bytes.make 4096 ' ' in
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bigger buffers are probably nice, like 16 kb at least :). Not your fault, I used this value before.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can make a new parameter to create. A server handling many small request will prefer smaller buffer than
a server handling big, but less many request. So it is nive to let the user choose ?

I also thought using your buffered input above ocaml's channel was stacking buffer ... So my PR also addresses that.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

funny, I thought the same, and wrote this today. turns out, it's not faster ☹️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in_channel use a large buffer (64Kb from memory) and is written in C, I am not very surprised... It is not slower at least ;-)

src/Tiny_httpd.ml Outdated Show resolved Hide resolved
src/Tiny_httpd.mli Show resolved Hide resolved
Copy link
Owner

@c-cube c-cube left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still a small change, but looking good!

src/Tiny_httpd.ml Outdated Show resolved Hide resolved
@craff
Copy link
Contributor Author

craff commented Dec 9, 2021

I removed the mutex.

@craff
Copy link
Contributor Author

craff commented Dec 9, 2021

I made available_connections available to the enduser by my latest commit.

@craff
Copy link
Contributor Author

craff commented Dec 9, 2021

I did further tests:

  • -1.0 is a good default if behind an apache proxy that keeps a pool of connection ... But max_connections should be compatible
    with apache configuration. apache really does not like the remote server to shutdown connection with the default parameter.
  • With a server attacked directly by web client (especially firefox) a positive default is recommended

May be a word should be added in the documentation of max_connections and max_keep_alive ?

i := 0;
let (to_read,_,_) = Unix.select [ic] [] [] timeout in
if to_read = [] then raise Timeout;
try len := Unix.read ic buf 0 (Bytes.length buf)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, should this be in a loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our file descriptor are sockets with an internal default buffer size of:
On 64-bit Linux 4.14.18: 212992 bytes
On 32-bit Linux 4.4.92: 163840 bytes

So if read returns less than those value, it means that the writer has currently written less that this number of bytes and the best is to return so that the scanning of the request can proceed, while the writer may continue to write, if the request was not send completely yet.

For a real file, read will "almost always" return the number of bytes required (except at the end of file).

The best would be to use the same buffer size for our buffer and the socket buffer using (or ensure at least a greater size)

val getsockopt_int : file_descr -> socket_int_option -> int
(** Same as {!getsockopt} for an integer-valued socket option. *)

val setsockopt_int : file_descr -> socket_int_option -> int -> unit
(** Same as {!setsockopt} for an integer-valued socket option. *)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, I mean the try. Should it be in a loop so that in case of exception (EAGAIN) we try to read again?

@craff
Copy link
Contributor Author

craff commented Dec 10, 2021

I reverted to acquire before accept, but adjusting available connection with a +1.

@c-cube c-cube merged commit 6dcae7f into c-cube:master Dec 11, 2021
@c-cube
Copy link
Owner

c-cube commented Dec 11, 2021

I tried to merge, but then I realized there's a deep issue here. There is a single FD per client, which means that all writing to the socket should also be non blocking (trying it yields Sys_blocked_io quite quicky).

The current state of things is now in a branch wip-nonblock , to which I can give you access if you want. It needs more work to figure out how to adapt everything to nonblocking, I suppose.

We also need much, much stronger tests for now.

@craff
Copy link
Contributor Author

craff commented Dec 11, 2021 via email

@craff
Copy link
Contributor Author

craff commented Dec 11, 2021 via email

@c-cube
Copy link
Owner

c-cube commented Dec 11, 2021

@craff question for your problem, too: would it be legit for the server to have a mode where, after the first request/response on a socket, the server closes the connection explicitly? unless keep-alive is specified? I forget if the http specs allow that.

@craff
Copy link
Contributor Author

craff commented Dec 12, 2021 via email

@craff
Copy link
Contributor Author

craff commented Dec 12, 2021 via email

@c-cube
Copy link
Owner

c-cube commented Dec 12, 2021

Ah, very interesting, I wrote the code assuming http 1.1. Could be an option in create, or perhaps a little middleware that injects the proper headers :)

@craff
Copy link
Contributor Author

craff commented Dec 12, 2021 via email

@c-cube
Copy link
Owner

c-cube commented Dec 12, 2021

that sounds like a good idea. Are you game to implement it? At least open an issue?

@craff
Copy link
Contributor Author

craff commented Dec 12, 2021 via email

@c-cube
Copy link
Owner

c-cube commented Dec 12, 2021

That sounds like a nice project :). You're teaching in Savoie, if I understand correctly?

@craff
Copy link
Contributor Author

craff commented Dec 13, 2021 via email

@c-cube
Copy link
Owner

c-cube commented Dec 13, 2021

Damn, that sounds lovely :)

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

Successfully merging this pull request may close these issues.

None yet

2 participants