Skip to content

Commit

Permalink
fix(push): concurrency issues within PushNotificationChannelManager (#…
Browse files Browse the repository at this point in the history
…228)

* fix(push): concurrency issues within PushNotificationChannelManager

* fix(push): lock on count to ensure atomicity
  • Loading branch information
Fenrikur authored Sep 12, 2024
1 parent b990843 commit 3d45110
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace Eurofurence.App.Server.Services.Abstractions.Communication
{
public interface IPrivateMessageQueueService
{
void EnqueueMessage(QueuedNotificationParameters message);
QueuedNotificationParameters? DequeueMessage();
int GetQueueSize();

public struct QueuedNotificationParameters
{
public string RecipientIdentityId;
public string RecipientRegSysId;
public string ToastTitle;
public string ToastMessage;
public Guid RelatedId;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
using dotAPNS;
using Microsoft.Extensions.Configuration;

namespace Eurofurence.App.Server.Services.Abstractions.PushNotifications
{
public class ApnsConfiguration
{
public bool IsConfigured => !string.IsNullOrWhiteSpace(BundleId) && !string.IsNullOrWhiteSpace(CertContent) && !string.IsNullOrWhiteSpace(KeyId) && !string.IsNullOrWhiteSpace(TeamId);
public string BundleId { get; set; }
public string CertFilePath { get; set; }
public string CertContent { get; set; }
public string KeyId { get; set; }
public string TeamId { get; set; }
public bool UseDevelopmentServer { get; set; }
public string BundleId { get; private set; }
public string CertFilePath { get; private set; }
public string CertContent { get; private set; }
public string KeyId { get; private set; }
public string TeamId { get; private set; }
public bool UseDevelopmentServer { get; private set; }
public ApnsJwtOptions ApnsJwtOptions { get; private set; }

public static ApnsConfiguration FromConfiguration(IConfiguration configuration)
=> new ApnsConfiguration
{
{

var apnsConfiguration = new ApnsConfiguration
{
BundleId = configuration["push:apns:bundleId"],
CertContent = configuration["push:apns:certContent"],
KeyId = configuration["push:apns:keyId"],
TeamId = configuration["push:apns:teamId"],
UseDevelopmentServer = bool.TryParse(configuration["push:apns:useDevelopmentServer"], out bool shouldUseDevelopmentServer) ? shouldUseDevelopmentServer : true,
};
};

if (apnsConfiguration.IsConfigured)
{
apnsConfiguration.ApnsJwtOptions = new ApnsJwtOptions()
{
BundleId = apnsConfiguration.BundleId,
CertContent = apnsConfiguration.CertContent,
KeyId = apnsConfiguration.KeyId,
TeamId = apnsConfiguration.TeamId,
};
}

return apnsConfiguration;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
using FirebaseAdmin;
using Google.Apis.Auth.OAuth2;
using Microsoft.Extensions.Configuration;

namespace Eurofurence.App.Server.Services.Abstractions.PushNotifications
{
public class FirebaseConfiguration
{
public bool IsConfigured => !string.IsNullOrWhiteSpace(GoogleServiceCredentialKeyFile);
public string GoogleServiceCredentialKeyFile { get; set; }
public bool IsConfigured => !string.IsNullOrWhiteSpace(GoogleServiceCredentialKeyFile) && GoogleCredential != null;
public string GoogleServiceCredentialKeyFile { get; private set; }
public GoogleCredential GoogleCredential { get; private set; }

public static FirebaseConfiguration FromConfiguration(IConfiguration configuration)
=> new FirebaseConfiguration
{
GoogleServiceCredentialKeyFile = configuration["push:firebase:googleServiceCredentialKeyFile"]
};
{
var firebaseConfiguration = new FirebaseConfiguration
{
GoogleServiceCredentialKeyFile = configuration["push:firebase:googleServiceCredentialKeyFile"]
};

if (!string.IsNullOrWhiteSpace(firebaseConfiguration.GoogleServiceCredentialKeyFile)) {
firebaseConfiguration.GoogleCredential = GoogleCredential.FromFile(firebaseConfiguration.GoogleServiceCredentialKeyFile);
FirebaseApp.Create(new AppOptions { Credential = firebaseConfiguration.GoogleCredential });
}

return firebaseConfiguration;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using Eurofurence.App.Server.Services.Abstractions.Communication;

namespace Eurofurence.App.Server.Services.Communication
{
public class PrivateMessageQueueService : IPrivateMessageQueueService
{
private readonly Queue<IPrivateMessageQueueService.QueuedNotificationParameters> _notificationQueue = new();

public void EnqueueMessage(IPrivateMessageQueueService.QueuedNotificationParameters message)
{
lock (_notificationQueue)
_notificationQueue.Enqueue(message);
}

public IPrivateMessageQueueService.QueuedNotificationParameters? DequeueMessage()
{
lock (_notificationQueue)
{
if (_notificationQueue.TryDequeue(out var item))
return item;
return default;
}
}

public int GetQueueSize() {
lock (_notificationQueue)
return _notificationQueue.Count;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ public class PrivateMessageService : EntityServiceBase<PrivateMessageRecord>, IP
{
private readonly AppDbContext _appDbContext;
private readonly IPushNotificationChannelManager _pushNotificationChannelManager;

private readonly ConcurrentQueue<QueuedNotificationParameters> _notificationQueue = new();
private readonly IPrivateMessageQueueService _privateMessageQueueService;

public PrivateMessageService(
AppDbContext appDbContext,
IStorageServiceFactory storageServiceFactory,
IPushNotificationChannelManager pushNotificationChannelManager
IPushNotificationChannelManager pushNotificationChannelManager,
IPrivateMessageQueueService privateMessageQueueService
)
: base(appDbContext, storageServiceFactory)
{
_appDbContext = appDbContext;
_pushNotificationChannelManager = pushNotificationChannelManager;
_privateMessageQueueService = privateMessageQueueService;
}

public async Task<List<PrivateMessageRecord>> GetPrivateMessagesForRecipientAsync(
Expand Down Expand Up @@ -106,7 +107,7 @@ public async Task<Guid> SendPrivateMessageAsync(

await InsertOneAsync(entity, cancellationToken);

_notificationQueue.Enqueue(new QueuedNotificationParameters()
_privateMessageQueueService.EnqueueMessage(new IPrivateMessageQueueService.QueuedNotificationParameters()
{
RecipientRegSysId = request.RecipientUid,
ToastTitle = request.ToastTitle,
Expand Down Expand Up @@ -135,7 +136,7 @@ public async Task<Guid> SendPrivateMessageAsync(

await InsertOneAsync(entity, cancellationToken);

_notificationQueue.Enqueue(new QueuedNotificationParameters()
_privateMessageQueueService.EnqueueMessage(new IPrivateMessageQueueService.QueuedNotificationParameters()
{
RecipientIdentityId = request.RecipientUid,
ToastTitle = request.ToastTitle,
Expand Down Expand Up @@ -188,7 +189,7 @@ public async Task<int> FlushPrivateMessageQueueNotifications(

for (int i = 0; i < messageCount; i++)
{
if (_notificationQueue.TryDequeue(out QueuedNotificationParameters parameters))
if (_privateMessageQueueService.DequeueMessage() is { } parameters)
{
if (!string.IsNullOrWhiteSpace(parameters.RecipientRegSysId))
{
Expand Down Expand Up @@ -224,7 +225,7 @@ await _pushNotificationChannelManager.PushPrivateMessageNotificationToIdentityId

public int GetNotificationQueueSize()
{
return _notificationQueue.Count;
return _privateMessageQueueService.GetQueueSize();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private void RegisterServices(ContainerBuilder builder)
builder.RegisterType<EventFeedbackService>().As<IEventFeedbackService>();
builder.RegisterType<EventService>().As<IEventService>();
//builder.RegisterType<FirebaseChannelManager>().As<IPushNotificationChannelManager>();
builder.RegisterType<PushNotificationChannelManager>().As<IPushNotificationChannelManager>().SingleInstance();
builder.RegisterType<PushNotificationChannelManager>().As<IPushNotificationChannelManager>();
builder.RegisterType<FursuitBadgeService>().As<IFursuitBadgeService>();
builder.RegisterType<GanssHtmlSanitizer>().As<IHtmlSanitizer>();
builder.RegisterType<ImageService>().As<IImageService>();
Expand All @@ -121,7 +121,8 @@ private void RegisterServices(ContainerBuilder builder)
builder.RegisterType<LostAndFoundService>().As<ILostAndFoundService>();
builder.RegisterType<LostAndFoundLassieImporter>().As<ILostAndFoundLassieImporter>();
builder.RegisterType<MapService>().As<IMapService>();
builder.RegisterType<PrivateMessageService>().As<IPrivateMessageService>().SingleInstance();
builder.RegisterType<PrivateMessageQueueService>().As<IPrivateMessageQueueService>().SingleInstance();
builder.RegisterType<PrivateMessageService>().As<IPrivateMessageService>();
builder.RegisterType<PushNotificationChannelStatisticsService>().As<IPushNotificationChannelStatisticsService>();
builder.RegisterType<QrCodeService>().As<IQrCodeService>();
builder.RegisterType<StorageServiceFactory>().As<IStorageServiceFactory>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public class PushNotificationChannelManager : IPushNotificationChannelManager
private readonly ApnsConfiguration _apnsConfiguration;
private readonly ExpoConfiguration _expoConfiguration;
private readonly IApnsService _apnsService;
private readonly ApnsJwtOptions _apnsJwtOptions;
private readonly ILogger _logger;

public PushNotificationChannelManager(
Expand All @@ -57,29 +56,8 @@ public PushNotificationChannelManager(

if (_firebaseConfiguration.IsConfigured)
{
_logger.LogInformation("Configuring Firebase Cloud Messaging (FCM)…");
var googleCredential = GoogleCredential.FromFile(_firebaseConfiguration.GoogleServiceCredentialKeyFile);
if (FirebaseApp.DefaultInstance == null)
{
FirebaseApp.Create(new AppOptions { Credential = googleCredential });
}

_firebaseMessaging = FirebaseMessaging.GetMessaging(FirebaseApp.DefaultInstance);
}

if (_apnsConfiguration.IsConfigured)
{
_logger.LogInformation("Configuring Apple Push Notification service (APNs)…");
if (_apnsConfiguration.UseDevelopmentServer)
_logger.LogInformation("Using APNs development servers!");
_apnsJwtOptions = new ApnsJwtOptions()
{
BundleId = apnsConfiguration.BundleId,
CertContent = apnsConfiguration.CertContent,
KeyId = apnsConfiguration.KeyId,
TeamId = apnsConfiguration.TeamId,
};
}
}

public async Task PushAnnouncementNotificationAsync(
Expand Down Expand Up @@ -437,7 +415,7 @@ private async Task<ApnsResult> pushApnsAsync(DeviceIdentityRecord deviceIdentity
return new ApnsResult()
{
DeviceIdentity = deviceIdentity,
ApnsResponse = await _apnsService.SendPush(applePush, _apnsJwtOptions)
ApnsResponse = await _apnsService.SendPush(applePush, _apnsConfiguration.ApnsJwtOptions)
};
}

Expand Down

0 comments on commit 3d45110

Please sign in to comment.