reworked hub

This commit is contained in:
9seconds
2019-10-07 17:26:03 +03:00
parent c9743b5675
commit 413cafeeb6
7 changed files with 189 additions and 148 deletions
+45 -42
View File
@@ -10,34 +10,17 @@ import (
"github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/protocol"
) )
type connectionID int
type connection struct { type connection struct {
conn conntypes.PacketReadWriteCloser conn conntypes.PacketReadWriteCloser
mutex sync.RWMutex mutex sync.RWMutex
id connectionID shutdownOnce sync.Once
hub *connectionHub hub *connectionHub
pending uint id int
closing bool pending uint
done chan struct{}
} }
func (c *connection) Write(packet conntypes.Packet) error { func (c *connection) read() (conntypes.Packet, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
err := c.conn.Write(packet)
if err != nil {
// if we tried to write into a socket and it was broken, it is
// a time to reconsider the prescence of this socket at all.
//
// probably we need to remove it completely because it seems
// that connection is broken.
c.pending = 0
}
return err
}
func (c *connection) Read() (conntypes.Packet, error) {
packet, err := c.conn.Read() packet, err := c.conn.Read()
c.mutex.Lock() c.mutex.Lock()
@@ -51,39 +34,59 @@ func (c *connection) Read() (conntypes.Packet, error) {
return packet, err return packet, err
} }
func (c *connection) Stats() (bool, uint) { func (c *connection) write(packet conntypes.Packet) error {
err := c.conn.Write(packet)
if err != nil {
// if we tried to write into a socket and it was broken, it is
// a time to reconsider the prescence of this socket at all.
//
// probably we need to remove it completely because it seems
// that connection is broken.
c.mutex.Lock()
c.pending = 0
c.mutex.Unlock()
}
return err
}
func (c *connection) shutdown() {
c.shutdownOnce.Do(func() {
close(c.done)
c.hub.channelBrokenSockets <- c.id
})
}
func (c *connection) closed() bool {
select {
case <-c.done:
return true
default:
return false
}
}
func (c *connection) idle() bool {
c.mutex.RLock() c.mutex.RLock()
defer c.mutex.RUnlock() defer c.mutex.RUnlock()
return c.closing, c.pending return c.pending == 0
}
func (c *connection) Close() error {
c.mutex.Lock()
defer c.mutex.Unlock()
c.closing = true
return c.conn.Close()
} }
func (c *connection) run() { func (c *connection) run() {
for { for {
packet, err := c.conn.Read() packet, err := c.read()
if err != nil { if err != nil {
c.Close() c.shutdown()
c.hub.brokenSocketsChan <- c.id
c.hub = nil
return return
} }
// TODO
if channel, ok := Registry.getChannel(conntypes.ConnID{}); ok { if channel, ok := Registry.getChannel(conntypes.ConnID{}); ok {
go channel.write(packet) // nolint: errcheck go channel.write(packet) // nolint: errcheck
} }
} }
} }
func newConnection(hub *connectionHub, req *protocol.TelegramRequest) (*connection, error) { func newConnection(req *protocol.TelegramRequest, hub *connectionHub) (*connection, error) {
conn, err := mtproto.TelegramProtocol(req) conn, err := mtproto.TelegramProtocol(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot create a new connection: %w", err) return nil, fmt.Errorf("cannot create a new connection: %w", err)
@@ -92,7 +95,7 @@ func newConnection(hub *connectionHub, req *protocol.TelegramRequest) (*connecti
rv := &connection{ rv := &connection{
conn: conn, conn: conn,
hub: hub, hub: hub,
id: connectionID(rand.Int()), id: rand.Int(),
} }
go rv.run() go rv.run()
+57 -52
View File
@@ -1,84 +1,89 @@
package hub package hub
import "time" import (
"time"
"github.com/9seconds/mtg/protocol"
)
const hubGCEvery = time.Minute const hubGCEvery = time.Minute
type connectionHub struct { type connectionHubRequest struct {
sockets map[connectionID]*connection request *protocol.TelegramRequest
response chan<- *connection
brokenSocketsChan chan connectionID
connectionRequestsChan chan *connectionHubRequest
returnConnectionsChan chan *connection
} }
func (h *connectionHub) run() { type connectionHub struct {
gcTicker := time.NewTicker(hubGCEvery) sockets map[int]*connection
defer gcTicker.Stop()
channelBrokenSockets chan int
channelConnectionRequests chan *connectionHubRequest
channelReturnConnections chan *connection
}
func (c *connectionHub) run() {
ticker := time.NewTicker(hubGCEvery)
defer ticker.Stop()
for { for {
select { select {
case <-gcTicker.C: case <-ticker.C:
h.runGC() c.runGC()
case id := <-h.brokenSocketsChan: case request := <-c.channelConnectionRequests:
h.runBrokenConnection(id) c.runConnectionRequest(request)
case request := <-h.connectionRequestsChan: case id := <-c.channelBrokenSockets:
h.runConnectionRequest(request) c.runBrokenSocket(id)
case conn := <-h.returnConnectionsChan: case conn := <-c.channelReturnConnections:
h.runReturnConnection(conn) c.runReturnConnection(conn)
} }
} }
} }
func (h *connectionHub) runBrokenConnection(id connectionID) { func (c *connectionHub) runGC() {
delete(h.sockets, id) for key, conn := range c.sockets {
}
func (h *connectionHub) runGC() {
for key, conn := range h.sockets {
closing, pending := conn.Stats()
switch { switch {
case closing: case conn.closed():
delete(h.sockets, key) delete(c.sockets, key)
case pending == 0: case conn.idle():
conn.Close() conn.shutdown()
delete(h.sockets, key) delete(c.sockets, key)
return return
} }
} }
} }
func (h *connectionHub) runConnectionRequest(req *connectionHubRequest) { func (c *connectionHub) runConnectionRequest(req *connectionHubRequest) {
for key, conn := range h.sockets { for key, conn := range c.sockets {
closing, _ := conn.Stats() delete(c.sockets, key)
delete(h.sockets, key) if !conn.closed() {
req.response <- conn
if !closing { close(req.response)
req.responseChan <- conn
return return
} }
} }
newConn, err := newConnection(h, req.req) if conn, err := newConnection(req.request, c); err == nil {
if err != nil { req.response <- conn
close(req.responseChan)
return
} }
close(req.response)
req.responseChan <- newConn
} }
func (h *connectionHub) runReturnConnection(conn *connection) { func (c *connectionHub) runBrokenSocket(id int) {
h.sockets[conn.id] = conn delete(c.sockets, id)
}
func (c *connectionHub) runReturnConnection(conn *connection) {
c.sockets[conn.id] = conn
} }
func newConnectionHub() *connectionHub { func newConnectionHub() *connectionHub {
return &connectionHub{ rv := &connectionHub{
sockets: map[connectionID]*connection{}, sockets: map[int]*connection{},
channelBrokenSockets: make(chan int, 1),
brokenSocketsChan: make(chan connectionID, 1), channelConnectionRequests: make(chan *connectionHubRequest),
connectionRequestsChan: make(chan *connectionHubRequest), channelReturnConnections: make(chan *connection, 1),
returnConnectionsChan: make(chan *connection, 1),
} }
go rv.run()
return rv
} }
-8
View File
@@ -1,8 +0,0 @@
package hub
import "github.com/9seconds/mtg/protocol"
type connectionHubRequest struct {
req *protocol.TelegramRequest
responseChan chan<- *connection
}
+11 -10
View File
@@ -12,46 +12,47 @@ const closeableChannelReadTimeout = 2 * time.Minute
type ChannelReadCloser interface { type ChannelReadCloser interface {
Read() (conntypes.Packet, error) Read() (conntypes.Packet, error)
Close() Close() error
} }
type closeableChannel struct { type ctxChannel struct {
channel chan conntypes.Packet channel chan conntypes.Packet
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
} }
func (c *closeableChannel) Read() (conntypes.Packet, error) { func (c *ctxChannel) Read() (conntypes.Packet, error) {
timer := time.NewTimer(closeableChannelReadTimeout) timer := time.NewTimer(closeableChannelReadTimeout)
defer timer.Stop() defer timer.Stop()
select { select {
case <-timer.C: case <-timer.C:
return nil, errors.New("timeout") return nil, ErrTimeout
case <-c.ctx.Done(): case <-c.ctx.Done():
return nil, errors.New("channel was closed") return nil, ErrClosed
case packet := <-c.channel: case packet := <-c.channel:
return packet, nil return packet, nil
} }
} }
func (c *closeableChannel) write(packet conntypes.Packet) error { func (c *ctxChannel) write(packet conntypes.Packet) error {
select { select {
case <-c.ctx.Done(): case <-c.ctx.Done():
return errors.New("channel was closed") return ErrClosed
case c.channel <- packet: case c.channel <- packet:
return nil return nil
} }
} }
func (c *closeableChannel) Close() { func (c *ctxChannel) Close() error {
c.cancel() c.cancel()
c.channel = nil c.channel = nil
return nil
} }
func newCloseableChannel(ctx context.Context) *closeableChannel { func newCtxChannel(ctx context.Context) *ctxChannel {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
return &closeableChannel{ return &ctxChannel{
channel: make(chan conntypes.Packet), channel: make(chan conntypes.Packet),
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
+38 -26
View File
@@ -1,48 +1,60 @@
package hub package hub
import ( import (
"errors" "encoding/binary"
"fmt"
"strings"
"sync" "sync"
"github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/protocol"
) )
type Concentrator struct { type hub struct {
hubs sync.Map subs map[string]*connectionHub
mutex sync.RWMutex
} }
func (c *Concentrator) Write(packet conntypes.Packet, req *protocol.TelegramRequest) error { func (h *hub) Write(packet conntypes.Packet, req *protocol.TelegramRequest) error {
hub := c.getHub(req) sub := h.getHub(req)
connectionChan := make(chan *connection) connections := make(chan *connection)
hub.connectionRequestsChan <- &connectionHubRequest{ sub.channelConnectionRequests <- &connectionHubRequest{
req: req, request: req,
responseChan: connectionChan, response: connections,
} }
conn, ok := <-connectionChan conn, ok := <-connections
if !ok { if !ok {
return errors.New("cannot establish connection to telegram") return ErrCannotCreateConnection
} }
if err := conn.write(packet); err != nil {
return fmt.Errorf("cannot send packet: %w", err)
}
return nil
} }
func (c *Concentrator) getHub(req *protocol.TelegramRequest) *connectionHub { func (h *hub) getHub(req *protocol.TelegramRequest) *connectionHub {
dcMapRaw, ok := c.hubs.Load(req.ClientProtocol.DC()) keyBuilder := strings.Builder{}
if !ok { binary.Write(&keyBuilder, binary.LittleEndian, int16(req.ClientProtocol.DC()))
dcMapRaw, _ = c.hubs.LoadOrStore(req.ClientProtocol.DC(), &sync.Map{}) keyBuilder.WriteRune('_')
} binary.Write(&keyBuilder, binary.LittleEndian, uint8(req.ClientProtocol.ConnectionProtocol()))
dcMap := dcMapRaw.(*sync.Map) key := keyBuilder.String()
h.mutex.RLock()
rv, ok := h.subs[key]
h.mutex.RUnlock()
loaded := true
hubRaw, ok := dcMap.Load(req.ClientProtocol.ConnectionProtocol())
if !ok { if !ok {
hubRaw, loaded = dcMap.LoadOrStore(req.ClientProtocol.ConnectionProtocol(), h.mutex.Lock()
newConnectionHub()) defer h.mutex.Unlock()
}
hub := hubRaw.(*connectionHub) rv, ok = h.subs[key]
if !loaded { if !ok {
go hub.run() rv = newConnectionHub()
h.subs[key] = rv
}
} }
return hub return rv
} }
+30
View File
@@ -0,0 +1,30 @@
package hub
import (
"context"
"errors"
"sync"
)
var (
Registry *registry
Hub *hub
ErrTimeout = errors.New("timeout")
ErrClosed = errors.New("channel was closed")
ErrCannotCreateConnection = errors.New("cannot create connection")
initOnce sync.Once
)
func Init(ctx context.Context) {
initOnce.Do(func() {
Registry = &registry{
conns: map[string]*ctxChannel{},
ctx: ctx,
}
Hub = &hub{
subs: map[string]*connectionHub{},
}
})
}
+8 -10
View File
@@ -7,16 +7,14 @@ import (
"github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/conntypes"
) )
var Registry *RegistryStruct type registry struct {
conns map[string]*ctxChannel
type RegistryStruct struct {
conns map[string]*closeableChannel
ctx context.Context ctx context.Context
mutex sync.RWMutex mutex sync.RWMutex
} }
func (r *RegistryStruct) Register(id conntypes.ConnID) ChannelReadCloser { func (r *registry) Register(id conntypes.ConnID) ChannelReadCloser {
channel := newCloseableChannel(r.ctx) channel := newCtxChannel(r.ctx)
r.mutex.Lock() r.mutex.Lock()
r.conns[string(id[:])] = channel r.conns[string(id[:])] = channel
@@ -25,7 +23,7 @@ func (r *RegistryStruct) Register(id conntypes.ConnID) ChannelReadCloser {
return channel return channel
} }
func (r *RegistryStruct) Unregister(id conntypes.ConnID) { func (r *registry) Unregister(id conntypes.ConnID) {
r.mutex.Lock() r.mutex.Lock()
defer r.mutex.Unlock() defer r.mutex.Unlock()
@@ -35,7 +33,7 @@ func (r *RegistryStruct) Unregister(id conntypes.ConnID) {
} }
} }
func (r *RegistryStruct) getChannel(id conntypes.ConnID) (*closeableChannel, bool) { func (r *registry) getChannel(id conntypes.ConnID) (*ctxChannel, bool) {
r.mutex.RLock() r.mutex.RLock()
defer r.mutex.RUnlock() defer r.mutex.RUnlock()
@@ -46,8 +44,8 @@ func (r *RegistryStruct) getChannel(id conntypes.ConnID) (*closeableChannel, boo
} }
func InitRegistry(ctx context.Context) { func InitRegistry(ctx context.Context) {
Registry = &RegistryStruct{ Registry = &registry{
ctx: ctx, ctx: ctx,
conns: map[string]*closeableChannel{}, conns: map[string]*ctxChannel{},
} }
} }