public async Task WriteCheckpoint(Flow flow) { var result = await _context.Connection.AppendToStreamAsync( flow.Context.Key.GetCheckpointStream(), ExpectedVersion.Any, _context.GetCheckpointEventData(flow)); if (flow is Query query) { try { var checkpoint = new TimelinePosition(result.NextExpectedVersion); var etag = QueryETag.From(query.Context.Key, checkpoint).ToString(); await _context.Connection.AppendToStreamAsync( TimelineStreams.ChangedQueries, ExpectedVersion.Any, _context.GetQueryChangedEventData(new QueryChanged(etag))); } catch (Exception error) { Log.Error(error, "Failed to write QueryChanged for {Query} - subscribers will not be aware of the change until the query observes another event.", query); } } }
public async Task <QueryState> ReadState(QueryETag etag) { var stream = etag.Key.GetCheckpointStream(); var result = await _context.Connection.ReadEventAsync(stream, StreamPosition.End, resolveLinkTos : false); switch (result.Status) { case EventReadStatus.NoStream: case EventReadStatus.NotFound: return(new QueryState(etag.WithoutCheckpoint(), GetDefaultData(etag))); case EventReadStatus.Success: var number = result.Event?.Event.EventNumber; var data = result.Event?.Event.Data; var checkpoint = new TimelinePosition(number); return(checkpoint == etag.Checkpoint ? new QueryState(etag) : new QueryState(etag.WithCheckpoint(checkpoint), new MemoryStream(data))); default: throw new Exception($"Unexpected result when reading {stream}: {result.Status}"); } }
internal ReadQueryContentCommand(EventStoreContext context, QueryETag etag) { _context = context; _etag = etag; _stream = etag.Key.GetCheckpointStream(); }
internal async Task <TQuery> Get <TQuery>(Id id) where TQuery : Query { var key = GetKey <TQuery>(id); var state = await _queryDb.ReadState(QueryETag.From(key)); return((TQuery)Deserialize(key.Type, state.ReadContent())); }
internal void OnChanged(QueryETag etag) { if (_instancesByKey.TryGetValue(etag.Key, out var instance)) { _notifier.NotifyChanged(etag, instance.ConnectionIds); } _changedWindow.OnChanged(etag); }
QueryETag ReadETag(Type type, Id id) { if (TryGetIfNoneMatch(out var ifNoneMatch)) { return(QueryETag.From(ifNoneMatch, _area)); } var key = FlowKey.From(_area.Queries.Get(type), id); return(QueryETag.From(key, TimelinePosition.None)); }
internal void OnChanged(QueryETag etag) { var now = Clock.Now; _queriesByKey[etag.Key] = new QueryWindow(etag, now); if (now - _whenTrimmed > _trimDelay) { TrimQueries(now); } }
async Task TryWriteQueryChanged(Flow query) { try { var etag = QueryETag.From(query.Context.Key, query.Context.CheckpointPosition).ToString(); await _context.AppendToClient(new QueryChanged(etag)); } catch (Exception error) { Log.Error(error, "Failed to write update of query {Query} to the client stream. Subscribers will not be aware of the change until the query observes another event.", query); } }
async Task TryWriteQueryStopped(Flow flow) { try { var etag = QueryETag.From(flow.Context.Key, flow.Context.CheckpointPosition).ToString(); await _context.AppendToClient(new QueryStopped(etag, flow.Context.ErrorMessage)); } catch (Exception error) { Log.Error(error, "Failed to write stoppage of query {Query} to the client stream. Subscribers will not be aware of the failure", flow); } }
TaskSource RemoveOrStartTask(QueryETag etag) { lock (_tasksByETag) { if (!_tasksByETag.Remove(etag, out var task)) { task = new TaskSource(); _tasksByETag[etag] = task; } return(task); } }
QueryContent GetCheckpointContent(QueryETag etag, ResolvedEvent e) { var metadata = _context.ReadCheckpointMetadata(e); if (metadata.ErrorPosition.IsSome) { throw new Exception($"Query is stopped at {metadata.ErrorPosition} with the following error: {metadata.ErrorMessage}"); } var checkpoint = new TimelinePosition(e.Event.EventNumber); return(checkpoint == etag.Checkpoint ? new QueryContent(etag) : new QueryContent(etag.WithCheckpoint(checkpoint), new MemoryStream(e.Event.Data))); }
// // Subscriptions // public async Task SubscribeToChanged(Id connectionId, QueryETag etag) { var connection = _connectionsById.GetOrAdd(connectionId, _ => new QueryConnection(connectionId, this)); var instance = _instancesByKey.GetOrAdd(etag.Key, _ => new QueryInstance(etag.Key, this)); connection.Subscribe(instance); await _changedSubscription.EnsureSubscribed(); var latestETag = _changedWindow.OnSubscribed(etag); if (latestETag != etag) { await _notifier.NotifyChanged(latestETag, Many.Of(connectionId)); } }
async Task <Stream> AppendAndWaitUntilChanged(FlowKey key, Event e, TestTimeline timeline) { var connectionId = Id.FromGuid(); await _queryDb.SubscribeToChanged(connectionId, QueryETag.From(key)); var position = await timeline.Append(e); var newETag = QueryETag.From(key, position); await _notifier.WaitUntilChanged(connectionId, newETag); _queryDb.UnsubscribeFromChanged(connectionId); return(await _queryDb.ReadContent(newETag)); }
QueryETag ETagFrom(string etag) => QueryETag.From(etag, _context.Area);
void OnChanged(ResolvedEvent e) => _db.OnChanged(QueryETag.From( _context.Json.FromJsonUtf8 <QueryChanged>(e.Event.Data).ETag, _context.Area));
public Task <QueryContent> ReadQueryContent(QueryETag etag) => ReadQueryCheckpoint(etag.Key, () => GetDefaultContent(etag), e => GetCheckpointContent(etag, e));
QueryContent GetDefaultContent(QueryETag etag) { var defaultJson = _context.Json.ToJsonUtf8(etag.Key.Type.New()); return(new QueryContent(etag.WithoutCheckpoint(), new MemoryStream(defaultJson))); }
Task IClientObserver.OnQueryStopped(QueryETag query, string error) { GetQueryInstance(query.Key).OnError(new Exception($"Query {query} stopped with the following error: {error}")); return(Task.CompletedTask); }
public Task NotifyStopped(QueryETag etag, string error, IEnumerable <Id> subscriberIds) => GetClients(subscriberIds).SendAsync("onStopped", etag.ToString(), error);
public Task NotifyChanged(QueryETag etag, IEnumerable <Id> connectionIds) { RemoveOrStartTask(etag).TrySetResult(); return(Task.CompletedTask); }
internal QueryETag OnSubscribed(QueryETag etag) => _queriesByKey .GetOrAdd(etag.Key, _ => new QueryWindow(etag, Clock.Now)) .GetLatest(etag);
internal Task WaitUntilChanged(Id connectionId, QueryETag etag) => RemoveOrStartTask(etag).Task;
public QueryNotModifiedResult(QueryETag etag) { ETag = etag; }
public async Task <Stream> ReadContent(QueryETag etag) => etag.Checkpoint.IsNone ? GetDefaultData(etag) : await new ReadQueryContentCommand(_context, etag).Execute();
internal QueryWindow(QueryETag etag, DateTimeOffset whenChanged) { _etag = etag; _whenChanged = whenChanged; }
public Task NotifyChanged(QueryETag etag, IEnumerable <Id> subscriberIds) => GetClients(subscriberIds).SendAsync("onChanged", etag.ToString());
Stream GetDefaultData(QueryETag etag) => new MemoryStream(_context.Json.ToJsonUtf8(etag.Key.Type.New()));
internal QueryETag GetLatest(QueryETag etag) => _etag.GetLatest(etag);
public Task SubscribeToChanged(string etag) => _db.SubscribeToChanged(ConnectionId, QueryETag.From(etag, _area));
public Task NotifyChanged(QueryETag etag, IEnumerable <Id> subscriberIds) => _hubContext .Clients .Clients(subscriberIds.ToMany(id => id.ToString())) .SendAsync("onChanged", etag.ToString());