Skip to content

Commit a8b6220

Browse files
author
Тимофей Шкредов
committed
Add runner context interrupting
1 parent 444ae5e commit a8b6220

File tree

6 files changed

+57
-2
lines changed

6 files changed

+57
-2
lines changed

main.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import (
1111
"fmt"
1212
"log"
1313
"os"
14+
"os/signal"
1415
"runtime"
1516
"runtime/pprof"
17+
"syscall"
1618

1719
"github.com/go-python/gpython/py"
1820
"github.com/go-python/gpython/repl"
@@ -48,6 +50,14 @@ func xmain(args []string) {
4850
ctx := py.NewContext(opts)
4951
defer ctx.Close()
5052

53+
sigCh := make(chan os.Signal, 1)
54+
signal.Notify(sigCh, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
55+
go func() {
56+
for range sigCh {
57+
ctx.SetInterrupt()
58+
}
59+
}()
60+
5161
if *cpuprofile != "" {
5262
f, err := os.Create(*cpuprofile)
5363
if err != nil {

py/run.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ type Context interface {
4242
// Gereric access to this context's modules / state.
4343
Store() *ModuleStore
4444

45+
// SetInterrupt signals the VM to raise KeyboardInterrupt at the next opcode boundary.
46+
// Safe to call from any goroutine (e.g. a signal handler).
47+
SetInterrupt()
48+
49+
// CheckInterrupt atomically checks and clears the interrupt flag.
50+
// Returns true if an interrupt was pending.
51+
CheckInterrupt() bool
52+
4553
// Close signals this context is about to go out of scope and any internal resources should be released.
4654
// Code execution on a py.Context that has been closed will result in an error.
4755
Close() error

repl/cli/cli.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ func newReadline(repl *repl.REPL) *readline {
5151
}
5252
rl.SetTabCompletionStyle(liner.TabPrints)
5353
rl.SetWordCompleter(rl.Completer)
54+
rl.SetCtrlCAborts(true)
5455
return rl
5556
}
5657

@@ -146,6 +147,11 @@ func RunREPL(replCtx *repl.REPL) error {
146147
fmt.Printf("\n")
147148
break
148149
}
150+
if err == liner.ErrPromptAborted {
151+
fmt.Println("KeyboardInterrupt")
152+
rl.repl.ResetContinuation()
153+
continue
154+
}
149155
fmt.Printf("Problem reading line: %v\n", err)
150156
continue
151157
}

repl/repl.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ func (r *REPL) SetUI(term UI) {
6565
r.term.SetPrompt(NormalPrompt)
6666
}
6767

68+
// ResetContinuation cancels any multi-line input in progress,
69+
// restoring the REPL to a clean prompt state (e.g. after Ctrl+C).
70+
func (r *REPL) ResetContinuation() {
71+
r.continuation = false
72+
r.previous = ""
73+
r.term.SetPrompt(NormalPrompt)
74+
}
75+
6876
// Run runs a single line of the REPL
6977
func (r *REPL) Run(line string) error {
7078
// Override the PrintExpr output temporarily
@@ -112,6 +120,10 @@ func (r *REPL) Run(line string) error {
112120
if py.IsException(py.SystemExit, err) {
113121
return err
114122
}
123+
if py.IsException(py.KeyboardInterrupt, err) {
124+
r.term.Print("KeyboardInterrupt")
125+
return nil
126+
}
115127
py.TracebackDump(err)
116128
}
117129
return nil

stdlib/stdlib.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"path/filepath"
1414
"strings"
1515
"sync"
16+
"sync/atomic"
1617

1718
"github.com/go-python/gpython/py"
1819
"github.com/go-python/gpython/stdlib/marshal"
@@ -44,6 +45,7 @@ type context struct {
4445
closed bool
4546
running sync.WaitGroup
4647
done chan struct{}
48+
interrupt int32 // atomic; non-zero means KeyboardInterrupt pending
4749
}
4850

4951
// NewContext creates a new gpython interpreter instance context.
@@ -192,6 +194,16 @@ func (ctx *context) ResolveAndCompile(pathname string, opts py.CompileOpts) (py.
192194
return out, nil
193195
}
194196

197+
// See interface py.Context defined in py/run.go
198+
func (ctx *context) SetInterrupt() {
199+
atomic.StoreInt32(&ctx.interrupt, 1)
200+
}
201+
202+
// See interface py.Context defined in py/run.go
203+
func (ctx *context) CheckInterrupt() bool {
204+
return atomic.SwapInt32(&ctx.interrupt, 0) != 0
205+
}
206+
195207
func (ctx *context) pushBusy() error {
196208
if ctx.closed {
197209
return py.ExceptionNewf(py.RuntimeError, "Context closed")
@@ -208,6 +220,7 @@ func (ctx *context) popBusy() {
208220
func (ctx *context) Close() error {
209221
ctx.closeOnce.Do(func() {
210222
ctx.closing = true
223+
ctx.SetInterrupt()
211224
ctx.running.Wait()
212225
ctx.closed = true
213226

vm/eval.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1751,7 +1751,6 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
17511751
frame: frame,
17521752
context: frame.Context,
17531753
}
1754-
17551754
// FIXME need to do this to save the old exeption when we
17561755
// yield from a generator. Should save it in the Frame though
17571756
// (see slots in the frame)
@@ -1778,6 +1777,13 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
17781777
var arg int32
17791778
opcodes := frame.Code.Code
17801779
for vm.why == whyNot {
1780+
// Check for pending interrupt (e.g. SIGINT / Context.SetInterrupt).
1781+
// Routed through the normal exception mechanism so that
1782+
// try/except/finally blocks are honored.
1783+
if vm.context != nil && vm.context.CheckInterrupt() {
1784+
vm.SetException(py.MakeException(py.ExceptionNewf(py.KeyboardInterrupt, "KeyboardInterrupt")))
1785+
goto handleException
1786+
}
17811787
if debugging {
17821788
debugf("* %4d:", frame.Lasti)
17831789
}
@@ -1822,7 +1828,7 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
18221828
if vm.why == whyYield {
18231829
goto fast_yield
18241830
}
1825-
1831+
handleException:
18261832
// Something exceptional has happened - unwind the block stack
18271833
// and find out what
18281834
for vm.why != whyNot && frame.Block != nil {

0 commit comments

Comments
 (0)