mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 18:34:02 +03:00
reworked hub
This commit is contained in:
+45
-42
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package hub
|
|
||||||
|
|
||||||
import "github.com/9seconds/mtg/protocol"
|
|
||||||
|
|
||||||
type connectionHubRequest struct {
|
|
||||||
req *protocol.TelegramRequest
|
|
||||||
responseChan chan<- *connection
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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
@@ -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 = ®istry{
|
||||||
|
conns: map[string]*ctxChannel{},
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
Hub = &hub{
|
||||||
|
subs: map[string]*connectionHub{},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
+8
-10
@@ -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 = ®istry{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
conns: map[string]*closeableChannel{},
|
conns: map[string]*ctxChannel{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user