console: implemented context timeout

This commit is contained in:
zsfelfoldi 2016-06-20 00:30:16 +02:00
parent f62fd849d5
commit 47eb6b817e
4 changed files with 105 additions and 32 deletions

View file

@ -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),

View file

@ -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

View file

@ -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 = ""
}
}

View file

@ -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()
}