Tendermint <-> Application refactor

This commit is contained in:
Jae Kwon
2015-12-01 20:12:01 -08:00
parent a8dc417cd9
commit ef43af19ab
401 changed files with 2401 additions and 76617 deletions
+28
View File
@@ -0,0 +1,28 @@
package proxy
import (
tmsp "github.com/tendermint/tmsp/types"
)
type Callback func(tmsp.Request, tmsp.Response)
type AppContext interface {
SetResponseCallback(Callback)
Error() error
EchoAsync(msg string)
FlushAsync()
AppendTxAsync(tx []byte)
GetHashAsync()
CommitAsync()
RollbackAsync()
SetOptionAsync(key string, value string)
AddListenerAsync(key string)
RemListenerAsync(key string)
InfoSync() (info []string, err error)
FlushSync() error
GetHashSync() (hash []byte, err error)
CommitSync() error
RollbackSync() error
}
+123
View File
@@ -0,0 +1,123 @@
package proxy
import (
tmsp "github.com/tendermint/tmsp/types"
)
type localAppContext struct {
tmsp.AppContext
Callback
}
func NewLocalAppContext(app tmsp.AppContext) *localAppContext {
return &localAppContext{
AppContext: app,
}
}
func (app *localAppContext) SetResponseCallback(cb Callback) {
app.Callback = cb
}
// TODO: change tmsp.AppContext to include Error()?
func (app *localAppContext) Error() error {
return nil
}
func (app *localAppContext) EchoAsync(msg string) {
msg2 := app.AppContext.Echo(msg)
app.Callback(
tmsp.RequestEcho{msg},
tmsp.ResponseEcho{msg2},
)
}
func (app *localAppContext) FlushAsync() {
// Do nothing
}
func (app *localAppContext) SetOptionAsync(key string, value string) {
retCode := app.AppContext.SetOption(key, value)
app.Callback(
tmsp.RequestSetOption{key, value},
tmsp.ResponseSetOption{retCode},
)
}
func (app *localAppContext) AppendTxAsync(tx []byte) {
events, retCode := app.AppContext.AppendTx(tx)
app.Callback(
tmsp.RequestAppendTx{tx},
tmsp.ResponseAppendTx{retCode},
)
for _, event := range events {
app.Callback(
nil,
tmsp.ResponseEvent{event},
)
}
}
func (app *localAppContext) GetHashAsync() {
hash, retCode := app.AppContext.GetHash()
app.Callback(
tmsp.RequestGetHash{},
tmsp.ResponseGetHash{retCode, hash},
)
}
func (app *localAppContext) CommitAsync() {
retCode := app.AppContext.Commit()
app.Callback(
tmsp.RequestCommit{},
tmsp.ResponseCommit{retCode},
)
}
func (app *localAppContext) RollbackAsync() {
retCode := app.AppContext.Rollback()
app.Callback(
tmsp.RequestRollback{},
tmsp.ResponseRollback{retCode},
)
}
func (app *localAppContext) AddListenerAsync(key string) {
retCode := app.AppContext.AddListener(key)
app.Callback(
tmsp.RequestAddListener{key},
tmsp.ResponseAddListener{retCode},
)
}
func (app *localAppContext) RemListenerAsync(key string) {
retCode := app.AppContext.RemListener(key)
app.Callback(
tmsp.RequestRemListener{key},
tmsp.ResponseRemListener{retCode},
)
}
func (app *localAppContext) InfoSync() (info []string, err error) {
info = app.AppContext.Info()
return info, nil
}
func (app *localAppContext) FlushSync() error {
return nil
}
func (app *localAppContext) GetHashSync() (hash []byte, err error) {
hash, retCode := app.AppContext.GetHash()
return hash, retCode.Error()
}
func (app *localAppContext) CommitSync() (err error) {
retCode := app.AppContext.Commit()
return retCode.Error()
}
func (app *localAppContext) RollbackSync() (err error) {
retCode := app.AppContext.Rollback()
return retCode.Error()
}
-349
View File
@@ -1,349 +0,0 @@
package proxy
import (
"bufio"
"container/list"
"errors"
"fmt"
"net"
"reflect"
"sync"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-wire"
tmsp "github.com/tendermint/tmsp/types"
)
const maxResponseSize = 1048576 // 1MB
// This is goroutine-safe, but users should beware that
// the application in general is not meant to be interfaced
// with concurrent callers.
// In other words, the mempool and consensus modules need to
// exclude each other w/ an external mutex.
type ProxyApp struct {
QuitService
sync.Mutex
reqQueue chan QueuedRequest
mtx sync.Mutex
conn net.Conn
bufWriter *bufio.Writer
err error
reqSent *list.List
reqPending *list.Element // Next element in reqSent waiting for response
resReceived *list.List
eventsReceived *list.List
}
func NewProxyApp(conn net.Conn, bufferSize int) *ProxyApp {
p := &ProxyApp{
reqQueue: make(chan QueuedRequest, bufferSize),
conn: conn,
bufWriter: bufio.NewWriter(conn),
reqSent: list.New(),
reqPending: nil,
resReceived: list.New(),
eventsReceived: list.New(),
}
p.QuitService = *NewQuitService(nil, "ProxyApp", p)
return p
}
func (p *ProxyApp) OnStart() error {
p.QuitService.OnStart()
go p.sendRequestsRoutine()
go p.recvResponseRoutine()
return nil
}
func (p *ProxyApp) OnStop() {
p.QuitService.OnStop()
p.conn.Close()
}
func (p *ProxyApp) StopForError(err error) {
p.mtx.Lock()
fmt.Println("Stopping ProxyApp for error:", err)
if p.err == nil {
p.err = err
}
p.mtx.Unlock()
p.Stop()
}
func (p *ProxyApp) Error() error {
p.mtx.Lock()
defer p.mtx.Unlock()
return p.err
}
//----------------------------------------
func (p *ProxyApp) sendRequestsRoutine() {
for {
var n int
var err error
select {
case <-p.QuitService.Quit:
return
case qreq := <-p.reqQueue:
wire.WriteBinary(qreq.Request, p.bufWriter, &n, &err)
if err != nil {
p.StopForError(err)
return
}
if _, ok := qreq.Request.(tmsp.RequestFlush); ok {
err = p.bufWriter.Flush()
if err != nil {
p.StopForError(err)
return
}
}
p.didSendReq(qreq)
}
}
}
func (p *ProxyApp) recvResponseRoutine() {
r := bufio.NewReader(p.conn) // Buffer reads
for {
var res tmsp.Response
var n int
var err error
wire.ReadBinaryPtr(&res, r, maxResponseSize, &n, &err)
if err != nil {
p.StopForError(err)
return
}
switch res := res.(type) {
case tmsp.ResponseException:
p.StopForError(errors.New(res.Error))
case tmsp.ResponseEvent:
p.didRecvEvent(res.Event)
default:
err := p.didRecvResponse(res)
if err != nil {
p.StopForError(err)
}
}
}
}
func (p *ProxyApp) didSendReq(qreq QueuedRequest) {
p.mtx.Lock()
defer p.mtx.Unlock()
p.reqSent.PushBack(qreq)
if p.reqPending == nil {
p.reqPending = p.reqSent.Front()
}
}
func (p *ProxyApp) didRecvResponse(res tmsp.Response) error {
p.mtx.Lock()
defer p.mtx.Unlock()
if p.reqPending == nil {
return fmt.Errorf("Unexpected result type %v when nothing expected",
reflect.TypeOf(res))
} else {
qreq := p.reqPending.Value.(QueuedRequest)
if !resMatchesReq(qreq.Request, res) {
return fmt.Errorf("Unexpected result type %v when response to %v expected",
reflect.TypeOf(res), reflect.TypeOf(qreq.Request))
}
if qreq.Sync {
qreq.Done()
}
p.reqPending = p.reqPending.Next()
}
p.resReceived.PushBack(res)
return nil
}
func (p *ProxyApp) didRecvEvent(event tmsp.Event) {
p.mtx.Lock()
defer p.mtx.Unlock()
p.eventsReceived.PushBack(event)
}
//----------------------------------------
func (p *ProxyApp) EchoAsync(key string) {
p.queueRequestAsync(tmsp.RequestEcho{key})
}
func (p *ProxyApp) FlushAsync() {
p.queueRequestAsync(tmsp.RequestFlush{})
}
func (p *ProxyApp) AppendTxAsync(tx []byte) {
p.queueRequestAsync(tmsp.RequestAppendTx{tx})
}
func (p *ProxyApp) GetHashAsync() {
p.queueRequestAsync(tmsp.RequestGetHash{})
}
/*
func (p *ProxyApp) CommitAsync() {
p.queueRequestAsync(tmsp.RequestCommit{})
}
func (p *ProxyApp) RollbackAsync() {
p.queueRequestAsync(tmsp.RequestRollback{})
}
*/
func (p *ProxyApp) SetEventsModeAsync(mode tmsp.EventsMode) {
p.queueRequestAsync(tmsp.RequestSetEventsMode{mode})
}
func (p *ProxyApp) AddListenerAsync(key string) {
p.queueRequestAsync(tmsp.RequestAddListener{key})
}
func (p *ProxyApp) RemListenerAsync(key string) {
p.queueRequestAsync(tmsp.RequestRemListener{key})
}
//----------------------------------------
// Get valid txs, root hash, events; or error
// Clears internal buffers
func (p *ProxyApp) ReapSync(commit bool) (txs [][]byte, hash []byte, events []tmsp.Event, err error) {
if commit {
// Send asynchronous commit
p.queueRequestAsync(tmsp.RequestCommit{})
// NOTE: we're assuming that there won't be a race condition.
}
// Get hash.
p.queueRequestAsync(tmsp.RequestGetHash{})
// Flush everything.
p.queueRequestSync(tmsp.RequestFlush{})
// Maybe there was an error in response matching
if p.err != nil {
return nil, nil, nil, p.err
}
// Process the resReceived/reqSent/reqPending.
if p.resReceived.Len() != p.reqSent.Len() {
PanicSanity("Unmatched requests & responses")
}
var commitCounter = 0
txs = make([][]byte, 0, p.reqSent.Len())
events = make([]tmsp.Event, 0, p.eventsReceived.Len())
reqE, resE := p.reqSent.Front(), p.resReceived.Front()
for ; reqE != nil; reqE, resE = reqE.Next(), resE.Next() {
req, res := reqE.Value.(tmsp.Request), resE.Value.(tmsp.Response)
switch req := req.(type) {
case tmsp.RequestAppendTx:
txs = append(txs, req.TxBytes)
case tmsp.RequestGetHash:
hash = res.(tmsp.ResponseGetHash).Hash
case tmsp.RequestCommit:
if commitCounter > 0 {
PanicSanity("Unexpected Commit response")
}
commitCounter++
case tmsp.RequestRollback:
PanicSanity("Unexpected Rollback response")
default:
// ignore other messages
}
}
for eE := p.eventsReceived.Front(); eE != nil; eE = eE.Next() {
events = append(events, eE.Value.(tmsp.Event))
}
return txs, hash, events, nil
}
// Rollback or error
// Clears internal buffers
func (p *ProxyApp) RollbackSync() (err error) {
// Get hash.
p.queueRequestAsync(tmsp.RequestRollback{})
// Flush everything.
p.queueRequestSync(tmsp.RequestFlush{})
// Maybe there was an error in response matching
if p.err != nil {
return p.err
}
p.reqSent = list.New()
p.reqPending = nil
p.resReceived = list.New()
p.eventsReceived = list.New()
return nil
}
func (p *ProxyApp) InfoSync() []string {
p.queueRequestAsync(tmsp.RequestInfo{})
p.queueRequestSync(tmsp.RequestFlush{})
return p.resReceived.Back().Prev().Value.(tmsp.ResponseInfo).Data
}
func (p *ProxyApp) FlushSync() {
p.queueRequestSync(tmsp.RequestFlush{})
}
//----------------------------------------
func (p *ProxyApp) queueRequestAsync(req tmsp.Request) {
qreq := QueuedRequest{Request: req}
p.reqQueue <- qreq
}
func (p *ProxyApp) queueRequestSync(req tmsp.Request) {
qreq := QueuedRequest{
req,
true,
waitGroup1(),
}
p.reqQueue <- qreq
qreq.Wait()
}
//----------------------------------------
func waitGroup1() (wg *sync.WaitGroup) {
wg = &sync.WaitGroup{}
wg.Add(1)
return
}
func resMatchesReq(req tmsp.Request, res tmsp.Response) (ok bool) {
switch req.(type) {
case tmsp.RequestEcho:
_, ok = res.(tmsp.ResponseEcho)
case tmsp.RequestFlush:
_, ok = res.(tmsp.ResponseFlush)
case tmsp.RequestInfo:
_, ok = res.(tmsp.ResponseInfo)
case tmsp.RequestAppendTx:
_, ok = res.(tmsp.ResponseAppendTx)
case tmsp.RequestGetHash:
_, ok = res.(tmsp.ResponseGetHash)
case tmsp.RequestCommit:
_, ok = res.(tmsp.ResponseCommit)
case tmsp.RequestRollback:
_, ok = res.(tmsp.ResponseRollback)
case tmsp.RequestSetEventsMode:
_, ok = res.(tmsp.ResponseSetEventsMode)
case tmsp.RequestAddListener:
_, ok = res.(tmsp.ResponseAddListener)
case tmsp.RequestRemListener:
_, ok = res.(tmsp.ResponseRemListener)
default:
return false
}
return
}
type QueuedRequest struct {
tmsp.Request
Sync bool
*sync.WaitGroup
}
+306
View File
@@ -0,0 +1,306 @@
package proxy
import (
"bufio"
"container/list"
"errors"
"fmt"
"net"
"reflect"
"sync"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-wire"
tmsp "github.com/tendermint/tmsp/types"
)
const maxResponseSize = 1048576 // 1MB
// This is goroutine-safe, but users should beware that
// the application in general is not meant to be interfaced
// with concurrent callers.
type remoteAppContext struct {
QuitService
sync.Mutex
reqQueue chan *reqRes
mtx sync.Mutex
conn net.Conn
bufWriter *bufio.Writer
err error
reqSent *list.List
resCb func(tmsp.Request, tmsp.Response)
}
func NewRemoteAppContext(conn net.Conn, bufferSize int) *remoteAppContext {
app := &remoteAppContext{
reqQueue: make(chan *reqRes, bufferSize),
conn: conn,
bufWriter: bufio.NewWriter(conn),
reqSent: list.New(),
resCb: nil,
}
app.QuitService = *NewQuitService(nil, "remoteAppContext", app)
return app
}
func (app *remoteAppContext) OnStart() error {
app.QuitService.OnStart()
go app.sendRequestsRoutine()
go app.recvResponseRoutine()
return nil
}
func (app *remoteAppContext) OnStop() {
app.QuitService.OnStop()
app.conn.Close()
}
func (app *remoteAppContext) SetResponseCallback(resCb Callback) {
app.mtx.Lock()
defer app.mtx.Unlock()
app.resCb = resCb
}
func (app *remoteAppContext) StopForError(err error) {
app.mtx.Lock()
fmt.Println("Stopping remoteAppContext for error:", err)
if app.err == nil {
app.err = err
}
app.mtx.Unlock()
app.Stop()
}
func (app *remoteAppContext) Error() error {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.err
}
//----------------------------------------
func (app *remoteAppContext) sendRequestsRoutine() {
for {
var n int
var err error
select {
case <-app.QuitService.Quit:
return
case reqres := <-app.reqQueue:
wire.WriteBinary(reqres.Request, app.bufWriter, &n, &err)
if err != nil {
app.StopForError(err)
return
}
if _, ok := reqres.Request.(tmsp.RequestFlush); ok {
err = app.bufWriter.Flush()
if err != nil {
app.StopForError(err)
return
}
}
app.didSendReq(reqres)
}
}
}
func (app *remoteAppContext) recvResponseRoutine() {
r := bufio.NewReader(app.conn) // Buffer reads
for {
var res tmsp.Response
var n int
var err error
wire.ReadBinaryPtr(&res, r, maxResponseSize, &n, &err)
if err != nil {
app.StopForError(err)
return
}
switch res := res.(type) {
case tmsp.ResponseException:
app.StopForError(errors.New(res.Error))
default:
err := app.didRecvResponse(res)
if err != nil {
app.StopForError(err)
}
}
}
}
func (app *remoteAppContext) didSendReq(reqres *reqRes) {
app.mtx.Lock()
defer app.mtx.Unlock()
app.reqSent.PushBack(reqres)
}
func (app *remoteAppContext) didRecvResponse(res tmsp.Response) error {
app.mtx.Lock()
defer app.mtx.Unlock()
// Special logic for events which have no corresponding requests.
if _, ok := res.(tmsp.ResponseEvent); ok && app.resCb != nil {
app.resCb(nil, res)
return nil
}
// Get the first reqRes
next := app.reqSent.Front()
if next == nil {
return fmt.Errorf("Unexpected result type %v when nothing expected", reflect.TypeOf(res))
}
reqres := next.Value.(*reqRes)
if !resMatchesReq(reqres.Request, res) {
return fmt.Errorf("Unexpected result type %v when response to %v expected",
reflect.TypeOf(res), reflect.TypeOf(reqres.Request))
}
reqres.Response = res // Set response
reqres.Done() // Release waiters
app.reqSent.Remove(next) // Pop first item from linked list
// Callback if there is a listener
if app.resCb != nil {
app.resCb(reqres.Request, res)
}
return nil
}
//----------------------------------------
func (app *remoteAppContext) EchoAsync(msg string) {
app.queueRequest(tmsp.RequestEcho{msg})
}
func (app *remoteAppContext) FlushAsync() {
app.queueRequest(tmsp.RequestFlush{})
}
func (app *remoteAppContext) SetOptionAsync(key string, value string) {
app.queueRequest(tmsp.RequestSetOption{key, value})
}
func (app *remoteAppContext) AppendTxAsync(tx []byte) {
app.queueRequest(tmsp.RequestAppendTx{tx})
}
func (app *remoteAppContext) GetHashAsync() {
app.queueRequest(tmsp.RequestGetHash{})
}
func (app *remoteAppContext) CommitAsync() {
app.queueRequest(tmsp.RequestCommit{})
}
func (app *remoteAppContext) RollbackAsync() {
app.queueRequest(tmsp.RequestRollback{})
}
func (app *remoteAppContext) AddListenerAsync(key string) {
app.queueRequest(tmsp.RequestAddListener{key})
}
func (app *remoteAppContext) RemListenerAsync(key string) {
app.queueRequest(tmsp.RequestRemListener{key})
}
//----------------------------------------
func (app *remoteAppContext) InfoSync() (info []string, err error) {
reqres := app.queueRequest(tmsp.RequestInfo{})
app.FlushSync()
if app.err != nil {
return nil, app.err
}
return reqres.Response.(tmsp.ResponseInfo).Data, nil
}
func (app *remoteAppContext) FlushSync() error {
app.queueRequest(tmsp.RequestFlush{}).Wait()
return app.err
}
func (app *remoteAppContext) GetHashSync() (hash []byte, err error) {
reqres := app.queueRequest(tmsp.RequestGetHash{})
app.FlushSync()
if app.err != nil {
return nil, app.err
}
return reqres.Response.(tmsp.ResponseGetHash).Hash, nil
}
// Commits or error
func (app *remoteAppContext) CommitSync() (err error) {
app.queueRequest(tmsp.RequestCommit{})
app.FlushSync()
return app.err
}
// Rollback or error
// Clears internal buffers
func (app *remoteAppContext) RollbackSync() (err error) {
app.queueRequest(tmsp.RequestRollback{})
app.FlushSync()
return app.err
}
//----------------------------------------
func (app *remoteAppContext) queueRequest(req tmsp.Request) *reqRes {
reqres := NewreqRes(req)
// TODO: set app.err if reqQueue times out
app.reqQueue <- reqres
return reqres
}
//----------------------------------------
func resMatchesReq(req tmsp.Request, res tmsp.Response) (ok bool) {
switch req.(type) {
case tmsp.RequestEcho:
_, ok = res.(tmsp.ResponseEcho)
case tmsp.RequestFlush:
_, ok = res.(tmsp.ResponseFlush)
case tmsp.RequestInfo:
_, ok = res.(tmsp.ResponseInfo)
case tmsp.RequestSetOption:
_, ok = res.(tmsp.ResponseSetOption)
case tmsp.RequestAppendTx:
_, ok = res.(tmsp.ResponseAppendTx)
case tmsp.RequestGetHash:
_, ok = res.(tmsp.ResponseGetHash)
case tmsp.RequestCommit:
_, ok = res.(tmsp.ResponseCommit)
case tmsp.RequestRollback:
_, ok = res.(tmsp.ResponseRollback)
case tmsp.RequestAddListener:
_, ok = res.(tmsp.ResponseAddListener)
case tmsp.RequestRemListener:
_, ok = res.(tmsp.ResponseRemListener)
default:
return false
}
return
}
type reqRes struct {
tmsp.Request
*sync.WaitGroup
tmsp.Response // Not set atomically, so be sure to use WaitGroup.
}
func NewreqRes(req tmsp.Request) *reqRes {
return &reqRes{
Request: req,
WaitGroup: waitGroup1(),
Response: nil,
}
}
func waitGroup1() (wg *sync.WaitGroup) {
wg = &sync.WaitGroup{}
wg.Add(1)
return
}
@@ -26,7 +26,8 @@ func TestEcho(t *testing.T) {
logBuffer := bytes.NewBuffer(nil)
logConn := logio.NewLoggedConn(conn, logBuffer)
proxy := NewProxyApp(logConn, 10)
proxy := NewRemoteAppContext(logConn, 10)
proxy.SetResponseCallback(nil)
proxy.Start()
for i := 0; i < 1000; i++ {
@@ -34,17 +35,11 @@ func TestEcho(t *testing.T) {
}
proxy.FlushSync()
if proxy.reqSent.Len() != 1001 {
t.Error(Fmt("Expected 1001 requests sent, got %v",
proxy.reqSent.Len()))
}
if proxy.resReceived.Len() != 1001 {
t.Error(Fmt("Expected 1001 responses received, got %v",
proxy.resReceived.Len()))
}
if t.Failed() {
logio.PrintReader(logBuffer)
}
/*
if t.Failed() {
logio.PrintReader(logBuffer)
}
*/
}
func BenchmarkEcho(b *testing.B) {
@@ -61,7 +56,7 @@ func BenchmarkEcho(b *testing.B) {
b.Log("Connected")
}
proxy := NewProxyApp(conn, 10)
proxy := NewRemoteAppContext(conn, 10)
proxy.Start()
echoString := strings.Repeat(" ", 200)
b.StartTimer() // Start benchmarking tests
@@ -91,10 +86,12 @@ func TestInfo(t *testing.T) {
logBuffer := bytes.NewBuffer(nil)
logConn := logio.NewLoggedConn(conn, logBuffer)
proxy := NewProxyApp(logConn, 10)
proxy := NewRemoteAppContext(logConn, 10)
proxy.Start()
data := proxy.InfoSync()
data, err := proxy.InfoSync()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if data[0] != "size:0" {
t.Error("Expected ResponseInfo with one element 'size:0' but got something else")
}