Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ src/PNEventEngine/obj/*
src/PNEventEngineUWP/bin/*
src/PNEventEngineUWP/obj/*

CLAUDE.md
.claude/*
.cursor/*

# GitHub Actions #
##################
.github/.release
Expand Down
19 changes: 12 additions & 7 deletions .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
name: c-sharp
version: "8.0.1"
version: "8.0.2"
schema: 1
scm: github.com/pubnub/c-sharp
changelog:
- date: 2025-11-17
version: v8.0.2
changes:
- type: bug
text: "Fixes issue of subscription loop breaking when a listener callback throws exception."
- date: 2025-11-04
version: v8.0.1
changes:
Expand Down Expand Up @@ -957,7 +962,7 @@ features:
- QUERY-PARAM
supported-platforms:
-
version: Pubnub 'C#' 8.0.1
version: Pubnub 'C#' 8.0.2
platforms:
- Windows 10 and up
- Windows Server 2008 and up
Expand All @@ -968,7 +973,7 @@ supported-platforms:
- .Net Framework 4.6.1+
- .Net Framework 6.0
-
version: PubnubPCL 'C#' 8.0.1
version: PubnubPCL 'C#' 8.0.2
platforms:
- Xamarin.Android
- Xamarin.iOS
Expand All @@ -988,7 +993,7 @@ supported-platforms:
- .Net Core
- .Net 6.0
-
version: PubnubUWP 'C#' 8.0.1
version: PubnubUWP 'C#' 8.0.2
platforms:
- Windows Phone 10
- Universal Windows Apps
Expand All @@ -1012,7 +1017,7 @@ sdks:
distribution-type: source
distribution-repository: GitHub
package-name: Pubnub
location: https://github.com/pubnub/c-sharp/releases/tag/v8.0.1.0
location: https://github.com/pubnub/c-sharp/releases/tag/v8.0.2
requires:
-
name: ".Net"
Expand Down Expand Up @@ -1295,7 +1300,7 @@ sdks:
distribution-type: source
distribution-repository: GitHub
package-name: PubNubPCL
location: https://github.com/pubnub/c-sharp/releases/tag/v8.0.1.0
location: https://github.com/pubnub/c-sharp/releases/tag/v8.0.2
requires:
-
name: ".Net Core"
Expand Down Expand Up @@ -1654,7 +1659,7 @@ sdks:
distribution-type: source
distribution-repository: GitHub
package-name: PubnubUWP
location: https://github.com/pubnub/c-sharp/releases/tag/v8.0.1.0
location: https://github.com/pubnub/c-sharp/releases/tag/v8.0.2
requires:
-
name: "Universal Windows Platform Development"
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
v8.0.2 - November 17 2025
-----------------------------
- Fixed: fixes issue of subscription loop breaking when a listener callback throws exception.

v8.0.1 - November 04 2025
-----------------------------
- Modified: removed `limit` value clamp for HereNow API. Server will perform validation for limit parameter value.
Expand Down
18 changes: 16 additions & 2 deletions src/Api/PubnubApi/EndPoint/PubSub/SubscribeEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,19 +212,33 @@
}
}

private void MessageEmitter<T>(Pubnub pubnubInstance, PNMessageResult<T> messageResult)

Check warning on line 215 in src/Api/PubnubApi/EndPoint/PubSub/SubscribeEndpoint.cs

View workflow job for this annotation

GitHub Actions / Integration and Unit tests

Type parameter 'T' has the same name as the type parameter from outer type 'SubscribeEndpoint<T>'

Check warning on line 215 in src/Api/PubnubApi/EndPoint/PubSub/SubscribeEndpoint.cs

View workflow job for this annotation

GitHub Actions / Integration and Unit tests

Type parameter 'T' has the same name as the type parameter from outer type 'SubscribeEndpoint<T>'

Check warning on line 215 in src/Api/PubnubApi/EndPoint/PubSub/SubscribeEndpoint.cs

View workflow job for this annotation

GitHub Actions / Acceptance tests

Type parameter 'T' has the same name as the type parameter from outer type 'SubscribeEndpoint<T>'

Check warning on line 215 in src/Api/PubnubApi/EndPoint/PubSub/SubscribeEndpoint.cs

View workflow job for this annotation

GitHub Actions / Publish to NuGet

Type parameter 'T' has the same name as the type parameter from outer type 'SubscribeEndpoint<T>'
{
foreach (var listener in SubscribeListenerList)
{
listener?.Message(pubnubInstance, messageResult);
try
{
listener?.Message(pubnubInstance, messageResult);
}
catch (Exception ex)
{
config.Logger?.Error($"error during event handler function, {ex.Message}");
}
}
}

private void StatusEmitter(Pubnub pubnubInstance, PNStatus status)
{
foreach (var listener in SubscribeListenerList)
{
listener?.Status(pubnubInstance, status);
try
{
listener?.Status(pubnubInstance, status);
}
catch (Exception ex)
{
config.Logger?.Error($"error during event handler function, {ex.Message}");
}
}
}

Expand Down
90 changes: 54 additions & 36 deletions src/Api/PubnubApi/EventEngine/Common/EventEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@ public EventEmitter(PNConfiguration configuration, List<SubscribeCallback> liste
channelListenersMap = new Dictionary<string, List<SubscribeCallback>>();
}

/// <summary>
/// Safely invokes a listener callback with exception handling to prevent listener exceptions from affecting SDK operations.
/// </summary>
/// <param name="listenerAction">The listener callback action to invoke.</param>
/// <param name="eventType">The type of event being processed (for logging purposes).</param>
/// <remarks>
/// This method ensures that exceptions thrown by user-provided listener callbacks do not crash the SDK
/// or prevent other listeners from receiving events. All exceptions are logged at Warning level.
/// </remarks>
private void SafeInvokeListener(Action listenerAction, string eventType)
{
if (listenerAction == null)
{
return;
}

try
{
listenerAction.Invoke();
}
catch (Exception ex)
{
configuration?.Logger?.Warn($"Exception in listener callback execution for {eventType}: {ex.Message}");
}
}

private TimetokenMetadata GetTimetokenMetadata(object t)
{
Dictionary<string, object> ttOriginMetaData = jsonLibrary.ConvertToDictionaryObject(t);
Expand Down Expand Up @@ -214,22 +240,22 @@ public void EmitEvent<T>(object e)
};
foreach (var listener in listeners)
{
listener?.Signal(instance, signalMessage);
SafeInvokeListener(() => listener?.Signal(instance, signalMessage), "Signal");
}

if (!string.IsNullOrEmpty(signalMessage.Channel) && channelListenersMap.ContainsKey(signalMessage.Channel))
{
foreach (var l in channelListenersMap[signalMessage.Channel])
{
l?.Signal(instance, signalMessage);
SafeInvokeListener(() => l?.Signal(instance, signalMessage), "Signal");
}
}

if (!string.IsNullOrEmpty(signalMessage.Subscription) && channelGroupListenersMap.ContainsKey(signalMessage.Subscription))
{
foreach (var l in channelGroupListenersMap[signalMessage.Subscription])
{
l?.Signal(instance, signalMessage);
SafeInvokeListener(() => l?.Signal(instance, signalMessage), "Signal");
}
}
}
Expand All @@ -244,22 +270,22 @@ public void EmitEvent<T>(object e)
{
foreach (var listener in listeners)
{
listener?.ObjectEvent(instance, objectApiEvent);
SafeInvokeListener(() => listener?.ObjectEvent(instance, objectApiEvent), "ObjectEvent");
}

if (!string.IsNullOrEmpty(objectApiEvent.Channel) && channelListenersMap.ContainsKey(objectApiEvent.Channel))
{
foreach (var l in channelListenersMap[objectApiEvent.Channel])
{
l?.ObjectEvent(instance, objectApiEvent);
SafeInvokeListener(() => l?.ObjectEvent(instance, objectApiEvent), "ObjectEvent");
}
}

if (!string.IsNullOrEmpty(objectApiEvent.Subscription) && channelGroupListenersMap.ContainsKey(objectApiEvent.Subscription))
{
foreach (var l in channelGroupListenersMap[objectApiEvent.Subscription])
{
l?.ObjectEvent(instance, objectApiEvent);
SafeInvokeListener(() => l?.ObjectEvent(instance, objectApiEvent), "ObjectEvent");
}
}
}
Expand All @@ -274,22 +300,22 @@ public void EmitEvent<T>(object e)
{
foreach (var listener in listeners)
{
listener?.MessageAction(instance, messageActionEvent);
SafeInvokeListener(() => listener?.MessageAction(instance, messageActionEvent), "MessageAction");
}

if (!string.IsNullOrEmpty(messageActionEvent.Channel) && channelListenersMap.ContainsKey(messageActionEvent.Channel))
{
foreach (var l in channelListenersMap[messageActionEvent.Channel])
{
l?.MessageAction(instance, messageActionEvent);
SafeInvokeListener(() => l?.MessageAction(instance, messageActionEvent), "MessageAction");
}
}

if (!string.IsNullOrEmpty(messageActionEvent.Subscription) && channelGroupListenersMap.ContainsKey(messageActionEvent.Subscription))
{
foreach (var l in channelGroupListenersMap[messageActionEvent.Subscription])
{
l?.MessageAction(instance, messageActionEvent);
SafeInvokeListener(() => l?.MessageAction(instance, messageActionEvent), "MessageAction");
}
}
}
Expand Down Expand Up @@ -340,22 +366,22 @@ public void EmitEvent<T>(object e)

foreach (var listener in listeners)
{
listener?.File(instance, fileMessage);
SafeInvokeListener(() => listener?.File(instance, fileMessage), "File");
}

if (!string.IsNullOrEmpty(fileMessage.Channel) && channelListenersMap.ContainsKey(fileMessage.Channel))
{
foreach (var l in channelListenersMap[fileMessage.Channel])
{
l?.File(instance, fileMessage);
SafeInvokeListener(() => l?.File(instance, fileMessage), "File");
}
}

if (!string.IsNullOrEmpty(fileMessage.Subscription) && channelGroupListenersMap.ContainsKey(fileMessage.Subscription))
{
foreach (var l in channelGroupListenersMap[fileMessage.Subscription])
{
l?.File(instance, fileMessage);
SafeInvokeListener(() => l?.File(instance, fileMessage), "File");
}
}
}
Expand All @@ -372,22 +398,22 @@ public void EmitEvent<T>(object e)
{
foreach (var listener in listeners)
{
listener?.Presence(instance, presenceEvent);
SafeInvokeListener(() => listener?.Presence(instance, presenceEvent), "Presence");
}

if (!string.IsNullOrEmpty(presenceEvent.Channel) && channelListenersMap.ContainsKey(presenceEvent.Channel))
{
foreach (var l in channelListenersMap[presenceEvent.Channel])
{
l?.Presence(instance, presenceEvent);
SafeInvokeListener(() => l?.Presence(instance, presenceEvent), "Presence");
}
}

if (!string.IsNullOrEmpty(presenceEvent.Subscription) && channelGroupListenersMap.ContainsKey(presenceEvent.Subscription))
{
foreach (var l in channelGroupListenersMap[presenceEvent.Subscription])
{
l?.Presence(instance, presenceEvent);
SafeInvokeListener(() => l?.Presence(instance, presenceEvent), "Presence");
}
}
}
Expand All @@ -397,37 +423,29 @@ public void EmitEvent<T>(object e)
jsonFields.Add("customMessageType", eventData.CustomMessageType);
ResponseBuilder responseBuilder =new ResponseBuilder(configuration, jsonLibrary);
PNMessageResult<T> userMessage = responseBuilder.GetEventResultObject<PNMessageResult<T>>(jsonFields);
try
if (userMessage != null)
{
if (userMessage != null)
foreach (var listener in listeners)
{
foreach (var listener in listeners)
{
listener?.Message(instance, userMessage);
}
SafeInvokeListener(() => listener?.Message(instance, userMessage), "Message");
}

if (!string.IsNullOrEmpty(userMessage.Channel) && channelListenersMap.ContainsKey(userMessage.Channel))
if (!string.IsNullOrEmpty(userMessage.Channel) && channelListenersMap.ContainsKey(userMessage.Channel))
{
foreach (var l in channelListenersMap[userMessage.Channel])
{
foreach (var l in channelListenersMap[userMessage.Channel])
{
l?.Message(instance, userMessage);
}
SafeInvokeListener(() => l?.Message(instance, userMessage), "Message");
}
}

if (!string.IsNullOrEmpty(userMessage.Subscription) && channelGroupListenersMap.ContainsKey(userMessage.Subscription))
if (!string.IsNullOrEmpty(userMessage.Subscription) && channelGroupListenersMap.ContainsKey(userMessage.Subscription))
{
foreach (var l in channelGroupListenersMap[userMessage.Subscription])
{
foreach (var l in channelGroupListenersMap[userMessage.Subscription])
{
l?.Message(instance, userMessage);
}
SafeInvokeListener(() => l?.Message(instance, userMessage), "Message");
}
}
}
catch (Exception ex)
{
configuration.Logger?.Error(
$"Listener call back execution encounters error: {ex.Message}\n{ex?.StackTrace}");
}
}

break;
Expand Down
4 changes: 2 additions & 2 deletions src/Api/PubnubApi/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
[assembly: AssemblyProduct("Pubnub C# SDK")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("8.0.1.0")]
[assembly: AssemblyFileVersion("8.0.1.0")]
[assembly: AssemblyVersion("8.0.2")]
[assembly: AssemblyFileVersion("8.0.2")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
Expand Down
4 changes: 2 additions & 2 deletions src/Api/PubnubApi/PubnubApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@

<PropertyGroup>
<PackageId>Pubnub</PackageId>
<PackageVersion>8.0.1.0</PackageVersion>
<PackageVersion>8.0.2</PackageVersion>
<Title>PubNub C# .NET - Web Data Push API</Title>
<Authors>Pandu Masabathula</Authors>
<Owners>PubNub</Owners>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIconUrl>http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png</PackageIconUrl>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<RepositoryUrl>https://github.com/pubnub/c-sharp/</RepositoryUrl>
<PackageReleaseNotes>Removed `limit` value clamp for HereNow API. Server will perform validation for limit parameter value.</PackageReleaseNotes>
<PackageReleaseNotes>Fixes issue of subscription loop breaking when a listener callback throws exception.</PackageReleaseNotes>
<PackageTags>Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing</PackageTags>
<!--<Summary>PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously</Summary>-->
<Description>PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously</Description>
Expand Down
Loading
Loading