mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 18:20:27 +00:00
167 lines
2.9 KiB
Go
167 lines
2.9 KiB
Go
package xfer
|
|
|
|
import (
|
|
"io"
|
|
"sync"
|
|
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
// Pipe is a bi-directional channel from something in the probe
|
|
// to the UI.
|
|
type Pipe interface {
|
|
Ends() (io.ReadWriter, io.ReadWriter)
|
|
CopyToWebsocket(io.ReadWriter, Websocket) error
|
|
|
|
Close() error
|
|
Closed() bool
|
|
OnClose(func())
|
|
}
|
|
|
|
type pipe struct {
|
|
mtx sync.Mutex
|
|
wg sync.WaitGroup
|
|
port, starboard io.ReadWriter
|
|
closers []io.Closer
|
|
quit chan struct{}
|
|
closed bool
|
|
onClose func()
|
|
}
|
|
|
|
// NewPipeFromEnds makes a new pipe specifying its ends
|
|
func NewPipeFromEnds(local io.ReadWriter, remote io.ReadWriter) Pipe {
|
|
return &pipe{
|
|
port: local,
|
|
starboard: remote,
|
|
quit: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// NewPipe makes a new pipe
|
|
func NewPipe() Pipe {
|
|
r1, w1 := io.Pipe()
|
|
r2, w2 := io.Pipe()
|
|
return &pipe{
|
|
port: struct {
|
|
io.Reader
|
|
io.Writer
|
|
}{
|
|
r1, w2,
|
|
},
|
|
starboard: struct {
|
|
io.Reader
|
|
io.Writer
|
|
}{
|
|
r2, w1,
|
|
},
|
|
closers: []io.Closer{
|
|
r1, r2, w1, w2,
|
|
},
|
|
quit: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (p *pipe) Ends() (io.ReadWriter, io.ReadWriter) {
|
|
return p.port, p.starboard
|
|
}
|
|
|
|
func (p *pipe) Close() error {
|
|
p.mtx.Lock()
|
|
var onClose func()
|
|
if !p.closed {
|
|
p.closed = true
|
|
close(p.quit)
|
|
for _, c := range p.closers {
|
|
c.Close()
|
|
}
|
|
onClose = p.onClose
|
|
}
|
|
p.mtx.Unlock()
|
|
p.wg.Wait()
|
|
|
|
// Don't run onClose under lock.
|
|
if onClose != nil {
|
|
onClose()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *pipe) Closed() bool {
|
|
p.mtx.Lock()
|
|
defer p.mtx.Unlock()
|
|
return p.closed
|
|
}
|
|
|
|
func (p *pipe) OnClose(f func()) {
|
|
p.mtx.Lock()
|
|
defer p.mtx.Unlock()
|
|
p.onClose = f
|
|
}
|
|
|
|
// CopyToWebsocket copies pipe data to/from a websocket. It blocks.
|
|
func (p *pipe) CopyToWebsocket(end io.ReadWriter, conn Websocket) error {
|
|
p.mtx.Lock()
|
|
if p.closed {
|
|
p.mtx.Unlock()
|
|
return nil
|
|
}
|
|
p.wg.Add(1)
|
|
p.mtx.Unlock()
|
|
defer p.wg.Done()
|
|
|
|
// The goroutines below both post their errors to the channel, but if you close()
|
|
// the pipe before any errors then the pipe may not get read from. Therefore it
|
|
// needs up to 2 slots free.
|
|
errors := make(chan error, 2)
|
|
|
|
// Read-from-UI loop
|
|
go func() {
|
|
for {
|
|
_, buf, err := conn.ReadMessage() // TODO type should be binary message
|
|
if err != nil {
|
|
errors <- err
|
|
return
|
|
}
|
|
|
|
if p.Closed() {
|
|
return
|
|
}
|
|
|
|
if _, err := end.Write(buf); err != nil {
|
|
errors <- err
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Write-to-UI loop
|
|
go func() {
|
|
buf := make([]byte, 1024)
|
|
for {
|
|
n, err := end.Read(buf)
|
|
if err != nil {
|
|
errors <- err
|
|
return
|
|
}
|
|
|
|
if p.Closed() {
|
|
return
|
|
}
|
|
|
|
if err := conn.WriteMessage(websocket.BinaryMessage, buf[:n]); err != nil {
|
|
errors <- err
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// block until one of the goroutines exits
|
|
// this convoluted mechanism is to ensure we only close the websocket once.
|
|
select {
|
|
case err := <-errors:
|
|
return err
|
|
case <-p.quit:
|
|
return nil
|
|
}
|
|
}
|