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

Add Ctrl-C handling to the REPL #537

Merged
merged 14 commits into from
Aug 2, 2022
Merged

Add Ctrl-C handling to the REPL #537

merged 14 commits into from
Aug 2, 2022

Conversation

jaykru
Copy link
Contributor

@jaykru jaykru commented Jun 14, 2022

I noticed that you can't stop execution with a SIGINT when working interactively at the REPL (it does seem to work for loaded files) so I went ahead and tried adding it. Would be glad to know if there is a less invasive way of doing this!

Copy link
Collaborator

@rblanckaert rblanckaert left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution! This seems like a great feature for the repl to have.
However, as implemented there is a performance penalty and some surprising behavior to people who are using luau as a library.

#define VM_NEXT() goto*(SingleStep ? &&dispatch : kDispatchTable[LUAU_INSN_OP(*pc)])

#define VM_NEXT() \
if (sigint_received)\
Copy link
Collaborator

Choose a reason for hiding this comment

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

This check is too expensive to run after every opcode. The VM_INTERRUPT macro already checks the status in places where loops and calls can occur. Just wait for the PC to reach one of these existing checks.

#define VM_NEXT() goto dispatch
#define VM_NEXT() \
if (sigint_received)\
{ sigint_received = 0; } \
Copy link
Collaborator

Choose a reason for hiding this comment

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

See previous comment, but also wouldn't the behavior need to be the same as above for this to work as submitted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely! Just forgot to duplicate my changes from the macro above.

@@ -320,6 +332,8 @@ static void luau_execute(lua_State* L)
base = L->base;
k = cl->l.p->k;

signal(SIGINT, handle_sig);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not every implementor is going to want this behavior. This should be setup inside the Repl.cpp. There the CLI could have a signal handler that reaches into its global lua_State and sets the status variable.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Correction: it should be sufficient for this entire feature to be implemented in Repl; instead of setting a status variable (and checking it in VM_INTERRUPT), it should be sufficient to dynamically set the interrupt callback from Repl. Some care needs to be taken in the profiler support code (in Repl) so that the profile callback doesn't overwrite the interrupt callback, but otherwise the interrupt callback is a fully general mechanism that should be sufficient to perform termination here.

@zeux
Copy link
Collaborator

zeux commented Jun 14, 2022

I believe this would also need some Windows-specific code as SIGINT isn't supported on Windows.

@jaykru
Copy link
Contributor Author

jaykru commented Jun 16, 2022

I went ahead and reimplemented the functionality using the interrupt handler scheme as suggested by @zeux. Let me know how this looks 😃 I had to add a couple things to lapi due to a circular import problem I didn't quite understand...not sure if that was the correct approach.

Haven't yet had a chance to look into reproducing this for Windows; maybe tomorrow evening!

@zeux
Copy link
Collaborator

zeux commented Jun 16, 2022

This still has core changes that are not necessary:

  • Interrupt handler should raise an error if SIGINT was raised; the error will be caught at the top level. The global sigint flag needs to be reset at the top level to correctly break out of pcall.
  • LUA_SIGINT status is not necessary, and neither is lua_setstatus (the latter is dangerous as it can put the thread into an inconsistent state depending on how it's used).
  • Setting interrupt handler can be done via lua_callbacks

Additionally, the preferred mode of operation for interrupt handlers is to set them lazily (this can be done on any thread / signal), in this case instead of the flag what should probably happen is that the signal handler should set the interrupt callback that errors unconditionally, and the top-level should clear the interrupt handler after handling the error.

CLI/Repl.cpp Outdated
void ihandler(lua_State* L, int k) {
if (sigint_received) {
sigint_received = 0;
std::runtime_error error("Execution interrupted");
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be luaL_error(L, "Execution interrupted"); (compatible with building Luau without use of exceptions)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, I tried replacing the exception with a call to luaL_error in my most recent commit. This works fine as a drop-in replacement in the release build, but results in the following error on Ctrl-C in the debug build:

VM/src/lapi.cpp(600): ASSERTION FAILED: L->top < L->ci->top
Illegal instruction

Is this behavior expected? or should I file an issue?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This required lua_rawcheckstack(L, 1); as inside the interrupt callback the LUA_MINSTACK guarantee is (intentionally) not upheld.

@jaykru
Copy link
Contributor Author

jaykru commented Jun 20, 2022

This still has core changes that are not necessary:

  • Interrupt handler should raise an error if SIGINT was raised; the error will be caught at the top level. The global sigint flag needs to be reset at the top level to correctly break out of pcall.

The most recent commit raises an error with luaL_error. I didn't understand the latter concern about resetting the sigint flag to break out of pcall, but it seems like it may not be relevant with the switch to lazy interrupt setting that I also rolled into the most recent commit.

  • LUA_SIGINT status is not necessary, and neither is lua_setstatus (the latter is dangerous as it can put the thread into an inconsistent state depending on how it's used).

I've addressed both of these concerns.

  • Setting interrupt handler can be done via lua_callbacks

I removed my custom interrupt handler setter and use lua_callbacks in the most recent commit.

Additionally, the preferred mode of operation for interrupt handlers is to set them lazily (this can be done on any thread / signal), in this case instead of the flag what should probably happen is that the signal handler should set the interrupt callback that errors unconditionally, and the top-level should clear the interrupt handler after handling the error.

This is also addressed in the most recent commit, although I had to keep track of a global lua_State for the repl because I'm not able to pass any context beyond the signal value to the signal handler. I'm not certain this is the best way to do this.

@zeux
Copy link
Collaborator

zeux commented Jul 29, 2022

Sorry this got stuck a little bit, I think this looks good now modulo some style issues, I'll clean this up a little and merge it next week.

@zeux zeux self-assigned this Jul 29, 2022
@jaykru
Copy link
Contributor Author

jaykru commented Jul 29, 2022

Sounds good, @zeux. Thanks! :)

@zeux zeux requested a review from rblanckaert August 2, 2022 14:47
@zeux zeux merged commit b204981 into luau-lang:master Aug 2, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants