Implement clean ^C shutdown and add the stop RPC.

Closes #69.
This commit is contained in:
Josh Rickmar 2014-06-24 16:00:27 -05:00
parent 85af882c13
commit b145868a4b
8 changed files with 368 additions and 111 deletions

View file

@ -465,7 +465,10 @@ func (a *Account) LockedOutpoints() []btcjson.TransactionInput {
locked := make([]btcjson.TransactionInput, len(a.lockedOutpoints)) locked := make([]btcjson.TransactionInput, len(a.lockedOutpoints))
i := 0 i := 0
for op := range a.lockedOutpoints { for op := range a.lockedOutpoints {
locked[i] = btcjson.TransactionInput{op.Hash.String(), op.Index} locked[i] = btcjson.TransactionInput{
Txid: op.Hash.String(),
Vout: op.Index,
}
i++ i++
} }
return locked return locked

View file

@ -29,6 +29,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
) )
// Errors relating to accounts. // Errors relating to accounts.
@ -36,6 +37,7 @@ var (
ErrAccountExists = errors.New("account already exists") ErrAccountExists = errors.New("account already exists")
ErrWalletExists = errors.New("wallet already exists") ErrWalletExists = errors.New("wallet already exists")
ErrNotFound = errors.New("not found") ErrNotFound = errors.New("not found")
ErrNoAccounts = errors.New("no accounts")
) )
// AcctMgr is the global account manager for all opened accounts. // AcctMgr is the global account manager for all opened accounts.
@ -70,6 +72,8 @@ type removeAccountCmd struct {
a *Account a *Account
} }
type quitCmd struct{}
// AccountManager manages a collection of accounts. // AccountManager manages a collection of accounts.
type AccountManager struct { type AccountManager struct {
// The accounts accessed through the account manager are not safe for // The accounts accessed through the account manager are not safe for
@ -81,6 +85,9 @@ type AccountManager struct {
ds *DiskSyncer ds *DiskSyncer
rm *RescanManager rm *RescanManager
wg sync.WaitGroup
quit chan struct{}
} }
// NewAccountManager returns a new AccountManager. // NewAccountManager returns a new AccountManager.
@ -89,6 +96,7 @@ func NewAccountManager() *AccountManager {
bsem: make(chan struct{}, 1), bsem: make(chan struct{}, 1),
cmdChan: make(chan interface{}), cmdChan: make(chan interface{}),
rescanMsgs: make(chan RescanMsg, 1), rescanMsgs: make(chan RescanMsg, 1),
quit: make(chan struct{}),
} }
am.ds = NewDiskSyncer(am) am.ds = NewDiskSyncer(am)
am.rm = NewRescanManager(am.rescanMsgs) am.rm = NewRescanManager(am.rescanMsgs)
@ -100,12 +108,29 @@ func (am *AccountManager) Start() {
// Ready the semaphore - can't grab unless the manager has started. // Ready the semaphore - can't grab unless the manager has started.
am.bsem <- struct{}{} am.bsem <- struct{}{}
am.wg.Add(4)
go am.accountHandler() go am.accountHandler()
go am.rescanListener() go am.rescanListener()
go am.ds.Start() go am.ds.Start()
go am.rm.Start() go am.rm.Start()
} }
// Stop shuts down the account manager by stoping all signaling all goroutines
// started by Start to close.
func (am *AccountManager) Stop() {
am.rm.Stop()
am.ds.Stop()
close(am.quit)
}
// WaitForShutdown blocks until all goroutines started by Start and stopped
// with Stop have finished.
func (am *AccountManager) WaitForShutdown() {
am.rm.WaitForShutdown()
am.ds.WaitForShutdown()
am.wg.Wait()
}
// accountData is a helper structure to let us centralise logic for adding // accountData is a helper structure to let us centralise logic for adding
// and removing accounts. // and removing accounts.
type accountData struct { type accountData struct {
@ -394,49 +419,57 @@ func openAccounts() *accountData {
func (am *AccountManager) accountHandler() { func (am *AccountManager) accountHandler() {
ad := openAccounts() ad := openAccounts()
for c := range am.cmdChan { out:
switch cmd := c.(type) { for {
case *openAccountsCmd: select {
// Write all old accounts before proceeding. case c := <-am.cmdChan:
for _, a := range ad.nameToAccount { switch cmd := c.(type) {
if err := am.ds.FlushAccount(a); err != nil { case *openAccountsCmd:
log.Errorf("Cannot write previously "+ // Write all old accounts before proceeding.
"scheduled account file: %v", err) for _, a := range ad.nameToAccount {
if err := am.ds.FlushAccount(a); err != nil {
log.Errorf("Cannot write previously "+
"scheduled account file: %v", err)
}
} }
ad = openAccounts()
case *accessAccountRequest:
a, ok := ad.nameToAccount[cmd.name]
if !ok {
a = nil
}
cmd.resp <- a
case *accessAccountByAddressRequest:
a, ok := ad.addressToAccount[cmd.address]
if !ok {
a = nil
}
cmd.resp <- a
case *accessAllRequest:
s := make([]*Account, 0, len(ad.nameToAccount))
for _, a := range ad.nameToAccount {
s = append(s, a)
}
cmd.resp <- s
case *addAccountCmd:
ad.addAccount(cmd.a)
case *removeAccountCmd:
ad.removeAccount(cmd.a)
case *markAddressForAccountCmd:
// TODO(oga) make sure we own account
ad.addressToAccount[cmd.address] = cmd.account
} }
ad = openAccounts() case <-am.quit:
case *accessAccountRequest: break out
a, ok := ad.nameToAccount[cmd.name]
if !ok {
a = nil
}
cmd.resp <- a
case *accessAccountByAddressRequest:
a, ok := ad.addressToAccount[cmd.address]
if !ok {
a = nil
}
cmd.resp <- a
case *accessAllRequest:
s := make([]*Account, 0, len(ad.nameToAccount))
for _, a := range ad.nameToAccount {
s = append(s, a)
}
cmd.resp <- s
case *addAccountCmd:
ad.addAccount(cmd.a)
case *removeAccountCmd:
ad.removeAccount(cmd.a)
case *markAddressForAccountCmd:
// TODO(oga) make sure we own account
ad.addressToAccount[cmd.address] = cmd.account
} }
} }
am.wg.Done()
} }
// rescanListener listens for messages from the rescan manager and marks // rescanListener listens for messages from the rescan manager and marks
@ -540,18 +573,22 @@ func (am *AccountManager) Account(name string) (*Account, error) {
// AccountByAddress returns the account specified by address, or // AccountByAddress returns the account specified by address, or
// ErrNotFound as an error if the account is not found. // ErrNotFound as an error if the account is not found.
func (am *AccountManager) AccountByAddress(addr btcutil.Address) (*Account, func (am *AccountManager) AccountByAddress(addr btcutil.Address) (*Account, error) {
error) {
respChan := make(chan *Account) respChan := make(chan *Account)
am.cmdChan <- &accessAccountByAddressRequest{ req := accessAccountByAddressRequest{
address: addr.EncodeAddress(), address: addr.EncodeAddress(),
resp: respChan, resp: respChan,
} }
resp := <-respChan select {
if resp == nil { case am.cmdChan <- &req:
return nil, ErrNotFound resp := <-respChan
if resp == nil {
return nil, ErrNotFound
}
return resp, nil
case <-am.quit:
return nil, ErrNoAccounts
} }
return resp, nil
} }
// MarkAddressForAccount labels the given account as containing the provided // MarkAddressForAccount labels the given account as containing the provided
@ -561,15 +598,18 @@ func (am *AccountManager) MarkAddressForAccount(address btcutil.Address,
// TODO(oga) really this entire dance should be carried out implicitly // TODO(oga) really this entire dance should be carried out implicitly
// instead of requiring explicit messaging from the account to the // instead of requiring explicit messaging from the account to the
// manager. // manager.
am.cmdChan <- &markAddressForAccountCmd{ req := markAddressForAccountCmd{
address: address.EncodeAddress(), address: address.EncodeAddress(),
account: account, account: account,
} }
select {
case am.cmdChan <- &req:
case <-am.quit:
}
} }
// Address looks up an address if it is known to wallet at all. // Address looks up an address if it is known to wallet at all.
func (am *AccountManager) Address(addr btcutil.Address) (wallet.WalletAddress, func (am *AccountManager) Address(addr btcutil.Address) (wallet.WalletAddress, error) {
error) {
a, err := am.AccountByAddress(addr) a, err := am.AccountByAddress(addr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -581,25 +621,38 @@ func (am *AccountManager) Address(addr btcutil.Address) (wallet.WalletAddress,
// AllAccounts returns a slice of all managed accounts. // AllAccounts returns a slice of all managed accounts.
func (am *AccountManager) AllAccounts() []*Account { func (am *AccountManager) AllAccounts() []*Account {
respChan := make(chan []*Account) respChan := make(chan []*Account)
am.cmdChan <- &accessAllRequest{ req := accessAllRequest{
resp: respChan, resp: respChan,
} }
return <-respChan select {
case am.cmdChan <- &req:
return <-respChan
case <-am.quit:
return nil
}
} }
// AddAccount adds an account to the collection managed by an AccountManager. // AddAccount adds an account to the collection managed by an AccountManager.
func (am *AccountManager) AddAccount(a *Account) { func (am *AccountManager) AddAccount(a *Account) {
am.cmdChan <- &addAccountCmd{ req := addAccountCmd{
a: a, a: a,
} }
select {
case am.cmdChan <- &req:
case <-am.quit:
}
} }
// RemoveAccount removes an account to the collection managed by an // RemoveAccount removes an account to the collection managed by an
// AccountManager. // AccountManager.
func (am *AccountManager) RemoveAccount(a *Account) { func (am *AccountManager) RemoveAccount(a *Account) {
am.cmdChan <- &removeAccountCmd{ req := removeAccountCmd{
a: a, a: a,
} }
select {
case am.cmdChan <- &req:
case <-am.quit:
}
} }
// RegisterNewAccount adds a new memory account to the account manager, // RegisterNewAccount adds a new memory account to the account manager,

65
cmd.go
View file

@ -32,8 +32,9 @@ import (
) )
var ( var (
cfg *config cfg *config
server *rpcServer server *rpcServer
shutdownChan = make(chan struct{})
curBlock = struct { curBlock = struct {
sync.RWMutex sync.RWMutex
@ -102,6 +103,36 @@ func accessClient() (*rpcClient, error) {
return c, nil return c, nil
} }
func clientConnect(certs []byte, newClient chan<- *rpcClient) {
const initialWait = 5 * time.Second
wait := initialWait
for {
select {
case <-server.quit:
return
default:
}
client, err := newRPCClient(certs)
if err != nil {
log.Warnf("Unable to open chain server client "+
"connection: %v", err)
time.Sleep(wait)
wait <<= 1
if wait > time.Minute {
wait = time.Minute
}
continue
}
wait = initialWait
client.Start()
newClient <- client
client.WaitForShutdown()
}
}
func main() { func main() {
// Work around defer not working after os.Exit. // Work around defer not working after os.Exit.
if err := walletMain(); err != nil { if err := walletMain(); err != nil {
@ -160,30 +191,18 @@ func walletMain() error {
// Start HTTP server to serve wallet client connections. // Start HTTP server to serve wallet client connections.
server.Start() server.Start()
// Shutdown the server if an interrupt signal is received.
addInterruptHandler(server.Stop)
// Start client connection to a btcd chain server. Attempt // Start client connection to a btcd chain server. Attempt
// reconnections if the client could not be successfully connected. // reconnections if the client could not be successfully connected.
clientChan := make(chan *rpcClient) clientChan := make(chan *rpcClient)
go clientAccess(clientChan) go clientAccess(clientChan)
const initialWait = 5 * time.Second go clientConnect(certs, clientChan)
wait := initialWait
for {
client, err := newRPCClient(certs)
if err != nil {
log.Warnf("Unable to open chain server client "+
"connection: %v", err)
time.Sleep(wait)
wait <<= 1
if wait > time.Minute {
wait = time.Minute
}
continue
}
wait = initialWait // Wait for the server to shutdown either due to a stop RPC request
// or an interrupt.
client.Start() server.WaitForShutdown()
clientChan <- client log.Info("Shutdown complete")
return nil
client.WaitForShutdown()
}
} }

View file

@ -204,6 +204,9 @@ type DiskSyncer struct {
// Account manager for this DiskSyncer. This is only // Account manager for this DiskSyncer. This is only
// needed to grab the account manager semaphore. // needed to grab the account manager semaphore.
am *AccountManager am *AccountManager
quit chan struct{}
shutdown chan struct{}
} }
// NewDiskSyncer creates a new DiskSyncer. // NewDiskSyncer creates a new DiskSyncer.
@ -215,6 +218,8 @@ func NewDiskSyncer(am *AccountManager) *DiskSyncer {
writeBatch: make(chan *writeBatchRequest), writeBatch: make(chan *writeBatchRequest),
exportAccount: make(chan *exportRequest), exportAccount: make(chan *exportRequest),
am: am, am: am,
quit: make(chan struct{}),
shutdown: make(chan struct{}),
} }
} }
@ -223,6 +228,14 @@ func (ds *DiskSyncer) Start() {
go ds.handler() go ds.handler()
} }
func (ds *DiskSyncer) Stop() {
close(ds.quit)
}
func (ds *DiskSyncer) WaitForShutdown() {
<-ds.shutdown
}
// handler runs the disk syncer. It manages a set of "dirty" account files // handler runs the disk syncer. It manages a set of "dirty" account files
// which must be written to disk, and synchronizes all writes in a single // which must be written to disk, and synchronizes all writes in a single
// goroutine. Periodic flush operations may be signaled by an AccountManager. // goroutine. Periodic flush operations may be signaled by an AccountManager.
@ -239,6 +252,7 @@ func (ds *DiskSyncer) handler() {
var timer <-chan time.Time var timer <-chan time.Time
var sem chan struct{} var sem chan struct{}
schedule := newSyncSchedule(netdir) schedule := newSyncSchedule(netdir)
out:
for { for {
select { select {
case <-sem: // Now have exclusive access of the account manager case <-sem: // Now have exclusive access of the account manager
@ -288,8 +302,16 @@ func (ds *DiskSyncer) handler() {
a := er.a a := er.a
dir := er.dir dir := er.dir
er.err <- a.writeAll(dir) er.err <- a.writeAll(dir)
case <-ds.quit:
err := schedule.flush()
if err != nil {
log.Errorf("Cannot write accounts: %v", err)
}
break out
} }
} }
close(ds.shutdown)
} }
// FlushAccount writes all scheduled account files to disk for a single // FlushAccount writes all scheduled account files to disk for a single

View file

@ -17,6 +17,8 @@
package main package main
import ( import (
"sync"
"github.com/conformal/btcutil" "github.com/conformal/btcutil"
"github.com/conformal/btcwire" "github.com/conformal/btcwire"
) )
@ -68,6 +70,8 @@ type RescanManager struct {
status chan interface{} // rescanProgress and rescanFinished status chan interface{} // rescanProgress and rescanFinished
msgs chan RescanMsg msgs chan RescanMsg
jobCompleteChan chan chan struct{} jobCompleteChan chan chan struct{}
wg sync.WaitGroup
quit chan struct{}
} }
// NewRescanManager creates a new RescanManger. If msgChan is non-nil, // NewRescanManager creates a new RescanManger. If msgChan is non-nil,
@ -80,15 +84,25 @@ func NewRescanManager(msgChan chan RescanMsg) *RescanManager {
status: make(chan interface{}, 1), status: make(chan interface{}, 1),
msgs: msgChan, msgs: msgChan,
jobCompleteChan: make(chan chan struct{}, 1), jobCompleteChan: make(chan chan struct{}, 1),
quit: make(chan struct{}),
} }
} }
// Start starts the goroutines to run the RescanManager. // Start starts the goroutines to run the RescanManager.
func (m *RescanManager) Start() { func (m *RescanManager) Start() {
m.wg.Add(2)
go m.jobHandler() go m.jobHandler()
go m.rpcHandler() go m.rpcHandler()
} }
func (m *RescanManager) Stop() {
close(m.quit)
}
func (m *RescanManager) WaitForShutdown() {
m.wg.Wait()
}
type rescanBatch struct { type rescanBatch struct {
addrs map[*Account][]btcutil.Address addrs map[*Account][]btcutil.Address
outpoints map[btcwire.OutPoint]struct{} outpoints map[btcwire.OutPoint]struct{}
@ -146,6 +160,7 @@ func (m *RescanManager) jobHandler() {
curBatch := newRescanBatch() curBatch := newRescanBatch()
nextBatch := newRescanBatch() nextBatch := newRescanBatch()
out:
for { for {
select { select {
case job := <-m.addJob: case job := <-m.addJob:
@ -205,8 +220,16 @@ func (m *RescanManager) jobHandler() {
// Unexpected status message // Unexpected status message
panic(s) panic(s)
} }
case <-m.quit:
break out
} }
} }
close(m.sendJob)
if m.msgs != nil {
close(m.msgs)
}
m.wg.Done()
} }
// rpcHandler reads jobs sent by the jobHandler and sends the rpc requests // rpcHandler reads jobs sent by the jobHandler and sends the rpc requests
@ -228,6 +251,7 @@ func (m *RescanManager) rpcHandler() {
m.MarkFinished(rescanFinished{err}) m.MarkFinished(rescanFinished{err})
} }
} }
m.wg.Done()
} }
// RescanJob is a job to be processed by the RescanManager. The job includes // RescanJob is a job to be processed by the RescanManager. The job includes

