generated from dogmatiq/template-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
816 additions
and
159 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package eventstream | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/dogmatiq/enginekit/protobuf/envelopepb" | ||
"github.com/dogmatiq/enginekit/protobuf/uuidpb" | ||
"github.com/dogmatiq/persistencekit/journal" | ||
"github.com/dogmatiq/veracity/internal/eventstream/internal/eventstreamjournal" | ||
"github.com/dogmatiq/veracity/internal/messaging" | ||
"github.com/dogmatiq/veracity/internal/signaling" | ||
) | ||
|
||
type Subscriber struct { | ||
StreamID *uuidpb.UUID | ||
Offset Offset | ||
Filter func(*envelopepb.Envelope) bool | ||
Events chan<- Event | ||
|
||
canceled signaling.Event | ||
} | ||
|
||
type Reader struct { | ||
Journals journal.BinaryStore | ||
SubscribeQueue *messaging.ExchangeQueue[*Subscriber, struct{}] | ||
UnsubscribeQueue *messaging.ExchangeQueue[*Subscriber, struct{}] | ||
} | ||
|
||
func (r *Reader) Read(ctx context.Context, sub *Subscriber) error { | ||
for { | ||
if err := r.readHistorical(ctx, sub); err != nil { | ||
return err | ||
} | ||
|
||
if err := r.readContemporary(ctx, sub); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
func (r *Reader) readHistorical(ctx context.Context, sub *Subscriber) error { | ||
j, err := eventstreamjournal.Open(ctx, r.Journals, sub.StreamID) | ||
if err != nil { | ||
return err | ||
} | ||
defer j.Close() | ||
|
||
searchBegin, searchEnd, err := j.Bounds(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return journal.RangeFromSearchResult( | ||
ctx, | ||
j, | ||
searchBegin, searchEnd, | ||
eventstreamjournal.SearchByOffset(uint64(sub.Offset)), | ||
func( | ||
ctx context.Context, | ||
pos journal.Position, | ||
rec *eventstreamjournal.Record, | ||
) (bool, error) { | ||
begin := Offset(rec.StreamOffsetBefore) | ||
end := Offset(rec.StreamOffsetAfter) | ||
|
||
if begin == end { | ||
// no events in this record | ||
return true, nil | ||
} | ||
|
||
if sub.Offset < begin || sub.Offset >= end { | ||
return false, fmt.Errorf( | ||
"event stream integrity error at journal position %d: expected event at offset %d, but found offset range [%d, %d)", | ||
pos, | ||
sub.Offset, | ||
begin, | ||
end, | ||
) | ||
} | ||
|
||
index := sub.Offset - begin | ||
|
||
for _, env := range rec.GetEventsAppended().Events[index:] { | ||
if sub.Filter(env) { | ||
sub.Offset++ | ||
continue | ||
} | ||
|
||
select { | ||
case <-ctx.Done(): | ||
return false, ctx.Err() | ||
case sub.Events <- Event{sub.StreamID, sub.Offset, env}: | ||
sub.Offset++ | ||
} | ||
} | ||
|
||
return true, nil | ||
}, | ||
) | ||
} | ||
|
||
func (r *Reader) readContemporary(ctx context.Context, cur *Subscriber) error { | ||
// TODO: remote read | ||
|
||
if err := r.subscribe(ctx, cur); err != nil { | ||
return err | ||
} | ||
defer r.unsubscribe(cur) | ||
|
||
select { | ||
case <-ctx.Done(): | ||
return ctx.Err() | ||
case <-cur.canceled.Signaled(): | ||
return nil | ||
} | ||
} | ||
|
||
func (r *Reader) subscribe(ctx context.Context, cur *Subscriber) error { | ||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second) // TODO: make configurable | ||
cancel() | ||
|
||
if _, err := r.SubscribeQueue.Exchange(ctx, cur); err != nil { | ||
Check failure on line 124 in internal/eventstream/reader.go GitHub Actions / Shared / Go
|
||
return fmt.Errorf("cannot subscribe to event stream: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (r *Reader) unsubscribe(cur *Subscriber) { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
|
||
// Cancel the unsubscribe context when the subscription is canceled, | ||
// regardless of the reason. | ||
// | ||
// This handles the situation where the subscription is canceled because the | ||
// worker shutdown (and hence wont service the unsubscribe request). | ||
go func() { | ||
<-cur.canceled.Signaled() | ||
cancel() | ||
}() | ||
|
||
r.UnsubscribeQueue.Exchange(ctx, cur) | ||
Check failure on line 145 in internal/eventstream/reader.go GitHub Actions / Shared / Go
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.