add internal/{processor,streaming}
This commit is contained in:
		
							parent
							
								
									6f17b59a47
								
							
						
					
					
						commit
						5c6da51234
					
				
					 6 changed files with 128 additions and 29 deletions
				
			
		
							
								
								
									
										27
									
								
								internal/processor/processor.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								internal/processor/processor.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| package processor | ||||
| 
 | ||||
| import ( | ||||
| 	"git.sleepycat.moe/sam/mercury/internal/database" | ||||
| 	"git.sleepycat.moe/sam/mercury/internal/database/sql" | ||||
| 	"git.sleepycat.moe/sam/mercury/internal/streaming" | ||||
| ) | ||||
| 
 | ||||
| type Processor struct { | ||||
| 	SocketHolder *streaming.SocketHolder | ||||
| 
 | ||||
| 	db *sql.Base | ||||
| } | ||||
| 
 | ||||
| func New(db *sql.Base) *Processor { | ||||
| 	p := &Processor{ | ||||
| 		SocketHolder: &streaming.SocketHolder{}, | ||||
| 
 | ||||
| 		db: db, | ||||
| 	} | ||||
| 
 | ||||
| 	return p | ||||
| } | ||||
| 
 | ||||
| func (p *Processor) HandlePost(post database.Post) { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										57
									
								
								internal/streaming/event.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								internal/streaming/event.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | |||
| package streaming | ||||
| 
 | ||||
| import "encoding/json" | ||||
| 
 | ||||
| type EventType int8 | ||||
| 
 | ||||
| const ( | ||||
| 	EventTypeError EventType = 1 | ||||
| 	EventTypePost  EventType = 2 | ||||
| 
 | ||||
| 	EventTypeSubscribe   EventType = 126 | ||||
| 	EventTypeUnsubscribe EventType = 127 | ||||
| ) | ||||
| 
 | ||||
| func (et EventType) Valid() bool { | ||||
| 	switch et { | ||||
| 	case EventTypeError: | ||||
| 		return true | ||||
| 	case EventTypePost: | ||||
| 		return true | ||||
| 	case EventTypeSubscribe: | ||||
| 		return true | ||||
| 	case EventTypeUnsubscribe: | ||||
| 		return true | ||||
| 	default: | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Returns true if this event can be subscribed to/unsubscribed from | ||||
| func (et EventType) ValidReceive() bool { | ||||
| 	if !et.Valid() { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	switch et { | ||||
| 	case EventTypeError, EventTypeSubscribe, EventTypeUnsubscribe: | ||||
| 		return false | ||||
| 	default: | ||||
| 		return true | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type Event struct { | ||||
| 	Type EventType `json:"t"` | ||||
| 	Data any       `json:"d"` | ||||
| } | ||||
| 
 | ||||
| type ErrorEvent struct { | ||||
| 	Code    int    `json:"code"` | ||||
| 	Message string `json:"message"` | ||||
| } | ||||
| 
 | ||||
| type IncomingEvent struct { | ||||
| 	Type EventType       `json:"t"` | ||||
| 	Data json.RawMessage `json:"d"` // this is a RawMessage so we can easily unmarshal it later | ||||
| } | ||||
							
								
								
									
										114
									
								
								internal/streaming/sockets.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								internal/streaming/sockets.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,114 @@ | |||
| package streaming | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"github.com/oklog/ulid/v2" | ||||
| ) | ||||
| 
 | ||||
| type SocketHolder struct { | ||||
| 	// map of sockets to | ||||
| 	sockets map[ulid.ULID]*userSockets | ||||
| 	mu      sync.Mutex | ||||
| } | ||||
| 
 | ||||
| func (sh *SocketHolder) Send(acctID ulid.ULID, et EventType, data any) { | ||||
| 	userSockets := sh.SocketsFor(acctID) | ||||
| 
 | ||||
| 	userSockets.mu.Lock() | ||||
| 	sockets := make([]*Socket, len(userSockets.sockets)) | ||||
| 	copy(sockets, userSockets.sockets) | ||||
| 	userSockets.mu.Unlock() | ||||
| 
 | ||||
| 	for _, s := range sockets { | ||||
| 		if s.willAcceptEvent(et) { | ||||
| 			// the socket might block for a bit, so spin this off into a separate goroutine | ||||
| 			go func(s *Socket) { | ||||
| 				s.ch <- Event{Type: et, Data: data} | ||||
| 			}(s) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *SocketHolder) SocketsFor(acct ulid.ULID) *userSockets { | ||||
| 	s.mu.Lock() | ||||
| 	defer s.mu.Unlock() | ||||
| 
 | ||||
| 	us, ok := s.sockets[acct] | ||||
| 	if !ok { | ||||
| 		us = &userSockets{} | ||||
| 		s.sockets[acct] = us | ||||
| 	} | ||||
| 	return us | ||||
| } | ||||
| 
 | ||||
| const sessionCountLimit = 50 // no more than 50 concurrent sessions per user | ||||
| 
 | ||||
| type userSockets struct { | ||||
| 	mu      sync.Mutex | ||||
| 	sockets []*Socket | ||||
| } | ||||
| 
 | ||||
| func (s *userSockets) NewSocket(ctx context.Context, cancel context.CancelFunc) (*Socket, bool) { | ||||
| 	s.mu.Lock() | ||||
| 	defer s.mu.Unlock() | ||||
| 	if len(s.sockets) >= sessionCountLimit { | ||||
| 		return nil, false | ||||
| 	} | ||||
| 	socket := NewSocket(ctx, cancel) | ||||
| 	s.sockets = append(s.sockets, socket) | ||||
| 	return socket, true | ||||
| } | ||||
| 
 | ||||
| type Socket struct { | ||||
| 	ctx    context.Context | ||||
| 	cancel context.CancelFunc | ||||
| 
 | ||||
| 	ch    chan Event | ||||
| 	types map[EventType]struct{} | ||||
| 
 | ||||
| 	mu sync.RWMutex | ||||
| } | ||||
| 
 | ||||
| func (s *Socket) willAcceptEvent(mt EventType) bool { | ||||
| 	if mt == EventTypeError { | ||||
| 		return true | ||||
| 	} | ||||
| 
 | ||||
| 	s.mu.RLock() | ||||
| 	_, ok := s.types[mt] | ||||
| 	s.mu.RUnlock() | ||||
| 	return ok | ||||
| } | ||||
| 
 | ||||
| func (s *Socket) SetEvent(mt EventType, add bool) { | ||||
| 	s.mu.Lock() | ||||
| 	if add { | ||||
| 		s.types[mt] = struct{}{} | ||||
| 	} else { | ||||
| 		delete(s.types, mt) | ||||
| 	} | ||||
| 	s.mu.Unlock() | ||||
| } | ||||
| 
 | ||||
| func (s *Socket) Cancel() { | ||||
| 	s.cancel() | ||||
| } | ||||
| 
 | ||||
| func (s *Socket) Done() <-chan struct{} { | ||||
| 	return s.ctx.Done() | ||||
| } | ||||
| 
 | ||||
| func (s *Socket) Chan() <-chan Event { | ||||
| 	return s.ch | ||||
| } | ||||
| 
 | ||||
| func NewSocket(ctx context.Context, cancel context.CancelFunc) *Socket { | ||||
| 	return &Socket{ | ||||
| 		ctx:    ctx, | ||||
| 		cancel: cancel, | ||||
| 		ch:     make(chan Event), | ||||
| 		types:  make(map[EventType]struct{}), | ||||
| 	} | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue