From 47eb6b817e8bd343ff6a5a065a3fe125245b3195 Mon Sep 17 00:00:00 2001 From: zsfelfoldi Date: Mon, 20 Jun 2016 00:30:16 +0200 Subject: [PATCH] console: implemented context timeout --- cmd/geth/consolecmd.go | 36 ++++++++++++++++++++++++------------ console/bridge.go | 32 ++++++++++++++++++++++++++++---- console/console.go | 41 +++++++++++++++++++++++++---------------- rpc/types.go | 28 ++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 32 deletions(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 257050a627..6c200824bf 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/console" + "github.com/ethereum/go-ethereum/rpc" "gopkg.in/urfave/cli.v1" ) @@ -67,10 +68,13 @@ func localConsole(ctx *cli.Context) error { defer node.Stop() // Attach to the newly started node and start the JavaScript console - client, err := node.Attach() - if err != nil { - utils.Fatalf("Failed to attach to the inproc geth: %v", err) - } + client := rpc.NewClientRestartWrapper(func() rpc.Client { + client, err := node.Attach() + if err != nil { + utils.Fatalf("Failed to attach to the inproc geth: %v", err) + } + return client + }) config := console.Config{ DataDir: node.DataDir(), DocRoot: ctx.GlobalString(utils.JSpathFlag.Name), @@ -99,10 +103,14 @@ func localConsole(ctx *cli.Context) error { // console to it. func remoteConsole(ctx *cli.Context) error { // Attach to a remotely running geth instance and start the JavaScript console - client, err := utils.NewRemoteRPCClient(ctx) - if err != nil { - utils.Fatalf("Unable to attach to remote geth: %v", err) - } + client := rpc.NewClientRestartWrapper(func() rpc.Client { + client, err := utils.NewRemoteRPCClient(ctx) + if err != nil { + utils.Fatalf("Unable to attach to remote geth: %v", err) + } + return client + }) + config := console.Config{ DataDir: utils.MustMakeDataDir(ctx), DocRoot: ctx.GlobalString(utils.JSpathFlag.Name), @@ -137,10 +145,14 @@ func ephemeralConsole(ctx *cli.Context) error { defer node.Stop() // Attach to the newly started node and start the JavaScript console - client, err := node.Attach() - if err != nil { - utils.Fatalf("Failed to attach to the inproc geth: %v", err) - } + client := rpc.NewClientRestartWrapper(func() rpc.Client { + client, err := node.Attach() + if err != nil { + utils.Fatalf("Failed to attach to the inproc geth: %v", err) + } + return client + }) + config := console.Config{ DataDir: node.DataDir(), DocRoot: ctx.GlobalString(utils.JSpathFlag.Name), diff --git a/console/bridge.go b/console/bridge.go index b23e06837d..65f2541b03 100644 --- a/console/bridge.go +++ b/console/bridge.go @@ -26,18 +26,20 @@ import ( "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/rpc" "github.com/robertkrimen/otto" + "golang.org/x/net/context" ) // bridge is a collection of JavaScript utility methods to bride the .js runtime // environment and the Go RPC connection backing the remote method calls. type bridge struct { - client rpc.Client // RPC client to execute Ethereum requests through + client *rpc.ClientRestartWrapper // RPC client to execute Ethereum requests through prompter UserPrompter // Input prompter to allow interactive user feedback printer io.Writer // Output writer to serialize any display strings to + ctx context.Context } // newBridge creates a new JavaScript wrapper around an RPC client. -func newBridge(client rpc.Client, prompter UserPrompter, printer io.Writer) *bridge { +func newBridge(client *rpc.ClientRestartWrapper, prompter UserPrompter, printer io.Writer) *bridge { return &bridge{ client: client, prompter: prompter, @@ -45,6 +47,10 @@ func newBridge(client rpc.Client, prompter UserPrompter, printer io.Writer) *bri } } +func (b *bridge) setContext(ctx context.Context) { + b.ctx = ctx +} + // NewAccount is a wrapper around the personal.newAccount RPC method that uses a // non-echoing password prompt to aquire the passphrase and executes the original // RPC method (saved in jeth.newAccount) with it to actually execute the RPC call. @@ -223,11 +229,29 @@ func (b *bridge) Send(call otto.FunctionCall) (response otto.Value) { for i, req := range reqs { // Execute the RPC request and parse the reply - if err = b.client.Send(&req); err != nil { + client := b.client.Client() + if err = client.Send(&req); err != nil { return newErrorResponse(call, -32603, err.Error(), req.Id) } + errc := make(chan error, 1) + errc2 := make(chan error) + go func(){ + if b.ctx != nil { + select { + case <-b.ctx.Done(): + b.client.Restart() + errc2 <- b.ctx.Err() + case err := <-errc: + errc2 <- err + } + } else { + errc2 <- <-errc + } + }() result := make(map[string]interface{}) - if err = b.client.Recv(&result); err != nil { + errc <- client.Recv(&result) + err := <-errc2 + if err != nil { return newErrorResponse(call, -32603, err.Error(), req.Id) } // Feed the reply back into the JavaScript runtime environment diff --git a/console/console.go b/console/console.go index 00d1fea1d8..449deeaf38 100644 --- a/console/console.go +++ b/console/console.go @@ -26,6 +26,7 @@ import ( "regexp" "sort" "strings" + "time" "github.com/ethereum/go-ethereum/internal/jsre" "github.com/ethereum/go-ethereum/internal/web3ext" @@ -33,6 +34,7 @@ import ( "github.com/mattn/go-colorable" "github.com/peterh/liner" "github.com/robertkrimen/otto" + "golang.org/x/net/context" ) var ( @@ -50,26 +52,27 @@ const DefaultPrompt = "> " // Config is te collection of configurations to fine tune the behavior of the // JavaScript console. type Config struct { - DataDir string // Data directory to store the console history at - DocRoot string // Filesystem path from where to load JavaScript files from - Client rpc.Client // RPC client to execute Ethereum requests through - Prompt string // Input prompt prefix string (defaults to DefaultPrompt) - Prompter UserPrompter // Input prompter to allow interactive user feedback (defaults to TerminalPrompter) - Printer io.Writer // Output writer to serialize any display strings to (defaults to os.Stdout) - Preload []string // Absolute paths to JavaScript files to preload + DataDir string // Data directory to store the console history at + DocRoot string // Filesystem path from where to load JavaScript files from + Client *rpc.ClientRestartWrapper // RPC client to execute Ethereum requests through + Prompt string // Input prompt prefix string (defaults to DefaultPrompt) + Prompter UserPrompter // Input prompter to allow interactive user feedback (defaults to TerminalPrompter) + Printer io.Writer // Output writer to serialize any display strings to (defaults to os.Stdout) + Preload []string // Absolute paths to JavaScript files to preload } // Console is a JavaScript interpreted runtime environment. It is a fully fleged // JavaScript console attached to a running node via an external or in-process RPC // client. type Console struct { - client rpc.Client // RPC client to execute Ethereum requests through - jsre *jsre.JSRE // JavaScript runtime environment running the interpreter - prompt string // Input prompt prefix string - prompter UserPrompter // Input prompter to allow interactive user feedback - histPath string // Absolute path to the console scrollback history - history []string // Scroll history maintained by the console - printer io.Writer // Output writer to serialize any display strings to + client *rpc.ClientRestartWrapper // RPC client to execute Ethereum requests through + jsre *jsre.JSRE // JavaScript runtime environment running the interpreter + prompt string // Input prompt prefix string + prompter UserPrompter // Input prompter to allow interactive user feedback + histPath string // Absolute path to the console scrollback history + history []string // Scroll history maintained by the console + printer io.Writer // Output writer to serialize any display strings to + setContext func(context.Context) } func New(config Config) (*Console, error) { @@ -103,6 +106,7 @@ func New(config Config) (*Console, error) { func (c *Console) init(preload []string) error { // Initialize the JavaScript <-> Go RPC bridge bridge := newBridge(c.client, c.prompter, c.printer) + c.setContext = bridge.setContext c.jsre.Set("jeth", struct{}{}) jethObj, _ := c.jsre.Get("jeth") @@ -127,7 +131,7 @@ func (c *Console) init(preload []string) error { return fmt.Errorf("web3 provider: %v", err) } // Load the supported APIs into the JavaScript runtime environment - apis, err := c.client.SupportedModules() + apis, err := c.client.Client().SupportedModules() if err != nil { return fmt.Errorf("api modules: %v", err) } @@ -253,7 +257,7 @@ func (c *Console) Welcome() { console.log(" datadir: " + admin.datadir); `) // List all the supported modules for the user to call - if apis, err := c.client.SupportedModules(); err == nil { + if apis, err := c.client.Client().SupportedModules(); err == nil { modules := make([]string, 0, len(apis)) for api, version := range apis { modules = append(modules, fmt.Sprintf("%s:%s", api, version)) @@ -347,7 +351,12 @@ func (c *Console) Interactive() { } } } + done := make(chan struct{}) + ctx, _ := context.WithTimeout(context.Background(), time.Second*5) + c.setContext(ctx) c.Evaluate(input) + c.setContext(nil) + close(done) input = "" } } diff --git a/rpc/types.go b/rpc/types.go index a1f36fbd26..5c821d41a7 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -284,3 +284,31 @@ type Client interface { Close() } + +type ClientRestartWrapper struct { + client Client + newClientFn func() Client + mu sync.RWMutex +} + +func NewClientRestartWrapper(newClientFn func() Client) *ClientRestartWrapper { + return &ClientRestartWrapper { + client: newClientFn(), + newClientFn: newClientFn, + } +} + +func (rw *ClientRestartWrapper) Client() Client { + rw.mu.RLock() + defer rw.mu.RUnlock() + + return rw.client +} + +func (rw *ClientRestartWrapper) Restart() { + rw.mu.Lock() + defer rw.mu.Unlock() + + rw.client.Close() + rw.client = rw.newClientFn() +}