View file

@ -260,6 +260,7 @@ type rpcClient struct {
*btcrpcclient.Client // client to btcd *btcrpcclient.Client // client to btcd
enqueueNotification chan notification enqueueNotification chan notification
dequeueNotification chan notification dequeueNotification chan notification
quit chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
} }
@ -267,6 +268,7 @@ func newRPCClient(certs []byte) (*rpcClient, error) {
client := rpcClient{ client := rpcClient{
enqueueNotification: make(chan notification), enqueueNotification: make(chan notification),
dequeueNotification: make(chan notification), dequeueNotification: make(chan notification),
quit: make(chan struct{}),
} }
initializedClient := make(chan struct{}) initializedClient := make(chan struct{})
ntfnCallbacks := btcrpcclient.NotificationHandlers{ ntfnCallbacks := btcrpcclient.NotificationHandlers{
@ -317,7 +319,13 @@ func (c *rpcClient) Stop() {
log.Warn("Disconnecting chain server client connection") log.Warn("Disconnecting chain server client connection")
c.Client.Shutdown() c.Client.Shutdown()
} }
close(c.enqueueNotification)
select {
case <-c.quit:
default:
close(c.quit)
close(c.enqueueNotification)
}
} }
func (c *rpcClient) WaitForShutdown() { func (c *rpcClient) WaitForShutdown() {

View file

@ -213,11 +213,13 @@ type rpcServer struct {
upgrader websocket.Upgrader upgrader websocket.Upgrader
requests requestChan requests chan handlerJob
addWSClient chan *websocketClient addWSClient chan *websocketClient
removeWSClient chan *websocketClient removeWSClient chan *websocketClient
broadcasts chan []byte broadcasts chan []byte
quit chan struct{}
} }
// newRPCServer creates a new server for serving RPC client connections, both // newRPCServer creates a new server for serving RPC client connections, both
@ -232,10 +234,11 @@ func newRPCServer(listenAddrs []string) (*rpcServer, error) {
// Allow all origins. // Allow all origins.
CheckOrigin: func(r *http.Request) bool { return true }, CheckOrigin: func(r *http.Request) bool { return true },
}, },
requests: make(requestChan), requests: make(chan handlerJob),
addWSClient: make(chan *websocketClient), addWSClient: make(chan *websocketClient),
removeWSClient: make(chan *websocketClient), removeWSClient: make(chan *websocketClient),
broadcasts: make(chan []byte), broadcasts: make(chan []byte),
quit: make(chan struct{}),
} }
// Check for existence of cert file and key file // Check for existence of cert file and key file
@ -292,8 +295,9 @@ func (s *rpcServer) Start() {
// A duplicator for notifications intended for all clients runs // A duplicator for notifications intended for all clients runs
// in another goroutines. Any such notifications are sent to // in another goroutines. Any such notifications are sent to
// the allClients channel and then sent to each connected client. // the allClients channel and then sent to each connected client.
s.wg.Add(2)
go s.NotificationHandler() go s.NotificationHandler()
go s.requests.handler() go s.RequestHandler()
log.Trace("Starting RPC server") log.Trace("Starting RPC server")
@ -349,16 +353,56 @@ func (s *rpcServer) Start() {
s.wg.Add(1) s.wg.Add(1)
go func(listener net.Listener) { go func(listener net.Listener) {
log.Infof("RPCS: RPC server listening on %s", listener.Addr()) log.Infof("RPCS: RPC server listening on %s", listener.Addr())
if err := httpServer.Serve(listener); err != nil { _ = httpServer.Serve(listener)
log.Errorf("Listener for %s exited with error: %v",
listener.Addr(), err)
}
log.Tracef("RPCS: RPC listener done for %s", listener.Addr()) log.Tracef("RPCS: RPC listener done for %s", listener.Addr())
s.wg.Done() s.wg.Done()
}(listener) }(listener)
} }
} }
// Stop gracefully shuts down the rpc server by stopping and disconnecting all
// clients, disconnecting the chain server connection, and closing the wallet's
// account files.
func (s *rpcServer) Stop() {
// If the server is changed to run more than one rpc handler at a time,
// to prevent a double channel close, this should be replaced with an
// atomic test-and-set.
select {
case <-s.quit:
log.Warnf("Server already shutting down")
return
default:
}
log.Warn("Server shutting down")
// Stop all the listeners. There will not be any listeners if
// listening is disabled.
for _, listener := range s.listeners {
err := listener.Close()
if err != nil {
log.Errorf("Cannot close listener %s: %v",
listener.Addr(), err)
}
}
// Disconnect the connected chain server, if any.
client, err := accessClient()
if err == nil {
client.Stop()
}
// Stop the account manager and finish all pending account file writes.
AcctMgr.Stop()
// Signal the remaining goroutines to stop.
close(s.quit)
}
func (s *rpcServer) WaitForShutdown() {
s.wg.Wait()
}
// ErrNoAuth represents an error where authentication could not succeed // ErrNoAuth represents an error where authentication could not succeed
// due to a missing Authorization HTTP header. // due to a missing Authorization HTTP header.
var ErrNoAuth = errors.New("no auth") var ErrNoAuth = errors.New("no auth")
@ -789,6 +833,7 @@ func (s *rpcServer) NotifyConnectionStatus(wsc *websocketClient) {
} }
func (s *rpcServer) NotificationHandler() { func (s *rpcServer) NotificationHandler() {
out:
for { for {
select { select {
case c := <-s.addWSClient: case c := <-s.addWSClient:
@ -801,8 +846,11 @@ func (s *rpcServer) NotificationHandler() {
delete(s.wsClients, wsc) delete(s.wsClients, wsc)
} }
} }
case <-s.quit:
break out
} }
} }
s.wg.Done()
} }
// requestHandler is a handler function to handle an unmarshaled and parsed // requestHandler is a handler function to handle an unmarshaled and parsed
@ -841,6 +889,7 @@ var rpcHandlers = map[string]requestHandler{
"settxfee": SetTxFee, "settxfee": SetTxFee,
"signmessage": SignMessage, "signmessage": SignMessage,
"signrawtransaction": SignRawTransaction, "signrawtransaction": SignRawTransaction,
"stop": Stop,
"validateaddress": ValidateAddress, "validateaddress": ValidateAddress,
"verifymessage": VerifyMessage, "verifymessage": VerifyMessage,
"walletlock": WalletLock, "walletlock": WalletLock,
@ -859,7 +908,6 @@ var rpcHandlers = map[string]requestHandler{
"listreceivedbyaccount": Unimplemented, "listreceivedbyaccount": Unimplemented,
"move": Unimplemented, "move": Unimplemented,
"setaccount": Unimplemented, "setaccount": Unimplemented,
"stop": Unimplemented,
// Standard bitcoind methods which won't be implemented by btcwallet. // Standard bitcoind methods which won't be implemented by btcwallet.
"encryptwallet": Unsupported, "encryptwallet": Unsupported,
@ -901,36 +949,42 @@ type handlerJob struct {
response chan<- handlerResponse response chan<- handlerResponse
} }
type requestChan chan handlerJob // RequestHandler reads and processes client requests from the request channel.
// Each request is run with exclusive access to the account manager.
func (s *rpcServer) RequestHandler() {
out:
for {
select {
case r := <-s.requests:
AcctMgr.Grab()
result, err := r.handler(r.request)
AcctMgr.Release()
// handler reads and processes client requests from the channel. Each var jsonErr *btcjson.Error
// request is run with exclusive access to the account manager. if err != nil {
func (c requestChan) handler() { jsonErr = &btcjson.Error{Message: err.Error()}
for r := range c { switch e := err.(type) {
AcctMgr.Grab() case btcjson.Error:
result, err := r.handler(r.request) *jsonErr = e
AcctMgr.Release() case DeserializationError:
jsonErr.Code = btcjson.ErrDeserialization.Code
var jsonErr *btcjson.Error case InvalidParameterError:
if err != nil { jsonErr.Code = btcjson.ErrInvalidParameter.Code
jsonErr = &btcjson.Error{Message: err.Error()} case ParseError:
switch e := err.(type) { jsonErr.Code = btcjson.ErrParse.Code
case btcjson.Error: case InvalidAddressOrKeyError:
*jsonErr = e jsonErr.Code = btcjson.ErrInvalidAddressOrKey.Code
case DeserializationError: default: // All other errors get the wallet error code.
jsonErr.Code = btcjson.ErrDeserialization.Code jsonErr.Code = btcjson.ErrWallet.Code
case InvalidParameterError: }
jsonErr.Code = btcjson.ErrInvalidParameter.Code
case ParseError:
jsonErr.Code = btcjson.ErrParse.Code
case InvalidAddressOrKeyError:
jsonErr.Code = btcjson.ErrInvalidAddressOrKey.Code
default: // All other errors get the wallet error code.
jsonErr.Code = btcjson.ErrWallet.Code
} }
r.response <- handlerResponse{result, jsonErr}
case <-s.quit:
break out
} }
r.response <- handlerResponse{result, jsonErr}
} }
s.wg.Done()
} }
// Unimplemented handles an unimplemented RPC request with the // Unimplemented handles an unimplemented RPC request with the
@ -2492,6 +2546,13 @@ func SignRawTransaction(icmd btcjson.Cmd) (interface{}, error) {
}, nil }, nil
} }
// Stop handles the stop command by shutting down the process after the request
// is handled.
func Stop(icmd btcjson.Cmd) (interface{}, error) {
server.Stop()
return "btcwallet stopping.", nil
}
// ValidateAddress handles the validateaddress command. // ValidateAddress handles the validateaddress command.
func ValidateAddress(icmd btcjson.Cmd) (interface{}, error) { func ValidateAddress(icmd btcjson.Cmd) (interface{}, error) {
cmd, ok := icmd.(*btcjson.ValidateAddressCmd) cmd, ok := icmd.(*btcjson.ValidateAddressCmd)

67
signal.go Normal file
View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package main
import (
"os"
"os/signal"
)
// interruptChannel is used to receive SIGINT (Ctrl+C) signals.
var interruptChannel chan os.Signal
// addHandlerChannel is used to add an interrupt handler to the list of handlers
// to be invoked on SIGINT (Ctrl+C) signals.
var addHandlerChannel = make(chan func())
// mainInterruptHandler listens for SIGINT (Ctrl+C) signals on the
// interruptChannel and invokes the registered interruptCallbacks accordingly.
// It also listens for callback registration. It must be run as a goroutine.
func mainInterruptHandler() {
// interruptCallbacks is a list of callbacks to invoke when a
// SIGINT (Ctrl+C) is received.
var interruptCallbacks []func()
for {
select {
case <-interruptChannel:
log.Info("Received SIGINT (Ctrl+C). Shutting down...")
// run handlers in LIFO order.
for i := range interruptCallbacks {
idx := len(interruptCallbacks) - 1 - i
interruptCallbacks[idx]()
}
case handler := <-addHandlerChannel:
interruptCallbacks = append(interruptCallbacks, handler)
}
}
}
// addInterruptHandler adds a handler to call when a SIGINT (Ctrl+C) is
// received.
func addInterruptHandler(handler func()) {
// Create the channel and start the main interrupt handler which invokes
// all other callbacks and exits if not already done.
if interruptChannel == nil {
interruptChannel = make(chan os.Signal, 1)
signal.Notify(interruptChannel, os.Interrupt)
go mainInterruptHandler()
}
addHandlerChannel <- handler
}