mcp/streamable: use event store to fix unbounded memory issues (#335)
This CL utilizes the event store to write outgoing messages and removes the unbounded outgoing data structure. It also adds a new interface [EventStore.Open] For #190
This commit is contained in:
parent
f6118aaace
commit
ddaf35ed77
1 changed files with 72 additions and 61 deletions
27
mcp/event.go
27
mcp/event.go
|
|
@ -153,6 +153,11 @@ func scanEvents(r io.Reader) iter.Seq2[Event, error] {
|
|||
//
|
||||
// All of an EventStore's methods must be safe for use by multiple goroutines.
|
||||
type EventStore interface {
|
||||
// Open prepares the event store for a given stream. It ensures that the
|
||||
// underlying data structure for the stream is initialized, making it
|
||||
// ready to store event streams.
|
||||
Open(_ context.Context, sessionID string, streamID StreamID) error
|
||||
|
||||
// Append appends data for an outgoing event to given stream, which is part of the
|
||||
// given session.
|
||||
Append(_ context.Context, sessionID string, _ StreamID, data []byte) error
|
||||
|
|
@ -162,6 +167,7 @@ type EventStore interface {
|
|||
// Once the iterator yields a non-nil error, it will stop.
|
||||
// After's iterator must return an error immediately if any data after index was
|
||||
// dropped; it must not return partial results.
|
||||
// The stream must have been opened previously (see [EventStore.Open]).
|
||||
After(_ context.Context, sessionID string, _ StreamID, index int) iter.Seq2[[]byte, error]
|
||||
|
||||
// SessionClosed informs the store that the given session is finished, along
|
||||
|
|
@ -256,11 +262,20 @@ func NewMemoryEventStore(opts *MemoryEventStoreOptions) *MemoryEventStore {
|
|||
}
|
||||
}
|
||||
|
||||
// Append implements [EventStore.Append] by recording data in memory.
|
||||
func (s *MemoryEventStore) Append(_ context.Context, sessionID string, streamID StreamID, data []byte) error {
|
||||
// Open implements [EventStore.Open]. It ensures that the underlying data
|
||||
// structures for the given session are initialized and ready for use.
|
||||
func (s *MemoryEventStore) Open(_ context.Context, sessionID string, streamID StreamID) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.init(sessionID, streamID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// init is an internal helper function that ensures the nested map structure for a
|
||||
// given sessionID and streamID exists, creating it if necessary. It returns the
|
||||
// dataList associated with the specified IDs.
|
||||
// Requires s.mu.
|
||||
func (s *MemoryEventStore) init(sessionID string, streamID StreamID) *dataList {
|
||||
streamMap, ok := s.store[sessionID]
|
||||
if !ok {
|
||||
streamMap = make(map[StreamID]*dataList)
|
||||
|
|
@ -271,6 +286,14 @@ func (s *MemoryEventStore) Append(_ context.Context, sessionID string, streamID
|
|||
dl = &dataList{}
|
||||
streamMap[streamID] = dl
|
||||
}
|
||||
return dl
|
||||
}
|
||||
|
||||
// Append implements [EventStore.Append] by recording data in memory.
|
||||
func (s *MemoryEventStore) Append(_ context.Context, sessionID string, streamID StreamID, data []byte) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
dl := s.init(sessionID, streamID)
|
||||
// Purge before adding, so at least the current data item will be present.
|
||||
// (That could result in nBytes > maxBytes, but we'll live with that.)
|
||||
s.purge()
|
||||
|
|
|
|||
Loading…
Reference in a new issue