-
Notifications
You must be signed in to change notification settings - Fork 31
/
node.go
142 lines (128 loc) · 3.45 KB
/
node.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright (c) 2014-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"time"
rpc "github.com/btcsuite/btcd/rpcclient"
)
// ErrConnectionTimeOut is raised when a rpc client is unable to connect
// to the node within maxConnRetries * 50ms
var ErrConnectionTimeOut = errors.New("connection timeout")
// Args is an interface which specifies how to access all the data required
// to launch and connect to a RPC server, typically btcd or btcwallet
type Args interface {
Arguments() []string
Command() *exec.Cmd
RPCConnConfig() rpc.ConnConfig
Cleanup() error
fmt.Stringer
}
// Node is a RPC server node, typically btcd or btcwallet and functions to
// manage the instance
// All functions common to btcd and btcwallet go here while btcdArgs and
// btcwalletArgs hold the different implementations
type Node struct {
Args
handlers *rpc.NotificationHandlers
cmd *exec.Cmd
client *rpc.Client
pidFile string
}
// NewNodeFromArgs starts a new node using the args provided, sets the handlers
// and loggers. It does not start the node process, Start() should be called for that
func NewNodeFromArgs(args Args, handlers *rpc.NotificationHandlers, w io.Writer) (*Node, error) {
n := Node{
Args: args,
handlers: handlers,
}
cmd := n.Command()
n.cmd = cmd
if w != nil {
n.cmd.Stdout = w
n.cmd.Stderr = w
}
return &n, nil
}
// Start stats the node command
// It writes a pidfile to AppDataDir with the name of the process
// which can be used to terminate the process in case of a hang or panic
func (n *Node) Start() error {
if err := n.cmd.Start(); err != nil {
return err
}
pid, err := os.Create(filepath.Join(AppDataDir,
fmt.Sprintf("%s.pid", n.Args)))
if err != nil {
return err
}
n.pidFile = pid.Name()
if _, err = fmt.Fprintf(pid, "%d\n", n.cmd.Process.Pid); err != nil {
return err
}
return pid.Close()
}
// Connect tries to connect to the launched node and sets the
// client field. It returns an error if the connection times out
func (n *Node) Connect() error {
var client *rpc.Client
var err error
rpcConf := n.RPCConnConfig()
for i := 0; i < *maxConnRetries; i++ {
if client, err = rpc.New(&rpcConf, n.handlers); err != nil {
time.Sleep(time.Duration(i) * 50 * time.Millisecond)
continue
}
break
}
if client == nil {
return ErrConnectionTimeOut
}
n.client = client
return nil
}
// Stop interrupts a process and waits until it exits
// On windows, interrupt is not supported, so a kill
// signal is used instead
func (n *Node) Stop() error {
if n.cmd == nil || n.cmd.Process == nil {
// return if not properly initialized
// or error starting the process
return nil
}
defer n.cmd.Wait()
if runtime.GOOS == "windows" {
return n.cmd.Process.Signal(os.Kill)
}
return n.cmd.Process.Signal(os.Interrupt)
}
// Cleanup cleanups process and args files
func (n *Node) Cleanup() error {
if n.pidFile != "" {
if err := os.Remove(n.pidFile); err != nil {
log.Printf("Cannot remove file %s: %v", n.pidFile, err)
}
}
return n.Args.Cleanup()
}
// Shutdown stops a node and cleansup
func (n *Node) Shutdown() {
if n.client != nil {
n.client.Shutdown()
}
if err := n.Stop(); err != nil {
log.Printf("%s: Cannot stop node: %v", n, err)
}
if err := n.Cleanup(); err != nil {
log.Printf("%s: Cannot cleanup: %v", n, err)
}
log.Printf("%s: Shutdown", n)
}