diff --git a/.pubnub.yml b/.pubnub.yml index 09e7972dd..648fdd3de 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,8 +1,13 @@ name: c-sharp -version: "7.3.6" +version: "7.3.7" schema: 1 scm: github.com/pubnub/c-sharp changelog: + - date: 2025-04-07 + version: v7.3.7 + changes: + - type: improvement + text: "Enhance request handling with detailed response interface." - date: 2025-04-03 version: v7.3.6 changes: @@ -877,7 +882,7 @@ features: - QUERY-PARAM supported-platforms: - - version: Pubnub 'C#' 7.3.6 + version: Pubnub 'C#' 7.3.7 platforms: - Windows 10 and up - Windows Server 2008 and up @@ -888,7 +893,7 @@ supported-platforms: - .Net Framework 4.6.1+ - .Net Framework 6.0 - - version: PubnubPCL 'C#' 7.3.6 + version: PubnubPCL 'C#' 7.3.7 platforms: - Xamarin.Android - Xamarin.iOS @@ -908,7 +913,7 @@ supported-platforms: - .Net Core - .Net 6.0 - - version: PubnubUWP 'C#' 7.3.6 + version: PubnubUWP 'C#' 7.3.7 platforms: - Windows Phone 10 - Universal Windows Apps @@ -932,7 +937,7 @@ sdks: distribution-type: source distribution-repository: GitHub package-name: Pubnub - location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.6.0 + location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.7.0 requires: - name: ".Net" @@ -1215,7 +1220,7 @@ sdks: distribution-type: source distribution-repository: GitHub package-name: PubNubPCL - location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.6.0 + location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.7.0 requires: - name: ".Net Core" @@ -1574,7 +1579,7 @@ sdks: distribution-type: source distribution-repository: GitHub package-name: PubnubUWP - location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.6.0 + location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.7.0 requires: - name: "Universal Windows Platform Development" diff --git a/CHANGELOG b/CHANGELOG index b4593e920..d4d74d43a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +v7.3.7 - April 07 2025 +----------------------------- +- Modified: enhance request handling with detailed response interface. + v7.3.6 - April 03 2025 ----------------------------- - Fixed: reafctor: Removed excess logging from transport module. Removed redundant logging from transport layer and added thread id information for tracking http request. diff --git a/src/Api/PubnubApi/EndPoint/Files/SendFileOperation.cs b/src/Api/PubnubApi/EndPoint/Files/SendFileOperation.cs index 38700d4bd..5832cae70 100644 --- a/src/Api/PubnubApi/EndPoint/Files/SendFileOperation.cs +++ b/src/Api/PubnubApi/EndPoint/Files/SendFileOperation.cs @@ -426,7 +426,7 @@ private async Task> ProcessFileUpload() publishFailed = true; returnValue.Status = publishFileMessageStatus; logger?.Debug($"PublishFileMessage Failed. retry count={currentFileRetryCount}"); - await Task.Delay(1000); + await Task.Delay(1000).ConfigureAwait(false); } } while (publishFailed && currentFileRetryCount <= publishFileRetryLimit && !(publishFileMessageStatus?.StatusCode != 400 || publishFileMessageStatus.StatusCode != 403)); diff --git a/src/Api/PubnubApi/EndPoint/PubSub/PublishOperation.cs b/src/Api/PubnubApi/EndPoint/PubSub/PublishOperation.cs index e03557f67..e343197f8 100644 --- a/src/Api/PubnubApi/EndPoint/PubSub/PublishOperation.cs +++ b/src/Api/PubnubApi/EndPoint/PubSub/PublishOperation.cs @@ -204,8 +204,8 @@ internal void Publish(string channel, object message, bool storeInHistory, int t PubnubInstance.transportMiddleware.Send(transportRequest).ContinueWith(t => { - var transportResponse = t.Result; - if (transportResponse.Error == null) + var transportResponse = t?.Result; + if (transportResponse is { Error: null }) { var responseString = Encoding.UTF8.GetString(transportResponse.Content); if (!string.IsNullOrEmpty(responseString)) @@ -304,7 +304,7 @@ internal async Task> Publish(string channel, object me var transportResponse = await PubnubInstance.transportMiddleware.Send(transportRequest).ConfigureAwait(false); logger?.Debug($"Publish() got transport response: {transportResponse?.StatusCode}\n error: {transportResponse?.Error?.Message}\nstackTrace: {transportResponse?.Error?.StackTrace}"); - if (transportResponse.Error == null) + if (transportResponse is { Error: null, Content: not null }) { string responseString = Encoding.UTF8.GetString(transportResponse.Content); PNStatus errorStatus = GetStatusIfError(requestState, responseString); diff --git a/src/Api/PubnubApi/EndPoint/PubSub/SubscribeManager2.cs b/src/Api/PubnubApi/EndPoint/PubSub/SubscribeManager2.cs index 98babde1f..4d0366028 100644 --- a/src/Api/PubnubApi/EndPoint/PubSub/SubscribeManager2.cs +++ b/src/Api/PubnubApi/EndPoint/PubSub/SubscribeManager2.cs @@ -33,7 +33,6 @@ public async Task> HandshakeRequest(PNOperati if (config.MaintainPresenceState) presenceState = BuildJsonUserState(channels, channelGroups, true); var requestParameter = CreateSubscribeRequestParameter(channels: channels, channelGroups: channelGroups, timetoken: timetoken.GetValueOrDefault(), region: region.GetValueOrDefault(), stateJsonValue: presenceState, initialSubscribeUrlParams: initialSubscribeUrlParams, externalQueryParam: externalQueryParam); var transportRequest = pubnubInstance.transportMiddleware.PreapareTransportRequest(requestParameter: requestParameter, operationType: PNOperationType.PNSubscribeOperation); - cancellationTokenSource = transportRequest.CancellationTokenSource; RequestState pubnubRequestState = new RequestState { Channels = channels, @@ -43,8 +42,7 @@ public async Task> HandshakeRequest(PNOperati Region = region.GetValueOrDefault(), TimeQueued = DateTime.Now }; - var transportResponse = await pubnubInstance.transportMiddleware.Send(transportRequest: transportRequest); - + var transportResponse = await pubnubInstance.transportMiddleware.Send(transportRequest: transportRequest).ConfigureAwait(false); if (transportResponse.StatusCode == Constants.HttpRequestSuccessStatusCode && transportResponse.Error == null && transportResponse.Content != null) { var responseJson = Encoding.UTF8.GetString(transportResponse.Content); logger?.Debug($"Handshake Effect received json: {responseJson}"); @@ -52,7 +50,6 @@ public async Task> HandshakeRequest(PNOperati HandshakeResponse handshakeResponse = jsonLibrary.DeserializeToObject(responseJson); return new Tuple(handshakeResponse, status); } - PNStatus errStatus; if (transportResponse.Error != null) { PNStatusCategory category = PNStatusCategoryHelper.GetPNStatusCategory(transportResponse.Error); @@ -89,7 +86,8 @@ internal async Task, PNStatus>> ReceiveRequest 0) + cancellationTokenSource = transportRequest.CancellationTokenSource; RequestState> pubnubRequestState = new RequestState> { Channels = channels, @@ -100,7 +98,7 @@ internal async Task, PNStatus>> ReceiveRequest, PNStatus>> ReceiveRequest receiveResponse = jsonLibrary.DeserializeToObject>(responseJson); return new Tuple, PNStatus>(receiveResponse, status); } + if (transportResponse.IsCancelled) return resp; PNStatus errStatus; if (transportResponse.Error != null) { PNStatusCategory category = PNStatusCategoryHelper.GetPNStatusCategory(transportResponse.Error); @@ -132,13 +131,14 @@ internal void ReceiveRequestCancellation() if (cancellationTokenSource != null) { cancellationTokenSource.Cancel(); cancellationTokenSource.Dispose(); + cancellationTokenSource = null; } else { - logger?.Trace($"SubscribeManager RequestCancellation. No request to cancel."); + logger?.Trace($"ReceiveRequestCancellation: No active request found to cancel."); } - logger?.Trace($"SubscribeManager ReceiveRequestCancellation. Done."); + logger?.Trace($"ReceiveRequestCancellation: Active request found and cancelled."); } catch (Exception ex) { - logger?.Trace($"SubscribeManager ReceiveRequestCancellation Exception: {ex}"); + logger?.Trace($"ReceiveRequestCancellation Exception: {ex}"); } } @@ -148,6 +148,7 @@ internal void ReceiveReconnectRequestCancellation() if (cancellationTokenSource != null) { cancellationTokenSource.Cancel(); cancellationTokenSource.Dispose(); + cancellationTokenSource = null; } else { logger?.Trace($"SubscribeManager ReceiveReconnectRequestCancellation. No request to cancel."); } diff --git a/src/Api/PubnubApi/EventEngine/Common/Delay.cs b/src/Api/PubnubApi/EventEngine/Common/Delay.cs index 405ce871c..835af1840 100644 --- a/src/Api/PubnubApi/EventEngine/Common/Delay.cs +++ b/src/Api/PubnubApi/EventEngine/Common/Delay.cs @@ -6,8 +6,8 @@ namespace PubnubApi.EventEngine.Common public class Delay { public bool Cancelled { get; private set; } = false; - private readonly TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); - private readonly object monitor = new object(); + private readonly TaskCompletionSource taskCompletionSource = new (); + private readonly CancellationTokenSource cancellationTokenSource = new (); private readonly int milliseconds; public Delay(int milliseconds) @@ -17,44 +17,37 @@ public Delay(int milliseconds) public Task Start() { - #if NETFX_CORE || WINDOWS_UWP || UAP || NETSTANDARD10 || NETSTANDARD11 || NETSTANDARD12 - Task taskAwaiter = Task.Factory.StartNew(AwaiterLoop); - taskAwaiter.Wait(); - #else - Thread awaiterThread = new Thread(AwaiterLoop); - awaiterThread.Start(); - #endif + AwaiterLoop(); return taskCompletionSource.Task; } public void Cancel() { - lock (monitor) - { - Cancelled = true; - Monitor.Pulse(monitor); - } + Cancelled = true; + cancellationTokenSource.Cancel(); } - private void AwaiterLoop() + private async void AwaiterLoop() { - while(true) + if (Cancelled) + { + taskCompletionSource.TrySetCanceled(); + return; + } + try + { + await Task.Delay(milliseconds, cancellationTokenSource.Token).ConfigureAwait(false); + } + catch (TaskCanceledException e) + { + taskCompletionSource.TrySetCanceled(); + return; + } + if (Cancelled) { - lock (monitor) - { - if (Cancelled) - { - taskCompletionSource.TrySetCanceled(); - break; - } - Monitor.Wait(monitor, milliseconds); - if (Cancelled) - { - taskCompletionSource.TrySetCanceled(); - break; - } - taskCompletionSource.TrySetResult(null); - } + taskCompletionSource.TrySetCanceled(); + return; } + taskCompletionSource.TrySetResult(null); } } } \ No newline at end of file diff --git a/src/Api/PubnubApi/EventEngine/Core/EffectDispatcher.cs b/src/Api/PubnubApi/EventEngine/Core/EffectDispatcher.cs index eb26b9d73..478e3104b 100644 --- a/src/Api/PubnubApi/EventEngine/Core/EffectDispatcher.cs +++ b/src/Api/PubnubApi/EventEngine/Core/EffectDispatcher.cs @@ -28,7 +28,7 @@ public async Task Dispatch(IEffectInvocation invocation) { if (handler.IsBackground(invocation)) FireAndForget(handler, invocation); else - await handler.Run(invocation); + await handler.Run(invocation).ConfigureAwait(false); } } diff --git a/src/Api/PubnubApi/EventEngine/Core/Engine.cs b/src/Api/PubnubApi/EventEngine/Core/Engine.cs index 655b337da..8de04e224 100644 --- a/src/Api/PubnubApi/EventEngine/Core/Engine.cs +++ b/src/Api/PubnubApi/EventEngine/Core/Engine.cs @@ -66,7 +66,7 @@ private async Task Transition(IEvent e) return; } - await ExecuteStateChange(currentState, stateInvocationPair.State, stateInvocationPair.Invocations); + await ExecuteStateChange(currentState, stateInvocationPair.State, stateInvocationPair.Invocations).ConfigureAwait(false); this.currentState = stateInvocationPair.State; } @@ -78,16 +78,16 @@ private async Task ExecuteStateChange(State sourceState, State targetState, IEnu logger?.Debug($"Exiting state {sourceState}"); foreach (var effectInvocation in sourceState.OnExit ?? emptyInvocationList) { logger?.Debug($"Dispatching effect: {effectInvocation}"); - await dispatcher.Dispatch(effectInvocation); + await dispatcher.Dispatch(effectInvocation).ConfigureAwait(false); } foreach (var effectInvocation in invocations ?? emptyInvocationList) { logger?.Debug($"Dispatching effect: {effectInvocation}"); - await dispatcher.Dispatch(effectInvocation); + await dispatcher.Dispatch(effectInvocation).ConfigureAwait(false); } logger?.Debug($"Entering state {targetState}"); foreach (var effectInvocation in targetState.OnEntry ?? emptyInvocationList) { logger?.Debug($"Dispatching effect: {effectInvocation}"); - await dispatcher.Dispatch(effectInvocation); + await dispatcher.Dispatch(effectInvocation).ConfigureAwait(false); } } } diff --git a/src/Api/PubnubApi/EventEngine/Presence/Effects/DelayedHeartbeatEffectHandler.cs b/src/Api/PubnubApi/EventEngine/Presence/Effects/DelayedHeartbeatEffectHandler.cs index e94735592..e22f17f1b 100644 --- a/src/Api/PubnubApi/EventEngine/Presence/Effects/DelayedHeartbeatEffectHandler.cs +++ b/src/Api/PubnubApi/EventEngine/Presence/Effects/DelayedHeartbeatEffectHandler.cs @@ -34,9 +34,9 @@ public override async Task Run(DelayedHeartbeatInvocation invocation) return; } retryDelay = new Delay(retryConfiguration.RetryPolicy.GetDelay(invocation.RetryCount, invocation.Reason, null)); - await retryDelay.Start(); + await retryDelay.Start().ConfigureAwait(false); if (!retryDelay.Cancelled) - await MakeHeartbeatRequest(invocation); + await MakeHeartbeatRequest(invocation).ConfigureAwait(false); } private void EnqueueHeartbeatGiveUpEvent() @@ -49,7 +49,7 @@ private async Task MakeHeartbeatRequest(DelayedHeartbeatInvocation invocation) var resp = await heartbeatOperation.HeartbeatRequest( invocation.Input.Channels.ToArray(), invocation.Input.ChannelGroups.ToArray() - ); + ).ConfigureAwait(false); switch (resp) { case { } when resp.Error: eventQueue.Enqueue(new Events.HeartbeatFailureEvent() { retryCount = invocation.RetryCount + 1, Status = resp }); diff --git a/src/Api/PubnubApi/EventEngine/Presence/Effects/HeartbeatEffectHandler.cs b/src/Api/PubnubApi/EventEngine/Presence/Effects/HeartbeatEffectHandler.cs index bca3e9876..006cc9dd9 100644 --- a/src/Api/PubnubApi/EventEngine/Presence/Effects/HeartbeatEffectHandler.cs +++ b/src/Api/PubnubApi/EventEngine/Presence/Effects/HeartbeatEffectHandler.cs @@ -25,7 +25,7 @@ public override async Task Run(HeartbeatInvocation invocation) var resp = await heartbeatOperation.HeartbeatRequest( invocation.Input.Channels.ToArray(), invocation.Input.ChannelGroups.ToArray() - ); + ).ConfigureAwait(false); switch (resp) { case { } when resp.Error: eventQueue.Enqueue(new Events.HeartbeatFailureEvent() { retryCount = 0, Status = resp }); diff --git a/src/Api/PubnubApi/EventEngine/Presence/Effects/LeaveEffectHandler.cs b/src/Api/PubnubApi/EventEngine/Presence/Effects/LeaveEffectHandler.cs index a6c0634ed..5f60df0fc 100644 --- a/src/Api/PubnubApi/EventEngine/Presence/Effects/LeaveEffectHandler.cs +++ b/src/Api/PubnubApi/EventEngine/Presence/Effects/LeaveEffectHandler.cs @@ -25,7 +25,7 @@ public override async Task Run(LeaveInvocation invocation) await leaveOperation.LeaveRequest( invocation.Input.Channels?.ToArray(), invocation.Input.ChannelGroups?.ToArray() - ); + ).ConfigureAwait(false); } catch (Exception) { } } diff --git a/src/Api/PubnubApi/EventEngine/Presence/Effects/WaitEffectHandler.cs b/src/Api/PubnubApi/EventEngine/Presence/Effects/WaitEffectHandler.cs index 3d50ad5d2..a5b96557e 100644 --- a/src/Api/PubnubApi/EventEngine/Presence/Effects/WaitEffectHandler.cs +++ b/src/Api/PubnubApi/EventEngine/Presence/Effects/WaitEffectHandler.cs @@ -32,7 +32,7 @@ public override bool IsBackground(WaitInvocation invocation) public override async Task Run(WaitInvocation invocation) { retryDelay = new Delay((int)(pnConfiguration.PresenceInterval * 1000)); - await retryDelay.Start(); + await retryDelay.Start().ConfigureAwait(false); if (!retryDelay.Cancelled) eventQueue.Enqueue(new Events.TimesUpEvent()); } diff --git a/src/Api/PubnubApi/EventEngine/Subscribe/Effects/HandshakeEffectHandler.cs b/src/Api/PubnubApi/EventEngine/Subscribe/Effects/HandshakeEffectHandler.cs index bf3aa197a..3158c56e2 100644 --- a/src/Api/PubnubApi/EventEngine/Subscribe/Effects/HandshakeEffectHandler.cs +++ b/src/Api/PubnubApi/EventEngine/Subscribe/Effects/HandshakeEffectHandler.cs @@ -27,7 +27,7 @@ internal HandshakeEffectHandler(SubscribeManager2 manager, EventQueue eventQueue public override async Task Run(HandshakeInvocation invocation) { - var response = await MakeHandshakeRequest(invocation); + var response = await MakeHandshakeRequest(invocation).ConfigureAwait(false); SubscriptionCursor cursor = null; if (response.Item1 != null) { @@ -89,7 +89,7 @@ public override bool IsBackground(HandshakeInvocation invocation) null, invocation.InitialSubscribeQueryParams, invocation.ExternalQueryParams - ); + ).ConfigureAwait(false); } public override async Task Cancel() @@ -133,9 +133,9 @@ public override async Task Run(HandshakeReconnectInvocation invocation) else { retryDelay = new Delay(retryConfiguration.RetryPolicy.GetDelay(invocation.AttemptedRetries, invocation.Reason, null)); - await retryDelay.Start(); + await retryDelay.Start().ConfigureAwait(false); if (!retryDelay.Cancelled) - await handshakeEffectHandler.Run(invocation as HandshakeInvocation); + await handshakeEffectHandler.Run(invocation as HandshakeInvocation).ConfigureAwait(false); } } catch (Exception ex) @@ -152,7 +152,7 @@ public override async Task Cancel() { retryDelay.Cancel(); } - await handshakeEffectHandler.Cancel(); + await handshakeEffectHandler.Cancel().ConfigureAwait(false); } } } diff --git a/src/Api/PubnubApi/EventEngine/Subscribe/Effects/ReceivingEffectHandler.cs b/src/Api/PubnubApi/EventEngine/Subscribe/Effects/ReceivingEffectHandler.cs index cec9eb9ae..4ffb6a8df 100644 --- a/src/Api/PubnubApi/EventEngine/Subscribe/Effects/ReceivingEffectHandler.cs +++ b/src/Api/PubnubApi/EventEngine/Subscribe/Effects/ReceivingEffectHandler.cs @@ -24,7 +24,7 @@ internal ReceivingEffectHandler(SubscribeManager2 manager, EventQueue eventQueue public override async Task Run(ReceiveMessagesInvocation invocation) { - var response = await MakeReceiveMessagesRequest(invocation); + var response = await MakeReceiveMessagesRequest(invocation).ConfigureAwait(false); SubscriptionCursor cursor = null; if (response.Item1 is not null) { cursor = new SubscriptionCursor() { @@ -68,7 +68,7 @@ private async Task, PNStatus>> MakeReceiveMessag invocation.Cursor.Region.Value, invocation.InitialSubscribeQueryParams, invocation.ExternalQueryParams - ); + ).ConfigureAwait(false); } public override async Task Cancel() @@ -108,8 +108,8 @@ public override async Task Run(ReceiveReconnectInvocation invocation) { retryDelay = new Delay(retryConfiguration.RetryPolicy.GetDelay(invocation.AttemptedRetries, invocation.Reason, null)); // Run in the background - await retryDelay.Start(); - await receivingEffectHandler.Run(invocation); + await retryDelay.Start().ConfigureAwait(false); + await receivingEffectHandler.Run(invocation).ConfigureAwait(false); } } @@ -124,7 +124,7 @@ public override async Task Cancel() { retryDelay.Cancel(); } - await receivingEffectHandler.Cancel(); + await receivingEffectHandler.Cancel().ConfigureAwait(false); } } } diff --git a/src/Api/PubnubApi/PNConfiguration.cs b/src/Api/PubnubApi/PNConfiguration.cs index 63d9f8453..f9b494ae9 100644 --- a/src/Api/PubnubApi/PNConfiguration.cs +++ b/src/Api/PubnubApi/PNConfiguration.cs @@ -255,7 +255,7 @@ private void ConstructorInit(UserId currentUserId) { Origin = "ps.pndsn.com"; presenceHeartbeatTimeout = 300; - NonSubscribeRequestTimeout = 10; + NonSubscribeRequestTimeout = 15; SubscribeTimeout = 310; LogVerbosity = PNLogVerbosity.NONE; CipherKey = ""; diff --git a/src/Api/PubnubApi/Properties/AssemblyInfo.cs b/src/Api/PubnubApi/Properties/AssemblyInfo.cs index 8bd092edb..81fd3763a 100644 --- a/src/Api/PubnubApi/Properties/AssemblyInfo.cs +++ b/src/Api/PubnubApi/Properties/AssemblyInfo.cs @@ -11,8 +11,8 @@ [assembly: AssemblyProduct("Pubnub C# SDK")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("7.3.6.0")] -[assembly: AssemblyFileVersion("7.3.6.0")] +[assembly: AssemblyVersion("7.3.7.0")] +[assembly: AssemblyFileVersion("7.3.7.0")] // 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. diff --git a/src/Api/PubnubApi/PubnubApi.csproj b/src/Api/PubnubApi/PubnubApi.csproj index 74ed779e4..48ad9aacd 100644 --- a/src/Api/PubnubApi/PubnubApi.csproj +++ b/src/Api/PubnubApi/PubnubApi.csproj @@ -14,7 +14,7 @@ Pubnub - 7.3.6.0 + 7.3.7.0 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -22,7 +22,7 @@ http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png true https://github.com/pubnub/c-sharp/ - Reafctor: Removed excess logging from transport module. Removed redundant logging from transport layer and added thread id information for tracking http request. + Enhance request handling with detailed response interface. Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing 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 diff --git a/src/Api/PubnubApi/Transport/HttpClientService.cs b/src/Api/PubnubApi/Transport/HttpClientService.cs index 3d84378cc..b771185d5 100644 --- a/src/Api/PubnubApi/Transport/HttpClientService.cs +++ b/src/Api/PubnubApi/Transport/HttpClientService.cs @@ -33,6 +33,7 @@ public HttpClientService(IWebProxy proxy, PNConfiguration configuration) public async Task GetRequest(TransportRequest transportRequest) { TransportResponse transportResponse; + CancellationTokenSource ctsWithTimeout = null; try { HttpRequestMessage requestMessage = @@ -44,10 +45,20 @@ public async Task GetRequest(TransportRequest transportReques requestMessage.Headers.Add(kvp.Key, kvp.Value); } } - logger?.Debug($"HttpClient Service: Task Id: {Task.CurrentId} Sending http request {transportRequest.RequestType} to {transportRequest.RequestUrl}" + - (requestMessage.Headers.Any() ? $"\n Header {string.Join(", ", requestMessage.Headers.Select(kv => $"{kv.Key}: {kv.Value}"))}" : "")); + logger?.Debug( + $"HttpClient Service: Sending http request {transportRequest.RequestType} to {transportRequest.RequestUrl}" + + (requestMessage.Headers.Any() + ? $"\n Header {string.Join(", ", requestMessage.Headers.Select(kv => $"{kv.Key}: {kv.Value}"))}" + : "")); + if (transportRequest.Timeout.HasValue) + { + ctsWithTimeout = + CancellationTokenSource.CreateLinkedTokenSource(transportRequest.CancellationTokenSource.Token); + ctsWithTimeout.CancelAfter(transportRequest.Timeout.Value); + } + var httpResult = await httpClient.SendAsync(request: requestMessage, - cancellationToken: transportRequest.CancellationTokenSource.Token).ConfigureAwait(false); + cancellationToken:ctsWithTimeout?.Token??transportRequest.CancellationTokenSource.Token).ConfigureAwait(false); var responseContent = await httpResult.Content.ReadAsByteArrayAsync().ConfigureAwait(false); transportResponse = new TransportResponse() { @@ -56,26 +67,28 @@ public async Task GetRequest(TransportRequest transportReques Headers = httpResult.Headers.ToDictionary(h => h.Key, h => h.Value), RequestUrl = httpResult.RequestMessage?.RequestUri?.AbsolutePath }; - logger?.Debug($"HttpClient Service: Task Id: {Task.CurrentId} Received http response from server with status code {httpResult.StatusCode}, content-length: {transportResponse.Content.Length} bytes, for url \n{transportRequest.RequestUrl}"); + logger?.Debug( + $"HttpClient Service: Received http response from server with status code {httpResult.StatusCode}, content-length: {transportResponse.Content.Length} bytes, for url \n{transportRequest.RequestUrl}"); } catch (TaskCanceledException taskCanceledException) { - logger?.Error($"HttpClient Service: Task Id: {Task.CurrentId} Request is cancelled for url {transportRequest.RequestUrl}"); - transportResponse = new TransportResponse() - { - RequestUrl = transportRequest.RequestUrl, - Error = taskCanceledException - }; + transportResponse = GetTransportResponseForTaskCancelation(transportRequest, taskCanceledException, ctsWithTimeout); } catch (Exception e) { - logger?.Error($"HttpClient Service: Task Id: {Task.CurrentId} Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); + logger?.Error( + $"HttpClient Service: Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); transportResponse = new TransportResponse() { RequestUrl = transportRequest.RequestUrl, Error = e }; } + finally + { + ctsWithTimeout?.Dispose(); + transportRequest.CancellationTokenSource?.Dispose(); + } return transportResponse; } @@ -83,6 +96,7 @@ public async Task GetRequest(TransportRequest transportReques public async Task PostRequest(TransportRequest transportRequest) { TransportResponse transportResponse; + CancellationTokenSource ctsWithTimeout = null; try { HttpContent postData = null; @@ -102,9 +116,19 @@ public async Task PostRequest(TransportRequest transportReque HttpRequestMessage requestMessage = new HttpRequestMessage(method: HttpMethod.Post, requestUri: transportRequest.RequestUrl) { Content = postData }; - logger?.Debug($"HttpClient Service:Sending http request {transportRequest.RequestType} to {transportRequest.RequestUrl}" + (requestMessage.Headers.Any() ? $"\n Header {string.Join(", ", requestMessage.Headers.Select(kv => $"{kv.Key}: {kv.Value}"))}" : "")); + logger?.Debug( + $"HttpClient Service:Sending http request {transportRequest.RequestType} to {transportRequest.RequestUrl}" + + (requestMessage.Headers.Any() + ? $"\n Header {string.Join(", ", requestMessage.Headers.Select(kv => $"{kv.Key}: {kv.Value}"))}" + : "")); + if (transportRequest.Timeout.HasValue) + { + ctsWithTimeout = + CancellationTokenSource.CreateLinkedTokenSource(transportRequest.CancellationTokenSource.Token); + ctsWithTimeout.CancelAfter(transportRequest.Timeout.Value); + } var httpResult = await httpClient.SendAsync(request: requestMessage, - cancellationToken: transportRequest.CancellationTokenSource.Token).ConfigureAwait(false); + cancellationToken: ctsWithTimeout?.Token??transportRequest.CancellationTokenSource.Token).ConfigureAwait(false); var responseContent = await httpResult.Content.ReadAsByteArrayAsync().ConfigureAwait(false); transportResponse = new TransportResponse() { @@ -113,22 +137,28 @@ public async Task PostRequest(TransportRequest transportReque Headers = httpResult.Headers.ToDictionary(h => h.Key, h => h.Value), RequestUrl = httpResult.RequestMessage?.RequestUri?.AbsolutePath }; - logger?.Debug($"Received http response from server with status code {httpResult.StatusCode}, content-length: {transportResponse.Content.Length} bytes, for url {transportRequest.RequestUrl}"); + logger?.Debug( + $"Received http response from server with status code {httpResult.StatusCode}, content-length: {transportResponse.Content.Length} bytes, for url {transportRequest.RequestUrl}"); } - catch (TaskCanceledException) + catch (TaskCanceledException taskCanceledException) { - logger?.Error($"Request is cancelled for url {transportRequest.RequestUrl}"); - transportResponse = null; + transportResponse = GetTransportResponseForTaskCancelation(transportRequest, taskCanceledException, ctsWithTimeout); } catch (Exception e) { - logger?.Error($"Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); + logger?.Error( + $"Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); transportResponse = new TransportResponse() { RequestUrl = transportRequest.RequestUrl, Error = e }; } + finally + { + ctsWithTimeout?.Dispose(); + transportRequest.CancellationTokenSource?.Dispose(); + } return transportResponse; } @@ -136,6 +166,7 @@ public async Task PostRequest(TransportRequest transportReque public async Task PutRequest(TransportRequest transportRequest) { TransportResponse transportResponse; + CancellationTokenSource ctsWithTimeout = null; try { HttpContent putData = null; @@ -163,9 +194,20 @@ public async Task PutRequest(TransportRequest transportReques requestMessage.Headers.Add(kvp.Key, kvp.Value); } } - logger?.Debug($"HttpClient Service:Sending http request {transportRequest.RequestType} to {transportRequest.RequestUrl}" + (requestMessage.Headers.Any() ? $"\n Header {string.Join(", ", requestMessage.Headers.Select(kv => $"{kv.Key}: {kv.Value}"))}" : "")); + + logger?.Debug( + $"HttpClient Service:Sending http request {transportRequest.RequestType} to {transportRequest.RequestUrl}" + + (requestMessage.Headers.Any() + ? $"\n Header {string.Join(", ", requestMessage.Headers.Select(kv => $"{kv.Key}: {kv.Value}"))}" + : "")); + if (transportRequest.Timeout.HasValue) + { + ctsWithTimeout = + CancellationTokenSource.CreateLinkedTokenSource(transportRequest.CancellationTokenSource.Token); + ctsWithTimeout.CancelAfter(transportRequest.Timeout.Value); + } var httpResult = await httpClient.SendAsync(request: requestMessage, - cancellationToken: transportRequest.CancellationTokenSource.Token).ConfigureAwait(false); + cancellationToken: ctsWithTimeout?.Token??transportRequest.CancellationTokenSource.Token).ConfigureAwait(false); var responseContent = await httpResult.Content.ReadAsByteArrayAsync().ConfigureAwait(false); transportResponse = new TransportResponse() { @@ -174,22 +216,28 @@ public async Task PutRequest(TransportRequest transportReques Headers = httpResult.Headers.ToDictionary(h => h.Key, h => h.Value), RequestUrl = httpResult.RequestMessage?.RequestUri?.AbsolutePath }; - logger?.Debug($"Received http response from server with status code {httpResult.StatusCode}, content-length: {transportResponse.Content.Length} bytes, for url {transportRequest.RequestUrl}"); + logger?.Debug( + $"Received http response from server with status code {httpResult.StatusCode}, content-length: {transportResponse.Content.Length} bytes, for url {transportRequest.RequestUrl}"); } - catch (TaskCanceledException) + catch (TaskCanceledException taskCanceledException) { - logger?.Error($"Request is cancelled for url {transportRequest.RequestUrl}"); - transportResponse = null; + transportResponse = GetTransportResponseForTaskCancelation(transportRequest, taskCanceledException, ctsWithTimeout); } catch (Exception e) { - logger?.Error($"Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); + logger?.Error( + $"Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); transportResponse = new TransportResponse() { RequestUrl = transportRequest.RequestUrl, Error = e }; } + finally + { + ctsWithTimeout?.Dispose(); + transportRequest.CancellationTokenSource?.Dispose(); + } return transportResponse; } @@ -197,9 +245,9 @@ public async Task PutRequest(TransportRequest transportReques public async Task DeleteRequest(TransportRequest transportRequest) { TransportResponse transportResponse; + CancellationTokenSource ctsWithTimeout = null; try { - if (transportRequest.Timeout.HasValue) httpClient.Timeout = (TimeSpan)transportRequest.Timeout; HttpRequestMessage requestMessage = new HttpRequestMessage(method: HttpMethod.Delete, requestUri: transportRequest.RequestUrl); if (transportRequest.Headers.Keys.Count > 0) @@ -209,9 +257,20 @@ public async Task DeleteRequest(TransportRequest transportReq requestMessage.Headers.Add(kvp.Key, kvp.Value); } } - logger?.Debug($"HttpClient Service:Sending http request {transportRequest.RequestType} to {transportRequest.RequestUrl}" + (requestMessage.Headers.Any() ? $"\n Header {string.Join(", ", requestMessage.Headers.Select(kv => $"{kv.Key}: {kv.Value}"))}" : "")); + + logger?.Debug( + $"HttpClient Service:Sending http request {transportRequest.RequestType} to {transportRequest.RequestUrl}" + + (requestMessage.Headers.Any() + ? $"\n Header {string.Join(", ", requestMessage.Headers.Select(kv => $"{kv.Key}: {kv.Value}"))}" + : "")); + if (transportRequest.Timeout.HasValue) + { + ctsWithTimeout = + CancellationTokenSource.CreateLinkedTokenSource(transportRequest.CancellationTokenSource.Token); + ctsWithTimeout.CancelAfter(transportRequest.Timeout.Value); + } var httpResult = await httpClient.SendAsync(request: requestMessage, - cancellationToken: transportRequest.CancellationTokenSource.Token).ConfigureAwait(false); + cancellationToken: ctsWithTimeout?.Token??transportRequest.CancellationTokenSource.Token).ConfigureAwait(false); var responseContent = await httpResult.Content.ReadAsByteArrayAsync().ConfigureAwait(false); transportResponse = new TransportResponse() { @@ -220,22 +279,28 @@ public async Task DeleteRequest(TransportRequest transportReq Headers = httpResult.Headers.ToDictionary(h => h.Key, h => h.Value), RequestUrl = httpResult.RequestMessage?.RequestUri?.AbsolutePath }; - logger?.Debug($"Received http response from server with status code {httpResult.StatusCode}, content-length: {transportResponse.Content.Length} bytes, for url {transportRequest.RequestUrl}"); + logger?.Debug( + $"Received http response from server with status code {httpResult.StatusCode}, content-length: {transportResponse.Content.Length} bytes, for url {transportRequest.RequestUrl}"); } - catch (TaskCanceledException) + catch (TaskCanceledException taskCanceledException) { - logger?.Error($"Request is cancelled for url {transportRequest.RequestUrl}"); - transportResponse = null; + transportResponse = GetTransportResponseForTaskCancelation(transportRequest, taskCanceledException, ctsWithTimeout); } catch (Exception e) { - logger?.Error($"Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); + logger?.Error( + $"Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); transportResponse = new TransportResponse() { RequestUrl = transportRequest.RequestUrl, Error = e }; } + finally + { + ctsWithTimeout?.Dispose(); + transportRequest.CancellationTokenSource?.Dispose(); + } return transportResponse; } @@ -243,6 +308,7 @@ public async Task DeleteRequest(TransportRequest transportReq public async Task PatchRequest(TransportRequest transportRequest) { TransportResponse transportResponse; + CancellationTokenSource ctsWithTimeout = null; try { HttpContent patchData = null; @@ -271,9 +337,20 @@ public async Task PatchRequest(TransportRequest transportRequ requestMessage.Headers.Add(kvp.Key, $"\"{kvp.Value}\""); } } - logger?.Debug($"HttpClient Service:Sending http request {transportRequest.RequestType} to {transportRequest.RequestUrl}" + (requestMessage.Headers.Any() ? $"\n Header {string.Join(", ", requestMessage.Headers.Select(kv => $"{kv.Key}: {kv.Value}"))}" : "")); + + logger?.Debug( + $"HttpClient Service:Sending http request {transportRequest.RequestType} to {transportRequest.RequestUrl}" + + (requestMessage.Headers.Any() + ? $"\n Header {string.Join(", ", requestMessage.Headers.Select(kv => $"{kv.Key}: {kv.Value}"))}" + : "")); + if (transportRequest.Timeout.HasValue) + { + ctsWithTimeout = + CancellationTokenSource.CreateLinkedTokenSource(transportRequest.CancellationTokenSource.Token); + ctsWithTimeout.CancelAfter(transportRequest.Timeout.Value); + } var httpResult = await httpClient.SendAsync(request: requestMessage, - cancellationToken: transportRequest.CancellationTokenSource.Token).ConfigureAwait(false); + cancellationToken: ctsWithTimeout?.Token??transportRequest.CancellationTokenSource.Token).ConfigureAwait(false); var responseContent = await httpResult.Content.ReadAsByteArrayAsync().ConfigureAwait(false); transportResponse = new TransportResponse() { @@ -282,22 +359,53 @@ public async Task PatchRequest(TransportRequest transportRequ Headers = httpResult.Headers.ToDictionary(h => h.Key, h => h.Value), RequestUrl = httpResult.RequestMessage?.RequestUri?.AbsolutePath }; - logger?.Debug($"Received http response from server with status code {httpResult.StatusCode}, content-length: {transportResponse.Content.Length} bytes, for url {transportRequest.RequestUrl}"); + logger?.Debug( + $"Received http response from server with status code {httpResult.StatusCode}, content-length: {transportResponse.Content.Length} bytes, for url {transportRequest.RequestUrl}"); } - catch (TaskCanceledException) + catch (TaskCanceledException taskCanceledException) { - logger?.Error($"Request is cancelled for url {transportRequest.RequestUrl}"); - transportResponse = null; + transportResponse = GetTransportResponseForTaskCancelation(transportRequest, taskCanceledException, ctsWithTimeout); } catch (Exception e) { - logger?.Error($"Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); + logger?.Error( + $"Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); transportResponse = new TransportResponse() { RequestUrl = transportRequest.RequestUrl, Error = e }; } + finally + { + ctsWithTimeout?.Dispose(); + transportRequest.CancellationTokenSource?.Dispose(); + } + + return transportResponse; + } + private TransportResponse GetTransportResponseForTaskCancelation(TransportRequest transportRequest, + TaskCanceledException taskCanceledException, CancellationTokenSource ctsWithTimeout) + { + TransportResponse transportResponse; + + logger?.Error($"HttpClient Service: TaskCanceledException for url {transportRequest.RequestUrl}"); + transportResponse = new TransportResponse() + { + RequestUrl = transportRequest.RequestUrl, + Error = taskCanceledException, + }; + if (ctsWithTimeout is { Token.IsCancellationRequested: true } && + !transportRequest.CancellationTokenSource.IsCancellationRequested) + { + logger?.Debug("\n\n\n\n TASK TIMEOUT NETWORK ERROR \n\n\n\n\n"); + transportResponse.IsTimeOut = true; + } + else + { + logger?.Debug("\n\n\n\n TASK CANCELLED \n\n\n\n\n"); + transportResponse.IsCancelled = true; + } return transportResponse; } } diff --git a/src/Api/PubnubApi/Transport/Middleware.cs b/src/Api/PubnubApi/Transport/Middleware.cs index 1cddf6fc6..48792c73d 100644 --- a/src/Api/PubnubApi/Transport/Middleware.cs +++ b/src/Api/PubnubApi/Transport/Middleware.cs @@ -92,7 +92,6 @@ public TransportRequest PreapareTransportRequest(RequestParameter requestParamet var urlString = $"{(configuration.Secure ? "https://" : "http://")}{configuration.Origin}{pathString}?{UriUtil.BuildQueryString(requestParameter.Query)}"; var cts = new CancellationTokenSource(); - cts.CancelAfter(operationType == PNOperationType.PNSubscribeOperation ? configuration.SubscribeTimeout * 1000 : configuration.NonSubscribeRequestTimeout* 1000); var transportRequest = new TransportRequest() { RequestType = requestParameter.RequestType, @@ -100,8 +99,9 @@ public TransportRequest PreapareTransportRequest(RequestParameter requestParamet BodyContentString = requestParameter.BodyContentString, FormData = requestParameter.FormData, CancellationTokenSource = cts, + Timeout = operationType == PNOperationType.PNSubscribeOperation ? TimeSpan.FromSeconds(configuration.SubscribeTimeout) : TimeSpan.FromSeconds(configuration.NonSubscribeRequestTimeout) }; - if(requestParameter.Headers.Count>0) transportRequest.Headers = requestParameter.Headers; + if (requestParameter.Headers.Count > 0) transportRequest.Headers = requestParameter.Headers; return transportRequest; } diff --git a/src/Api/PubnubApi/TransportContract/TransportResponse.cs b/src/Api/PubnubApi/TransportContract/TransportResponse.cs index ae106ec0b..cae84a683 100644 --- a/src/Api/PubnubApi/TransportContract/TransportResponse.cs +++ b/src/Api/PubnubApi/TransportContract/TransportResponse.cs @@ -10,5 +10,9 @@ public class TransportResponse public Dictionary> Headers { get; set; } public string RequestUrl { get; set; } public Exception Error { get; set; } + + public bool IsTimeOut {get; set;} + + public bool IsCancelled { get; set; } } } \ No newline at end of file diff --git a/src/Api/PubnubApiPCL/PubnubApiPCL.csproj b/src/Api/PubnubApiPCL/PubnubApiPCL.csproj index ba7cf105e..8f367c313 100644 --- a/src/Api/PubnubApiPCL/PubnubApiPCL.csproj +++ b/src/Api/PubnubApiPCL/PubnubApiPCL.csproj @@ -14,7 +14,7 @@ PubnubPCL - 7.3.6.0 + 7.3.7.0 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -22,7 +22,7 @@ http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png true https://github.com/pubnub/c-sharp/ - Reafctor: Removed excess logging from transport module. Removed redundant logging from transport layer and added thread id information for tracking http request. + Enhance request handling with detailed response interface. Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing 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 diff --git a/src/Api/PubnubApiUWP/PubnubApiUWP.csproj b/src/Api/PubnubApiUWP/PubnubApiUWP.csproj index 47322c0d5..c16cd7ec7 100644 --- a/src/Api/PubnubApiUWP/PubnubApiUWP.csproj +++ b/src/Api/PubnubApiUWP/PubnubApiUWP.csproj @@ -16,7 +16,7 @@ PubnubUWP - 7.3.6.0 + 7.3.7.0 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -24,7 +24,7 @@ http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png true https://github.com/pubnub/c-sharp/ - Reafctor: Removed excess logging from transport module. Removed redundant logging from transport layer and added thread id information for tracking http request. + Enhance request handling with detailed response interface. Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing 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 diff --git a/src/Api/PubnubApiUnity/PubnubApiUnity.csproj b/src/Api/PubnubApiUnity/PubnubApiUnity.csproj index 0a27a324d..aac56470d 100644 --- a/src/Api/PubnubApiUnity/PubnubApiUnity.csproj +++ b/src/Api/PubnubApiUnity/PubnubApiUnity.csproj @@ -14,7 +14,7 @@ PubnubApiUnity - 7.3.6.0 + 7.3.7.0 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub