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

Lin: Print commands that trigger unexpected exceptions #324

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

shym
Copy link
Collaborator

@shym shym commented Apr 6, 2023

The standard QCheck mechanism to catch exceptions that are raised in tests do not give much clues about the source (ie Lin command) of that exception
So this creates a specific Unexpected_exception that will pack the command along with the original exception so that it can be explained when the tests errors out

Before:

exception Invalid_argument("Bytes.blit")

After:

exception Invalid_argument("Bytes.blit") unexpected in command Buffer.add_string t ""

@jmid
Copy link
Collaborator

jmid commented Apr 12, 2023

This is both a nice improvement and elegantly implemented - many thanks! 😃

  • Could you add a test case to test/ to guard against someone (read: me) breaking this by mistake in the future?
  • I think a CHANGES entry is also called for, as this is a quality-of-life improvement for users!
  • Finally I'm wondering whether the same approach could apply to STM too...

shym added a commit to shym/multicoretests that referenced this pull request Jun 5, 2023
@shym
Copy link
Collaborator Author

shym commented Jun 5, 2023

I finally got round to update that PR with your suggestions. It also takes care now to preserve the original backtrace.

@shym
Copy link
Collaborator Author

shym commented Jun 5, 2023

Well, that’s quite a success...
I don’t get the same output when running the new internal tests locally and I don’t understand where this can come from.
I finally saw that the difference seems to be in the necessary time to run the test! 0.0s locally, 0.1s or 0.2s in CI, depending on the runner.

shym added a commit to shym/multicoretests that referenced this pull request Jun 6, 2023
@jmid
Copy link
Collaborator

jmid commented Jun 6, 2023

The Random module changed between OCaml 4 and 5, and as a result the output distribution changed.
Overall that means we cannot expect the same output with the same seed across the two - or the same number of shrinking steps (printed) as a result. 🤷

Furthermore, the distribution varies between 32 bit and 64 bit architectures.
As a result there are 4 possible combinations: OCaml{4,5} * {32,64}-bit).
test/mutable_set_v5.ml thus has 4 different expect-files to handle these. It is not particularly pretty... 😅

It the present case we may just want to filter the output a bit.
I recently learned of dune's (pipe-outputs ...) which can be handy for this sort of thing in combination with sed.
There's an example of it here.

@shym
Copy link
Collaborator Author

shym commented Jun 6, 2023

sed is indeed the path I was following 😄
I might have been a bit radical in my filtering (removing all progress lines), but I expect this is good enough for our purpose.

Copy link
Collaborator

@jmid jmid left a comment

Choose a reason for hiding this comment

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

LGTM modulo a minor nit.

At first I thought the Printexc would fail under bytecode due to the dreaded bytecode_complete but I then realized it will work fine, as you catch the exception without needing the full backtrace.

The PR now installs a handler on every cmd interpreted in a central hot path of both Lin and STM. This can potentially affect our ability to trigger parallel or concurrency issues, so we should carefully review the CI logs.
Did the first Lin version also do so? (I may be misremembering)

lib/util.mli Outdated
@@ -41,6 +41,12 @@ val print_triple_vertical :
val protect : ('a -> 'b) -> 'a -> ('b, exn) result
(** [protect f] turns an [exception] throwing function into a [result] returning function. *)

val tag_exn_with : ('a -> string) -> ('a -> 'b) -> 'a -> 'b
(** [tag_exn_with show run x] behaves as [run x] unless raises an
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: "unless it raises an"

shym added a commit to shym/multicoretests that referenced this pull request Jun 7, 2023
@shym
Copy link
Collaborator Author

shym commented Jun 7, 2023

The PR now installs a handler on every cmd interpreted in a central hot path of both Lin and STM. This can potentially affect our ability to trigger parallel or concurrency issues, so we should carefully review the CI logs. Did the first Lin version also do so? (I may be misremembering)

It did, but it was inlined at the site of the call. I don’t know how keen the compiler is to inline across modules but the function is modeled after Fun.protect.
For Lin DSL, we could move that code and try to use it only when the tested function does not declare it can throw exceptions. On the other hand, the function invocation is already a quite long path in Lin DSL. It is maybe a bit more troublesome, in cases where the model is really simple (ie when next_state and postcond are really cheap).

I fixed your small suggestion, also as a way to trigger another round of testing.

@shym
Copy link
Collaborator Author

shym commented Jun 8, 2023

Proposition of an alternate design for that functionality: maybe we could install the catch-all exception handler only at the very start of the execution of the whole sequence of commands on each domain, and using domain-local storage to store the currently running command. I’m not sure we have a similar safe storage mechanism for systhreads though 😅
According to @OlivierNicole, installing an exception-handler is only 3 machine instructions, so I’m not sure this alternative would be cheaper. But I could update this PR by replacing uses of tag_exn_with by explicit trys (to avoid the extra function calls in the hot path), calling out a function in Util only in the handler itself. That would duplicate very little code.

shym added 6 commits June 12, 2023 13:45
Add the utilitary exception Uncaught_exception and function
wrap_uncaught_exn to improve the error reports in cases of uncaught
exceptions
This will allow STM and Lin to display the command along with the
exception when a command raises an exception but its description did not
announce it so
The standard QCheck mechanism to catch exceptions that are raised in
tests do not give much clue about the source (ie Lin command) of that
exception
This packs the unsoundly-specified command with the exception in such a
case so that it can be displayed

Before:
```
exception Failure("unexpected")
```

After:
```
exception Failure("unexpected") raised but not caught while running always_fail t
```
The standard QCheck mechanism to catch exceptions that are raised in
tests do not give much clue about the source (ie STM command) of that
exception
This packs the unsoundly-specified command with the exception in such a
case so that it can be displayed

Before:
```
exception Failure("unexpected")
```

After:
```
exception Failure("unexpected") raised but not caught while running AlwaysFail ()
```
@shym
Copy link
Collaborator Author

shym commented Jun 12, 2023

I just updated this PR with the latest proposal: using an inline try with at the various calling sites, sharing only the code that it is run when an exception does appear. This should have the smallest possible impact on runs while catching those exceptions.

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