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

Change timeout semantics, support altering timeout per-eval #69

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

Conversation

z64
Copy link
Collaborator

@z64 z64 commented Mar 10, 2022

Hi Jesse! I have an interesting PR for you.

We have a particular use case wherein we do something like the following:

  1. Create a Sandbox instance (via Runtime)
  2. Load a large prelude of JS code - some libraries - into that runtime
  3. Evaluate a small subset of scripts on that loaded runtime

This is a latency sensitive context, so we want to apply an execution timeout. However a few things are at odds with the current implementation:

  1. We want a realistic limit of something around 100ms. Most things we are running do not exceed this, if it does, its a problem.
  2. Loading our JS prelude however can take upwards of 300ms
  3. The sandbox timeout is measured from Sandbox.new, not eval!

So we were left with potentially having to create a new sandbox every time in order for the timeout to function well, but at the cost of awful latency.

I did some digging and found it easy enough to alter duktape to fit our use-case, which is namely allowing us to set a timeout that is measured per-eval, and allows us to take as much time we need loading our prelude into our Runtime singleton.

All that said, I leave these commits for your consideration. You will find my notes in code & commits. Please feel free to alter this branch as you see fit, we are using an internal fork for now, and I don't have much time to participate in code review.. but I'm happy to answer any questions or maybe I did something terrible 😸

Hope all is well ❤️

z64 added 6 commits March 9, 2022 19:50
The goal is to alter the behavior of Sandbox so that the execution
timeout is measured from the time of calling #eval, instead of the time
of creating the sandbox.

To achieve this, instead of placing a timeout struct on the heap and
passing that as the user data pointer, we allocate a mutable Crystal
class with a start point field that we can update prior to engaging the
VM.

I also had to do two monkeypatches to the Context methods that serve as
the edge of Crystal code before calling into LibDuk. This may not be the
proper way to do this, but it is an awkward situation, and Sandbox is
the only descendant of Context, so I'm not sure what the intention is..

The next commit will complete the wiring of this new object.

---

As a bonus, this adds a ctor that takes a time span instead of just an
int, so you can expressively decalre the timeout as such:

    Duktape::Sandbox.new(500.milliseoncds)

instead of having to check what units are expected.
It is not well known, but Crystal allows you to export symbols that can
be called by foreign code. As such, this replaces the C procedure
definted in the duk_config.h header with a Crystal native `fun`
definition.

In this `fun`, we cast down the user data pointer - a pointer to our
TimeoutData struct for the executing Sandbox instance - and the timeout
measurement is made.

This could have been done by altering the C program as well. But I
figured that moving this to native Crystal code would be preferred.
Add a forwarding method to Runtime
A little awkward because of backwards compat. Perhaps it should just be
dropped?
@jessedoyle
Copy link
Owner

Hi @z64!

This is amazing! Thank you so much for the contribution!

I'd love to merge this feature into duktape.cr. Please give me a week or two to review the change, but at a high level I think feature makes a ton of sense!

Thanks!

@z64
Copy link
Collaborator Author

z64 commented Mar 10, 2022

@jessedoyle You're welcome! 😸

It is inconsequential for us, but the main issues with merging it into duktape publicly are:

  1. Figuring out what to do about the eval_string monkeypatches
  2. Currently I only allocate @timeout_data if you initialize Sandbox with a timeout. This is all we ever do, so that's fine. However it seems like you'll always need to set up the duktape instance with this udata pointer if the user can configure a timeout on a whim.
  3. Perhaps deprecate accepting timeout as an int, and just use Time::Span in order to simplify. This should be bearable as it will be a compiler error when users update, and I think a welcome change.

Once those are resolved I think this changeset should be in good shape.

@chendo
Copy link

chendo commented Mar 12, 2023

This PR looks great! I was trying to implement this myself as my use case is rather similar, and thankfully stumbled across this PR before I got anywhere.

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.

3 participants