Skip to content

Exception when disposing BetterStackLoggerProvider #1

@Jericho

Description

@Jericho

I am referencing Formitable.BetterStack.Logger.Microsoft v0.1.2 and I get the following exception when my app is terminating:

System.AggregateException
  HResult=0x80131500
  Message=One or more errors occurred. (The CancellationTokenSource has been disposed.)
  Source=System.Private.CoreLib
  StackTrace:
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait(TimeSpan timeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait(TimeSpan timeout)
   at Formitable.BetterStack.Logger.Microsoft.BetterStackLoggerProvider.Dispose()
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.Dispose()
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.Dispose()
   at Picton.IntegrationTests.Program.<Main>d__0.MoveNext() in D:\_build\Picton\Source\Picton.IntegrationTests\Program.cs:line 25
   at Picton.IntegrationTests.Program.<Main>d__0.MoveNext() in D:\_build\Picton\Source\Picton.IntegrationTests\Program.cs:line 26
   at Picton.IntegrationTests.Program.<Main>()

  This exception was originally thrown at this call stack:
    [External Code]

Inner Exception 1:
ObjectDisposedException: The CancellationTokenSource has been disposed.

This is due to the fact that the cancellation token is disposed in the Dispose method like so:

_cancellationTokenSource.Dispose();

meanwhile the _flushTask is still running and one of the lines in this flush task is the following:

while (!_cancellationTokenSource.IsCancellationRequested)

This line causes the exception because the cancellation token has been disposed.

I propose the following changes in the Dispose method to resolve this problem:

  1. Invoke Cancel() on the cancellation token source rather than disposing it. This ensures that no exception is thrown in "_flushTask" and _cancellationTokenSource.IsCancellationRequested can be evaluated without problem. It will return true, causing the task to stop gracefully which is the desired behavior.
  2. There's a possibility that a few messages might still be in the queue when "_flushTask" is stopped. Therefore the try ... catch ... code in Dispose needs to be altered to process these remaining messages.
  3. Add a finally section to the try ... catch ... where the cancellation token source can be safely disposed.

I will submit a PR where I implement the changes I propose.

For reference, this is what my app looks like:
namespace Picton.IntegrationTests
{
	public class Program
	{
		public static async Task Main()
		{
			var cts = new CancellationTokenSource();
			Console.CancelKeyPress += (s, e) =>
			{
				e.Cancel = true;
				cts.Cancel();
			};

			var services = new ServiceCollection();
			ConfigureServices(services);
			using var serviceProvider = services.BuildServiceProvider();
			var app = serviceProvider.GetService<IHostedService>();
			await app.StartAsync(cts.Token).ConfigureAwait(false);
		}

		private static void ConfigureServices(ServiceCollection services)
		{
			services.AddHostedService<TestsRunner>();

			services
				.AddLogging(logging =>
				{
					var betterStackToken = Environment.GetEnvironmentVariable("BETTERSTACK_TOKEN");
					if (!string.IsNullOrEmpty(betterStackToken))
					{
						logging.AddBetterStackLogger(options =>
						{
							options.SourceToken = betterStackToken;
						});
					}

					logging.AddSimpleConsole(options =>
					{
						options.SingleLine = true;
						options.TimestampFormat = "yyyy-MM-dd HH:mm:ss ";
					});

					logging.AddFilter("*", LogLevel.Debug);
				});
		}
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions