Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Transports/GCP/Wolverine.Pubsub/IPubsubEnvelopeMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ namespace Wolverine.Pubsub;
/// <summary>
/// Pluggable strategy for reading and writing data to Google Cloud Platform Pub/Sub
/// </summary>
public interface IPubsubEnvelopeMapper : IEnvelopeMapper<ReceivedMessage, PubsubMessage>
public interface IPubsubEnvelopeMapper : IEnvelopeMapper<PubsubMessage, PubsubMessage>
{
void MapIncomingToEnvelope(PubsubEnvelope envelope, ReceivedMessage incoming);
void MapIncomingToEnvelope(PubsubEnvelope envelope, PubsubMessage incoming);
void MapOutgoingToMessage(OutgoingMessageBatch outgoing, PubsubMessage message);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Google.Api.Gax.Grpc;
using Google.Api.Gax;
using Google.Cloud.PubSub.V1;
using JasperFx.Blocks;
using Microsoft.Extensions.Logging;
using Wolverine.Runtime;
using Wolverine.Transports;
Expand All @@ -25,39 +24,47 @@ public override async Task StartAsync()
throw new WolverinePubsubTransportNotConnectedException();
}

using var streamingPull =
_transport.SubscriberApiClient.StreamingPull(CallSettings.FromCancellationToken(_cancellation.Token));

await streamingPull.WriteAsync(new StreamingPullRequest
// Create a high-level SubscriberClient for receiving messages which may
// use multiple underlying streaming pull connections.
var subscriberClientBuilder = new SubscriberClientBuilder()
{
SubscriptionAsSubscriptionName = _endpoint.Server.Subscription.Name,
StreamAckDeadlineSeconds = 20,
MaxOutstandingMessages = _endpoint.Client.MaxOutstandingMessages,
MaxOutstandingBytes = _endpoint.Client.MaxOutstandingByteCount
});
EmulatorDetection = _transport.EmulatorDetection,
SubscriptionName = _endpoint.Server.Subscription.Name,
Settings = new SubscriberClient.Settings
{
AckDeadline = TimeSpan.FromSeconds(20),
MaxTotalAckExtension = TimeSpan.FromMinutes(10),
FlowControlSettings = new FlowControlSettings(
_endpoint.Client.MaxOutstandingMessages,
_endpoint.Client.MaxOutstandingByteCount
)
}
};

await using var stream = streamingPull.GetResponseStream();

_acknowledge = new RetryBlock<string[]>((ackIds, _) => streamingPull.WriteAsync(new StreamingPullRequest
{
AckIds = { ackIds }
}), _logger, _runtime.Cancellation);
await using var subscriberClient = await subscriberClientBuilder.BuildAsync();

try
{
await listenForMessagesAsync(async () =>
// Start the subscriber and capture the lifetime task
var subscriberLifetime = subscriberClient.StartAsync(async (msg, ct) =>
{
while (await stream.MoveNextAsync(_cancellation.Token))
{
await handleMessagesAsync(stream.Current.ReceivedMessages);
}
await handleMessagesAsync(msg);
return SubscriberClient.Reply.Ack;
Comment on lines +49 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acking should technically happen automatically here in this lambda on the return of SubscriberClient.Reply.Ack so it seems like this removes some of the code that was previously managing the Ack at a lower level, but not clear if that code I removed is "structurally required" by Wolverine.

});

// Wait for whatever condition you have for running (your helper can return the lifetime task)
await listenForMessagesAsync(() => subscriberLifetime);

// When listenForMessagesAsync returns, request a graceful stop
}
finally
{
try
{
await streamingPull.WriteCompleteAsync();
await subscriberClient.StopAsync(TimeSpan.FromSeconds(15));
// Ensure the StartAsync task has completed and observe any exceptions
// (if subscriberLifetime had an exception it will be rethrown here)
// Note: if you need the variable here, capture it in an outer scope.
}
catch (Exception ex)
{
Expand All @@ -66,4 +73,4 @@ await listenForMessagesAsync(async () =>
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public InlinePubsubListener(
IWolverineRuntime runtime
) : base(endpoint, transport, receiver, runtime)
{

}

public override async Task StartAsync()
Expand All @@ -30,7 +30,7 @@ await listenForMessagesAsync(async () =>
_cancellation.Token
);

await handleMessagesAsync(response.ReceivedMessages);
await handleMessagesAsync(response.ReceivedMessages[0].Message);
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
using Google.Cloud.PubSub.V1;
using Google.Protobuf;
using ImTools;
using JasperFx.Core;
using Wolverine.Transports;

namespace Wolverine.Pubsub.Internal;

public class PubsubEnvelopeMapper : EnvelopeMapper<ReceivedMessage, PubsubMessage>, IPubsubEnvelopeMapper
public class PubsubEnvelopeMapper : EnvelopeMapper<PubsubMessage, PubsubMessage>, IPubsubEnvelopeMapper
{
public PubsubEnvelopeMapper(PubsubEndpoint endpoint) : base(endpoint)
{
MapProperty(
e => e.Data!,
(e, m) =>
{
if (m.Message.Data.IsEmpty)
if (m.Data.IsEmpty)
{
return;
}

e.Data = m.Message.Data.ToByteArray();
e.Data = m.Data.ToByteArray();
},
(e, m) =>
{
Expand All @@ -31,15 +30,15 @@ public PubsubEnvelopeMapper(PubsubEndpoint endpoint) : base(endpoint)
m.Data = ByteString.CopyFrom(e.Data);
}
);

MapPropertyToHeader(x => x.GroupId, "group-id");
MapPropertyToHeader(x => x.DeduplicationId, "deduplication-id");
MapPropertyToHeader(x => x.PartitionKey, "partition-key");
}

public void MapIncomingToEnvelope(PubsubEnvelope envelope, ReceivedMessage incoming)
public void MapIncomingToEnvelope(PubsubEnvelope envelope, PubsubMessage incoming)
{
envelope.AckId = incoming.AckId;
envelope.AckId = incoming.MessageId;

base.MapIncomingToEnvelope(envelope, incoming);
}
Expand All @@ -56,19 +55,19 @@ protected override void writeOutgoingHeader(PubsubMessage outgoing, string key,
outgoing.Attributes[key] = value;
}

protected override void writeIncomingHeaders(ReceivedMessage incoming, Envelope envelope)
protected override void writeIncomingHeaders(PubsubMessage incoming, Envelope envelope)
{
if (incoming.Message.Attributes is null)
if (incoming.Attributes is null)
{
return;
}

foreach (var pair in incoming.Message.Attributes) envelope.Headers[pair.Key] = pair.Value;
foreach (var pair in incoming.Attributes) envelope.Headers[pair.Key] = pair.Value;
}

protected override bool tryReadIncomingHeader(ReceivedMessage incoming, string key, out string? value)
protected override bool tryReadIncomingHeader(PubsubMessage incoming, string key, out string? value)
{
if (incoming.Message.Attributes.TryGetValue(key, out var header))
if (incoming.Attributes.TryGetValue(key, out var header))
{
value = header;

Expand All @@ -79,4 +78,4 @@ protected override bool tryReadIncomingHeader(ReceivedMessage incoming, string k

return false;
}
}
}
141 changes: 34 additions & 107 deletions src/Transports/GCP/Wolverine.Pubsub/Internal/PubsubListener.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Google.Cloud.PubSub.V1;
using Google.Protobuf.Collections;
using Grpc.Core;
using JasperFx.Blocks;
using JasperFx.Core;
Expand Down Expand Up @@ -53,72 +52,6 @@ IWolverineRuntime runtime

NativeDeadLetterQueueEnabled = true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this code block because dead lettering is handled by the SubscriberClient internally as is Acking.

_acknowledge = new RetryBlock<string[]>(async (ackIds, _) =>
{
if (transport.SubscriberApiClient is null)
{
throw new WolverinePubsubTransportNotConnectedException();
}

if (ackIds.Any())
{
await transport.SubscriberApiClient.AcknowledgeAsync(
_endpoint.Server.Subscription.Name,
ackIds
);
}
}, _logger, runtime.Cancellation);

_deadLetter = new RetryBlock<Envelope>(async (e, _) =>
{
if (_deadLetterTopic is null)
{
return;
}

if (e is PubsubEnvelope pubsubEnvelope)
{
await _acknowledge.PostAsync([pubsubEnvelope.AckId]);
}

await _deadLetterTopic.SendMessageAsync(e, _logger);
}, _logger, runtime.Cancellation);
Comment on lines -73 to -86

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly here, new DLQ setup has to be written somewhere?


_requeue = new RetryBlock<Envelope>(async (e, _) =>
{
if (e is PubsubEnvelope pubsubEnvelope)
{
await _acknowledge.PostAsync([pubsubEnvelope.AckId]);
}

await _endpoint.SendMessageAsync(e, _logger);
}, _logger, runtime.Cancellation);

_complete = new RetryBlock<Envelope[]>(async (envelopes, _) =>
{
var pubsubEnvelopes = envelopes.OfType<PubsubEnvelope>().ToArray();

if (!pubsubEnvelopes.Any())
{
return;
}

if (transport.SubscriberApiClient is null)
{
throw new WolverinePubsubTransportNotConnectedException();
}

var ackIds = pubsubEnvelopes
.Select(e => e.AckId)
.Where(x => !string.IsNullOrEmpty(x))
.Distinct()
.ToArray();

await _acknowledge.PostAsync(ackIds);
}, _logger, _cancellation.Token);

_task = StartAsync();
}

public Uri Address => _endpoint.Uri;
Expand Down Expand Up @@ -232,61 +165,55 @@ protected async Task listenForMessagesAsync(Func<Task> listenAsync)
}
}

protected async Task handleMessagesAsync(RepeatedField<ReceivedMessage> messages)
protected async Task handleMessagesAsync(PubsubMessage message)
{
var envelopes = new List<PubsubEnvelope>(messages.Count);
PubsubEnvelope? envelope = null;

foreach (var message in messages)
if (message.Attributes.ContainsKey("batched"))
{
if (message.Message.Attributes.Keys.Contains("batched"))
var batched = EnvelopeSerializer.ReadMany(message.Data.ToByteArray());

if (batched.Any())
{
var batched = EnvelopeSerializer.ReadMany(message.Message.Data.ToByteArray());
await _receiver.ReceivedAsync(this, batched);
}

if (batched.Any())
{
await _receiver.ReceivedAsync(this, batched);
}
return;
}
Comment on lines +172 to +182

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not certain if this batching piece is still relevant with the higher level API


await _acknowledge.PostAsync([message.AckId]);
try
{
envelope = new PubsubEnvelope();

continue;
}
_mapper.MapIncomingToEnvelope(envelope, message);

try
if (envelope.IsPing())
{
var envelope = new PubsubEnvelope();

_mapper.MapIncomingToEnvelope(envelope, message);

if (envelope.IsPing())
try
{
try
{
await _complete.PostAsync([envelope]);
}
catch (Exception ex)
{
_logger.LogError(ex,
"{Uri}: Error while acknowledging Google Cloud Platform Pub/Sub ping message \"{AckId}\".",
_endpoint.Uri, message.AckId);
}

continue;
await _complete.PostAsync([envelope]);
}
catch (Exception ex)
{
_logger.LogError(ex,
"{Uri}: Error while acknowledging Google Cloud Platform Pub/Sub ping message \"{MessageId}\".",
_endpoint.Uri, message.MessageId);
}

envelopes.Add(envelope);
}
catch (Exception ex)
{
_logger.LogError(ex, "{Uri}: Error while mapping Google Cloud Platform Pub/Sub message {AckId}.",
_endpoint.Uri, message.AckId);
return;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "{Uri}: Error while mapping Google Cloud Platform Pub/Sub message {MessageId}.",
_endpoint.Uri, message.MessageId);
}


if (envelopes.Any())
if (envelope != null)
{
await _receiver.ReceivedAsync(this, envelopes.ToArray());
await _complete.PostAsync(envelopes.ToArray());
await _receiver.ReceivedAsync(this, [envelope]);
await _complete.PostAsync([envelope]);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,4 @@ public PubsubTopicListenerConfiguration ConfigureDeadLettering(

return this;
}
}
}
Loading
Loading