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

Address ECONNRESET in issue #118 #165

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

pm-mck
Copy link
Contributor

@pm-mck pm-mck commented Oct 3, 2021

Summary

I took a few liberties here, so please feel free to recommend alternate strategies for any of the following:

  1. I enabled Cram tests in the dune-project. I felt that cram tests would be the best way to make the TCP RST packet issue pop up. I generally like how this turned out, however I don't really like how it does open TCP port selection.
  2. I added a few dependencies in the dream.opam file, such as emile and mimic. I personally run Dream's build in an esy "harness" of sorts, and it was not happy about those undeclared dependencies. Not a major issue to remove if this adds too much clutter or is undesired.
  3. The place where I added the fix seems like it is awfully naive to the structure of Dream's error handling. The Cram test itself validates the fix, (although I do need to test a bit more broadly, only osx/x64 so far, and I haven't tested ipv4 yet) so I'm happy with it as a one-off but I think there likely should be some sort of designated "error mangler" function for these types of things.

What has been done?

  • Write a test that demonstrates the behavior in ipv6
  • Write a test that demonstrates the behavior in ipv4
  • Update the error logging "middleware" to identify ECONNRESETs and downgrade to `Info
  • Test OSX
  • Test Linux
  • Test Windows if I can

dream.opam Outdated
@@ -55,6 +55,7 @@ depends: [
"conf-libev" {os != "win32"}
"cstruct" {>= "6.0.0"}
"dune" {>= "2.7.0"} # --instrument-with.
"emile"
Copy link
Owner

Choose a reason for hiding this comment

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

AFAIK Dream should not depend on emile at all. What is your setup in some more detail, and what did you observe that caused adding emile here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I generally use esy to build ocaml projects, almost as a force of habit. I believe I was building "everything", including tests, examples, etc. and esy was complaining about emile. I went back with a new switch and ran the opam install --deps-only ./dream.opam --with-test which worked. I will pull back this dependency from the PR.

dream.opam Outdated
@@ -64,9 +65,12 @@ depends: [
"lwt_ssl"
"logs" {>= "0.5.0"}
"magic-mime"
"mimic"
Copy link
Owner

Choose a reason for hiding this comment

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

This should be moved to dream-mirage.opam, which does seem to depend directly on mimic (and the dependency is not listed). Does this work with your setup if it is moved?

dream.opam Outdated
"mirage-clock"
"mirage-crypto" {>= "0.8.1"} # AES-256-GCM.
"mirage-crypto-rng" {>= "0.8.0"} # Signature of initialize.
"mirage-time"
"mirage-stack"
Copy link
Owner

Choose a reason for hiding this comment

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

These should probably also be moved to dream-mirage.opam.

backtrace |> Dream__middleware.Log.iter_backtrace (fun line ->
select_log error.severity (fun log ->
log ?request:error.request "%s" line))
(* TODO is this the right place to handle lower level connection resets? *)
Copy link
Owner

Choose a reason for hiding this comment

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

I'm not yet sure where this should be handled. Going to discuss a bit in #118, and do some research.

@@ -0,0 +1,2 @@
(cram
Copy link
Owner

Choose a reason for hiding this comment

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

Rather than a cram test, could this be done using an in-process expect test? It seems like that approach would be more portable, as it wouldn't depend on Unix commands, and could be a bit less awkward, since it wouldn't require any complex shell command lines (everything would be kept in OCaml code).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I spent some time today on this, I wanted to report back and see if you have any thoughts on a different approach.

Short answer is that it is possible, but without modifying Dream itself to add test hooks into the http module, quite ugly.

I have a successful expect test which does 3 things:

  1. Spins up a server on an open port using Dream.serve with a stop promise.
  2. Continually runs a "reset client" which tries to connect to the server. If it connects, it immediately closes the socket fd which causes an RST packet to be sent to the server. It then resolves the stop promise.
  3. Those two promises race against a third timeout promise - if the "reset client" doesn't resolve the stop promise in 4 seconds, the whole thing gets torn down.

A few things I don't like:

  • The test has to ignore SIGPIPE since closing the fd the way it does signals a closed pipe.
  • There is a timer since there's a potential the server can get stuck due to other failures, causing the test to stall indefinitely.
  • Since the server actually binds to an open TCP port, and we don't know when that binding succeeds, there's retry logic in the "reset client." This is kind of painful.

Reviewing the expect tests got me thinking: how useful would it be to offer a test capability to be able to inject errors at various points in Dream's request processing cycle to validate their behaviors?

Copy link
Owner

Choose a reason for hiding this comment

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

how useful would it be to offer a test capability to be able to inject errors at various points in Dream's request processing cycle to validate their behaviors?

Potentially very useful. I have a big project/roadmap category for making Dream apps (and probably Dream itself) really testable. But it's in the queue behind several other things.

I think it's fine then to use a cram test. If really good programmatic Dream testing materializes, I can change the test to use that later, if necessary.

(* TODO is this the right place to handle lower level connection resets? *)
| `Exn (Unix.Unix_error (Unix.ECONNRESET, _, _)) ->
let condition = `String "Connection Reset at Client" in
let severity = `Info in
Copy link
Owner

Choose a reason for hiding this comment

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

@hannesm Would it be fine for your use case if the errors were reported as warnings, as already done for most other client errors?

Copy link
Contributor

Choose a reason for hiding this comment

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

sure. at the same time with an operations view, I'd expect if warning and error level occur, I should check the log and look how to fix the issues. OTOH if a random client resets the TCP connection or sends a malformed request, that should be on the informational level. but eventually that should be discussed in a different issue and applied uniformally.

@aantron aantron force-pushed the master branch 2 times, most recently from ce104e4 to a9a9dfc Compare November 14, 2023 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants